Source code for caelus.utils.osutils
# -*- coding: utf-8 -*-
"""\
Miscellaneous utilities
-----------------------
This module implements functions that are utilized throughout CPL. They mostly
provide a higher-level interface to various ``os.path`` functions to make it
easier to perform some tasks.
.. autosummary::
:nosignatures:
set_work_dir
ensure_directory
abspath
ostype
timestamp
"""
import fnmatch
import logging
import os
import shutil
from contextlib import contextmanager
from datetime import datetime
from pathlib import Path
import pytz
_lgr = logging.getLogger(__name__)
[docs]
def ostype():
"""String indicating the operating system type
Returns:
str: One of ["linux", "darwin", "windows"]
"""
return "windows" if os.name == 'nt' else os.uname()[0].lower()
[docs]
def timestamp(time_format=None, time_zone=pytz.utc):
"""Return a formatted timestamp for embedding in files
Args:
time_format: A time formatter suitable for strftime
time_zone: Time zone used to generate timestamp (Default: UTC)
Returns:
str: A formatted time string
"""
time_fmt = time_format or "%Y-%m-%d %H:%M:%S (%Z)"
return datetime.now(time_zone).strftime(time_fmt)
[docs]
def backup_file(fname, time_format=None, time_zone=pytz.utc):
"""Given a filename return a timestamp based backup filename
Args:
time_format: A time formatter suitable for strftime
time_zone: Time zone used to generate timestamp (Default: UTC)
Returns:
str: A timestamped filename suitable for creating backups
"""
bname = os.path.basename(fname)
name, ext = os.path.splitext(bname)
time_fmt = time_format or "%Y%m%d-%H%M%S-%Z"
tstamp = datetime.now(time_zone).strftime(time_fmt)
bak_name = name + "_" + tstamp + ext
return os.path.join(os.path.dirname(fname), bak_name)
[docs]
def username():
"""Return the username of the current user"""
import getpass
return getpass.getuser()
[docs]
def user_home_dir():
"""Return the absolute path of the user's home directory"""
try:
path = os.path.expanduser("~")
except ImportError:
pass
else:
if os.path.isdir(path):
return path
for envvar in "HOME USERPROFILE".split():
path = os.environ.get(envvar)
if path is not None and os.path.isdir(path):
return path
return None
[docs]
def abspath(pname):
"""Return the absolute path of the directory.
This function expands the user home directory as well as any shell
variables found in the path provided and returns an absolute path.
Args:
pname (path): Pathname to be expanded
Returns:
path: Absolute path after all substitutions
"""
pth1 = os.path.expanduser(pname)
pth2 = os.path.expandvars(pth1)
return os.path.normpath(os.path.abspath(pth2))
[docs]
def path_exists(pname):
"""Check path of the directory exists.
This function expands the user home directory as well as any shell
variables found in the path provided and checks if that path exists.
Args:
pname (path): Pathname to be checked
Returns:
bool: True if path exists
"""
return os.path.exists(abspath(pname))
[docs]
def ensure_directory(dname):
"""Check if directory exists, if not, create it.
Args:
dname (path): Directory name to check for
Returns:
Path: Absolute path to the directory
"""
abs_dir = abspath(dname)
if not os.path.exists(abs_dir):
os.makedirs(abs_dir)
return abs_dir
[docs]
@contextmanager
def set_work_dir(dname, create=False):
"""A with-block to execute code in a given directory.
Args:
dname (path): Path to the working directory.
create (bool): If true, directory is created prior to execution
Returns:
path: Absolute path to the execution directory
Example:
>>> with osutils.set_work_dir("results_dir", create=True) as wdir:
... with open(os.path.join(wdir, "results.dat"), 'w') as fh:
... fh.write("Data")
"""
abs_dir = abspath(dname)
if create:
ensure_directory(abs_dir)
orig_dir = os.getcwd()
try:
os.chdir(abs_dir)
yield abs_dir
finally:
os.chdir(orig_dir)
[docs]
def clean_directory(dirname, preserve_patterns=None):
"""Utility function to remove files and directories from a given directory.
User can specify a list of filename patterns to preserve with the
``preserve_patterns`` argument. These patterns can contain shell wildcards
to glob multiple files.
Args:
dirname (path): Absolute path to the directory whose entries are purged.
preserve_patterns (list): A list of shell wildcard patterns
"""
_lgr.debug("Removing files in directory: %s", dirname)
ppatterns = preserve_patterns or []
with set_work_dir(dirname) as wdir:
for fpath in os.listdir(wdir):
is_preserve = False
for pp in ppatterns:
if fnmatch.fnmatch(fpath, pp):
is_preserve = True
break
if is_preserve:
continue
if os.path.isdir(fpath):
shutil.rmtree(fpath)
elif os.path.isfile(fpath) or os.path.islink(fpath):
os.remove(fpath)
[docs]
def remove_files_dirs(paths, basedir=None):
"""Remove files and/or directories
Args:
paths (list): A list of file paths to delete (no patterns allowed)
basedir (path): Base directory to search
"""
wdir = basedir or os.getcwd()
with set_work_dir(wdir):
for fpath in paths:
if os.path.exists(fpath):
if os.path.isdir(fpath):
shutil.rmtree(fpath)
elif os.path.isfile(fpath) or os.path.islink(fpath):
os.remove(fpath)
[docs]
def copy_tree(srcdir, destdir, symlinks=False, ignore_func=None):
"""Enchanced version of shutil.copytree
- removes the output directory if it already exists.
Args:
srcdir (path): path to source directory to be copied.
destdir (path): path (or new name) of destination directory.
symlinks (bool): as in shutil.copytree
ignore_func (func): as in shutil.copytree
"""
if os.path.exists(destdir):
shutil.rmtree(destdir)
shutil.copytree(srcdir, destdir, symlinks, ignore_func)
[docs]
def split_path(fname):
"""Split a path into directory, basename, extension
Returns:
tuple: (directory, basename, extension)
"""
abs_fname = abspath(fname)
fdir = os.path.dirname(abs_fname)
ftmp = os.path.basename(abs_fname)
base, ext = os.path.splitext(ftmp)
return (fdir, base, ext)
[docs]
def path_or_cwd(pdir: Path | str | None) -> Path:
"""Sanitize the given path. If None, return current workding directory"""
return Path(abspath(str(pdir))) if pdir is not None else Path.cwd()