# coding: utf-8
# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
# Distributed under the terms of "New BSD License", see the LICENSE file.
from __future__ import print_function
from copy import copy
import os
import posixpath
from pyiron.base.settings.generic import Settings
from six import string_types
"""
Classes for representing the file system path in pyiron
"""
__author__ = "Jan Janssen, Joerg Neugebauer"
__copyright__ = (
"Copyright 2020, Max-Planck-Institut für Eisenforschung GmbH - "
"Computational Materials Design (CM) Department"
)
__version__ = "1.0"
__maintainer__ = "Jan Janssen"
__email__ = "janssen@mpie.de"
__status__ = "production"
__date__ = "Sep 1, 2017"
[docs]class GenericPath(object):
"""
Basic class to represent a project path in PyIron. A path consists of two parts, the root
part which defines directory path where the project repository is located (top_level_path)
and the project part which defines the relative path from the root_path to the project.
This class is meant for storing and accessing a path, not for moving around which is done by
the ProjectPath class.
Args:
root_path (str): absolute path name of the repository
project_path (str): relative path to the specific project
Attributes:
.. attribute:: root_path
the pyiron user directory, defined in the .pyiron configuration
.. attribute:: project_path
the relative path of the current project / folder starting from the root path
of the pyiron user directory
.. attribute:: path
the absolute path of the current project / folder
.. attribute:: base_name
the name of the current project / folder
Author: Jan Janssen
"""
def __init__(self, root_path, project_path):
self._root_path = None
self._project_path = None
self.root_path = root_path
self.project_path = project_path
@property
def root_path(self):
"""
the pyiron user directory, defined in the .pyiron configuration
Returns:
str: pyiron user directory of the current project
"""
return self._root_path
@root_path.setter
def root_path(self, new_path):
"""
the pyiron user directory, defined in the .pyiron configuration
Args:
new_path (str): new pyiron root path
"""
self._root_path = self._windows_path_to_unix_path(new_path)
@property
def project_path(self):
"""
the relative path of the current project / folder starting from the root path
of the pyiron user directory
Returns:
str: relative path of the current project / folder
"""
if self._project_path[-1] != "/":
self._project_path += "/"
return self._project_path
@project_path.setter
def project_path(self, new_path):
"""
the relative path of the current project / folder starting from the root path
of the pyiron user directory
Args:
new_path (str): new pyiron project path
"""
self._project_path = self._windows_path_to_unix_path(
posixpath.normpath(new_path)
)
@property
def path(self):
"""
The absolute path to of the current object.
Returns:
str: current project path
"""
if self.root_path is not None:
return posixpath.join(self.root_path, self.project_path)
else:
return self.project_path
@property
def base_name(self):
"""
The name of the current project folder
Returns:
str: name of the current project folder
"""
if self.project_path[-1] in ["/", "\\"]:
return self.project_path.split("/")[-2]
else:
return self.project_path.split("/")[-1]
[docs] def copy(self):
"""
Copy the GenericPath object
Returns:
GenericPath: independent GenericPath object pointing to the same project folder
"""
return copy(self)
def __copy__(self):
"""
Copy the GenericPath object
Returns:
GenericPath: independent GenericPath object pointing to the same project folder
"""
return GenericPath(self.root_path, self.project_path)
def __repr__(self):
"""
String representation of the GenericPath object
Returns:
str: string representation of the root and the project path
"""
project_str = "Project path: \n"
project_str += " root: " + str(self.root_path) + "\n"
project_str += " project: " + str(self.project_path) + "\n"
return project_str
def __str__(self):
"""
String representation of the GenericPath object
Returns:
str: string representation of the absolute path
"""
return self.path
@staticmethod
def _windows_path_to_unix_path(path):
"""
Helperfunction to covert windows path into unix path
Args:
path (str): input path in windows or unix format
Returns:
str: output path in unix format
"""
if path is not None:
linux_path = path.replace("\\", "/")
if linux_path[-1] != "/":
linux_path += "/"
return linux_path
else:
return None
[docs]class ProjectPath(GenericPath):
def __init__(self, path):
"""
Open a new or an existing project. The project is defined by providing a relative or an
absolute path. If no path is provided the current working directory is used. This is the
main class to walk inside the project structure, create new jobs, subprojects etc.
Note: Changing the path has no effect on the current working directory
Args:
path (GenericPath, str): path of the project defined by GenericPath, absolute or relative (with respect to
current working directory) path
.. attribute:: root_path
the pyiron user directory, defined in the .pyiron configuration
.. attribute:: project_path
the relative path of the current project / folder starting from the root path
of the pyiron user directory
.. attribute:: path
the absolute path of the current project / folder
.. attribute:: base_name
the name of the current project / folder
.. attribute:: history
previously opened projects / folders
"""
if path == "":
raise ValueError("ProjectPath: path is not allowed to be empty!")
generic_path = self._convert_str_to_generic_path(path)
super(ProjectPath, self).__init__(
generic_path.root_path, generic_path.project_path
)
self._history = []
@property
def history(self):
"""
The history of the previously opened paths
Returns:
list: list of previously opened relative paths
"""
return self._history
[docs] def open(self, rel_path, history=True):
"""
if rel_path exist set the project path to this directory
if not create it and go there
Args:
rel_path (str): path relative to the current project path
history (bool): By default pyiron stores a history of previously opened paths
Returns:
ProjectPath: New ProjectPath object pointing to the relative path
"""
new_project = self.copy()
new_project._create_path(new_project.path, rel_path)
new_project.project_path = os.path.normpath(
os.path.join(new_project.project_path, rel_path)
).replace("\\", "/")
if history:
new_project.history.append(rel_path)
return new_project
[docs] def close(self):
"""
return to the path before the last open if no history exists nothing happens
"""
if self.history:
path_lst = self.project_path.split("/")
hist_lst = self.history[-1].split("/")
self.project_path = "/".join(path_lst[: -len(hist_lst)])
del self.history[-1]
[docs] def copy(self):
"""
copy the path without the history, i.e., to going back with close is not possible
Returns:
ProjectPath:
"""
return ProjectPath(path=self.path)
[docs] def removedirs(self, project_name=None):
"""
equivalent to os.removedirs -> remove empty dirs
Args:
project_name (str): relative path to the project folder to be deleted
"""
try:
if project_name:
os.removedirs(os.path.join(self.path, project_name))
else:
os.removedirs(os.path.join(self.path))
except OSError:
pass
[docs] def listdir(self):
"""
equivalent to os.listdir
list all files and directories in this path
Returns:
list: list of folders and files in the current project path
"""
try:
return os.listdir(self.path)
except OSError:
return []
[docs] def walk(self):
"""
equivalent to os.listdir
list all files and directories in this path
Returns:
Generator: Directory tree generator.
"""
return os.walk(self.path)
def __repr__(self):
"""
String representation of the ProjectPath object
Returns:
str: string representation of the root and the project path including the path history
"""
project_str = super(ProjectPath, self).__repr__()
if len(self.history) > 0:
project_str += " history: " + str(self.history) + "\n"
return project_str
def __enter__(self):
"""
Helper function to support with calls
Returns:
ProjectPath: The object itself
"""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""
Helper function to support with calls
Args:
exc_type: Python internal
exc_val: Python internal
exc_tb: Python internal
"""
self.close()
def _convert_str_to_generic_path(self, path):
"""
Convert path in string representation to an GenericPath object
Args:
path (str): absolute path
Returns:
GenericPath: GenericPath object pointing to the absolute path
"""
if isinstance(path, GenericPath):
return path
elif isinstance(path, string_types):
path = os.path.normpath(path)
if not os.path.isabs(path):
path_local = self._windows_path_to_unix_path(
posixpath.abspath(os.curdir)
)
self._create_path(path_local, path)
path = posixpath.join(path_local, path)
elif not os.path.exists(path) and os.path.exists(
os.path.normpath(os.path.join(path, ".."))
):
self._create_path(path)
# else:
# raise ValueError(path, ' does not exist!')
path = self._windows_path_to_unix_path(path)
root_path, project_path = self._get_project_from_path(path)
return GenericPath(root_path, project_path)
else:
raise TypeError("Only string and GenericPath objects are supported.")
def _create_path(self, path, rel_path=None):
"""
Create the directory if it does not exist already using os.makedirs()
Args:
path (str): absolute path
rel_path (str): relative path starting from the absolute path (optional)
"""
if rel_path:
rel_path = self._windows_path_to_unix_path(rel_path)
path = posixpath.join(path, rel_path)
try:
os.makedirs(path)
except os.error:
pass
@staticmethod
def _get_project_from_path(full_path):
"""
Split the absolute path in root_path and project_path using the top_path function in Settings()
Args:
full_path (str): absolute path
Returns:
str, str: root_path, project_path
"""
root = Settings().top_path(full_path)
if root is not None:
pr_path = posixpath.relpath(full_path, root)
return root, pr_path
else:
return None, full_path