def get_dirs(*args: Any, pkgname: OptStr = None, **kwds: Any) -> StrDict: """Get application specific environmental directories. This function returns application specific system directories by platform independent names to allow platform independent storage for caching, logging, configuration and permanent data storage. Args: *args: Optional arguments that specify the application, as required by the function '.env.update_dirs'. **kwds: Optional keyword arguments that specify the application, as required by the function '.env.update_dirs'. Returns: Dictionary containing paths of application specific environmental directories. """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) # Update appdirs if not present or if optional arguments are given try: return globals()['cache'][pkgname]['dirs'].copy() except KeyError: update_dirs(*args, pkgname=pkgname, **kwds) return globals()['cache'][pkgname]['dirs'].copy()
def copytree(source: NestPath, target: NestPath, pkgname: OptStr = None) -> None: """Copy directory structure from given source to target directory. Args: source: Path like structure, which comprises the path of a source folder target: Path like structure, which comprises the path of a destination folder Returns: True if the operation was successful. """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) # Recursive copy function, that allows existing files def copy(source: pathlib.Path, target: pathlib.Path) -> None: if source.is_dir(): if not target.is_dir(): target.mkdir() for each in source.glob('*'): copy(each, target / each.name) else: shutil.copy(source, target) copy(expand(source, pkgname=pkgname), expand(target, pkgname=pkgname))
def get_vars(*args: Any, pkgname: OptStr = None, **kwds: Any) -> StrDict: """Get dictionary with environment and application variables. Environment variables comprise static and runtime properties of the operating system like 'username' or 'hostname'. Application variables in turn, are intended to describe the application distribution by authorship information, bibliographic information, status, formal conditions and notes or warnings. For mor information see :PEP:`345`. Args: *args: Optional arguments that specify the application, as required by :func:`~hup.base.env.update_vars`. **kwds: Optional keyword arguments that specify the application, as required by :func:`~hup.base.env.update_vars`. Returns: Dictionary containing application variables. """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) # Update variables if not present or if optional arguments are given try: return globals()['cache'][pkgname]['vars'].copy() except KeyError: update_vars(*args, pkgname=pkgname, **kwds) return globals()['cache'][pkgname]['vars'].copy()
def get_module(name: OptStr = None, errors: bool = True) -> OptModule: """Get reference to a module instance. Args: name: Optional name of module- If provided, the name is required to be a fully qualified name. By default a refrence to the module of the current caller is returned. errors: Boolean value which determines if an error is raised, if the module could not be found. By default errors are raised. Returns: Module reference or None, if the name does not point to a valid module. """ # Set default values name = name or stack.get_caller_module_name(-1) # Try to import a module with importlib try: return importlib.import_module(name) except ModuleNotFoundError: if errors: raise return None except ImportError: if errors: raise return None
def get_dir(dirname: str, *args: Any, pkgname: OptStr = None, **kwds: Any) -> pathlib.Path: """Get application specific environmental directory by name. This function returns application specific system directories by platform independent names to allow platform independent storage for caching, logging, configuration and permanent data storage. Args: dirname: Environmental directory name. Allowed values are: :user_cache_dir: Cache directory of user :user_config_dir: Configuration directory of user :user_data_dir: Data directory of user :user_log_dir: Logging directory of user :site_config_dir: Site global configuration directory :site_data_dir: Site global data directory :site_package_dir: Site global package directory :site_temp_dir: Site global directory for temporary files :package_dir: Current package directory :package_data_dir: Current package data directory *args: Optional arguments that specify the application, as required by the function '.env.update_dirs'. **kwds: Optional keyword arguments that specify the application, as required by the function '.env.update_dirs'. Returns: String containing path of environmental directory or None if the pathname is not supported. """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) # Check type of 'dirname' check.has_type("'dirname'", dirname, str) # Update derectories if not present or if any optional arguments are given try: dirs = globals()['cache'][pkgname]['dirs'] except KeyError: update_dirs(*args, pkgname=pkgname, **kwds) dirs = globals()['cache'][pkgname]['dirs'] # Check value of 'dirname' if dirname not in dirs: raise ValueError(f"directory name '{dirname}' is not valid") return dirs[dirname]
def touch(path: NestPath, parents: bool = True, mode: int = 0o666, exist_ok: bool = True, pkgname: OptStr = None) -> bool: """Create an empty file at the specified path. Args: path: Nested :term:`path-like object`, which represents a valid filename in the directory structure of the operating system. parents: Boolean value, which determines if missing parents of the path are created as needed. mode: Integer value, which specifies the properties if the file. For more information see :func:`os.chmod`. exist_ok: Boolean value which determines, if the function returns False, if the file already exists. Returns: True if the file could be created, else False. """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) filepath = expand(path, pkgname=pkgname) # Check type of 'filepath' if not isinstance(filepath, pathlib.Path): return False # Check if directory exists and optionally create it dirpath = filepath.parent if not dirpath.is_dir(): if not parents: return False dirpath.mkdir(parents=True, exist_ok=True) if not dirpath.is_dir(): return False # Check if file already exsists if filepath.is_file() and not exist_ok: return False # Touch file with given filepath.touch(mode=mode, exist_ok=exist_ok) return filepath.is_file()
def is_file(path: NestPath, pkgname: OptStr = None) -> bool: """Determine if given path points to a file. Extends :meth:`pathlib.Path.is_file` by nested paths and path variable expansion. Args: path: Path like structure, which is expandable to a valid path. Returns: True if the path points to a directory (or a symbolic link pointing to a directory), False if it points to another kind of file. """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) return expand(path, pkgname=pkgname).is_file()
def rmdir(*args: NestPath, pkgname: OptStr = None) -> bool: """Remove directory. Args: *args: Path like structure, which identifies the path of a directory Returns: True if the directory could be deleted """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) path = expand(*args, pkgname=pkgname) if not path.is_dir(): return False shutil.rmtree(str(path), ignore_errors=True) return not path.exists()
def fileext(*args: NestPath, pkgname: OptStr = None) -> str: """Fileextension of file. Args: *args: Path like arguments, respectively given by a tree of strings, which can be joined to a path. Returns: String containing fileextension of file. Examples: >>> fileext(('a', ('b', 'c')), 'base.ext') 'ext' """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) path = expand(*args, pkgname=pkgname) if path.is_dir(): return '' return str(path.suffix).lstrip('.')
def filename(*args: NestPath, pkgname: OptStr = None) -> str: """Extract file name from a path like structure. Args: *args: Path like arguments, respectively given by a tree of strings, which can be joined to a path. Returns: String containing normalized directory path of file. Examples: >>> filename(('a', ('b', 'c')), 'base.ext') 'base.ext' """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) path = expand(*args, pkgname=pkgname) if path.is_dir(): return '' return str(path.name)
def update_vars(filepath: OptPathLike = None, pkgname: OptStr = None) -> None: """Update environment and application variables. Environment variables comprise static and runtime properties of the operating system like 'username' or 'hostname'. Application variables in turn, are intended to describe the application distribution by authorship information, bibliographic information, status, formal conditions and notes or warnings. For mor information see :PEP:`345`. Args: filepath: Valid filepath to python module, that contains the application variables as module attributes. By default the current top level module is used. """ # Get package specific environment variables by parsing a given file for # module attributes. By default the file of the callers current top level # module is taken. If the name is not given, then use the name of the # current top level module. pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) filepath = filepath or pkg.get_module(pkgname).__file__ text = pathlib.Path(filepath).read_text() pattern = r"^[ ]*__([^\d\W]\w*)__[ ]*=[ ]*['\"]([^'\"]*)['\"]" matches = re.finditer(pattern, text, re.M) info = {str(m.group(1)): str(m.group(2)) for m in matches} info['name'] = info.get('name', pkgname) # Get plattform specific environment variables info['encoding'] = get_encoding() info['hostname'] = get_hostname() info['osname'] = get_osname() info['username'] = get_username() # Update globals if not 'cache' in globals(): globals()['cache'] = {} if not pkgname in globals()['cache']: globals()['cache'][pkgname] = {} globals()['cache'][pkgname]['vars'] = info
def mkdir(*args: NestPath, pkgname: OptStr = None) -> bool: """Create directory. Args: *args: Path like structure, which comprises the path of a new directory Returns: True if the directory already exists, or the operation was successful. """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) path = expand(*args, pkgname=pkgname) if path.is_dir(): return True try: os.makedirs(path) except Exception as err: raise OSError("could not create directory") from err return path.is_dir()
def get_var(varname: str, *args: Any, pkgname: OptStr = None, **kwds: Any) -> OptStr: """Get environment or application variable. Environment variables comprise static and runtime properties of the operating system like 'username' or 'hostname'. Application variables in turn, are intended to describe the application distribution by authorship information, bibliographic information, status, formal conditions and notes or warnings. For mor information see :PEP:`345`. Args: varname: Name of environment variable. Typical application variable names are: 'name': The name of the distribution 'version': A string containing the distribution's version number 'status': Development status of the distributed application. Typical values are 'Prototype', 'Development', or 'Production' 'description': A longer description of the distribution that can run to several paragraphs. 'keywords': A list of additional keywords to be used to assist searching for the distribution in a larger catalog. 'url': A string containing the URL for the distribution's homepage. 'license': Text indicating the license covering the distribution 'copyright': Notice of statutorily prescribed form that informs users of the distribution to published copyright ownership. 'author': A string containing the author's name at a minimum; additional contact information may be provided. 'email': A string containing the author's e-mail address. It can contain a name and e-mail address, as described in :rfc:`822`. 'maintainer': A string containing the maintainer's name at a minimum; additional contact information may be provided. 'company': The company, which created or maintains the distribution. 'organization': The organization, twhich created or maintains the distribution. 'credits': list with strings, acknowledging further contributors, Teams or supporting organizations. *args: Optional arguments that specify the application, as required by the function :func:`~hup.base.env.update_vars`. pkgname: **kwds: Optional keyword arguments that specify the application, as required by the function :func:`~hup.base.env.update_vars`. Returns: String representing the value of the application variable. """ # Check type of 'varname' check.has_type("'varname'", varname, str) # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) # Update variables if not present or if optional arguments are given try: appvars = globals()['cache'][pkgname]['vars'] except KeyError: update_vars(*args, pkgname=pkgname, **kwds) appvars = globals()['cache'][pkgname]['vars'] return appvars.get(varname, None)
def expand(*args: NestPath, udict: OptStrDict = None, pkgname: OptStr = None, envdirs: bool = True) -> pathlib.Path: r"""Expand path variables. Args: *args: Path like arguments, respectively given by a tree of strings, which can be joined to a path. udict: dictionary for user variables. Thereby the keys in the dictionary are encapsulated by the symbol '%'. The user variables may also include references. envdirs: Boolen value which determines if environmental path variables are expanded. For a full list of valid environmental path variables see '.env.get_dirs'. Default is True Returns: String containing valid path syntax. Examples: >>> expand('%var1%/c', 'd', udict = {'var1': 'a/%var2%', 'var2': 'b'}) 'a\\b\\c\\d' """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) path = join_path(*args) udict = udict or {} # Create mapping with path variables pvars = {} if envdirs: for key, val in get_dirs(pkgname=pkgname).items(): pvars[key] = str(val) if udict: for key, val in udict.items(): pvars[key] = str(join_path(val)) # Itereratively expand directories update = True i = 0 while update: update = False for key, val in pvars.items(): if '%' + key + '%' not in str(path): continue try: path = pathlib.Path(str(path).replace('%' + key + '%', val)) except TypeError: del pvars[key] update = True i += 1 if i > sys.getrecursionlimit(): raise RecursionError('cyclic dependency in variables detected') path = pathlib.Path(path) # Expand unix style home path '~' if envdirs: path = path.expanduser() return path
def update_dirs(appname: OptStr = None, appauthor: OptStrOrBool = None, version: OptStr = None, pkgname: OptStr = None, **kwds: Any) -> None: """Update application specific directories from name, author and version. This function retrieves application specific directories from the package `appdirs`_. Additionally the directory 'site_package_dir' is retrieved fom the standard library package distutils and 'package_dir' and 'package_data_dir' from the current top level module. Args: appname: is the name of application. If None, just the system directory is returned. appauthor: is the name of the appauthor or distributing body for this application. Typically it is the owning company name. You may pass False to disable it. Only applied in windows. version: is an optional version path element to append to the path. You might want to use this if you want multiple versions of your app to be able to run independently. If used, this would typically be "<major>.<minor>". Only applied when appname is present. **kwds: Optional directory name specific keyword arguments. For more information see `appdirs`_. .. _appdirs: http://github.com/ActiveState/appdirs """ # Get package name from callers top level module pkgname = pkgname or pkg.get_root_name(stack.get_caller_module_name(-2)) # Get system directories dirs: StrDictOfPaths = {} dirs['home'] = get_home() dirs['cwd'] = get_cwd() # Get application directories from appdirs appname = appname or get_var('name', pkgname=pkgname) appauthor = appauthor or get_var('author', pkgname=pkgname) app_dirs = appdirs.AppDirs(appname=appname, appauthor=appauthor, version=version, **kwds) dirnames = [ 'user_cache_dir', 'user_config_dir', 'user_data_dir', 'user_log_dir', 'site_config_dir', 'site_data_dir' ] for dirname in dirnames: dirs[dirname] = pathlib.Path(getattr(app_dirs, dirname)) # Get distribution directories from distutils path = pathlib.Path(sysconfig.get_python_lib(), appname) dirs['site_package_dir'] = path # Tempdir from module tempfile tempdir = pathlib.Path(tempfile.gettempdir()) dirs['site_temp_dir'] = tempdir # Get current package directories from top level module path = pathlib.Path(pkg.get_root().__file__).parent dirs['package_dir'] = path dirs['package_data_dir'] = path / 'data' dirs['package_temp_dir'] = tempdir / appname # Create package temp dir if it does not exist dirs['package_temp_dir'].mkdir(parents=True, exist_ok=True) # type: ignore # Update globals if not 'cache' in globals(): globals()['cache'] = {} if not pkgname in globals()['cache']: globals()['cache'][pkgname] = {} globals()['cache'][pkgname]['dirs'] = dirs
def test_get_caller_module_name(self) -> None: name = stack.get_caller_module_name() self.assertEqual(name, __name__)