Coverage for python/lsst/resources/utils.py: 39%
57 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-30 11:34 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-30 11:34 +0000
1# This file is part of lsst-resources.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14__all__ = ("os2posix", "posix2os", "NoTransaction", "TransactionProtocol")
16import contextlib
17import logging
18import os
19import posixpath
20import shutil
21import stat
22import tempfile
23from collections.abc import Callable, Iterator
24from pathlib import Path, PurePath, PurePosixPath
25from typing import Any, Protocol
27# Determine if the path separator for the OS looks like POSIX
28IS_POSIX = os.sep == posixpath.sep
30# Root path for this operating system. This can use getcwd which
31# can fail in some situations so in the default case assume that
32# posix means posix and only determine explicitly in the non-posix case.
33OS_ROOT_PATH = posixpath.sep if IS_POSIX else Path().resolve().root
35log = logging.getLogger(__name__)
38def os2posix(ospath: str) -> str:
39 """Convert a local path description to a POSIX path description.
41 Parameters
42 ----------
43 ospath : `str`
44 Path using the local path separator.
46 Returns
47 -------
48 posix : `str`
49 Path using POSIX path separator
50 """
51 if IS_POSIX:
52 return ospath
54 posix = PurePath(ospath).as_posix()
56 # PurePath strips trailing "/" from paths such that you can no
57 # longer tell if a path is meant to be referring to a directory
58 # Try to fix this.
59 if ospath.endswith(os.sep) and not posix.endswith(posixpath.sep):
60 posix += posixpath.sep
62 return posix
65def posix2os(posix: PurePath | str) -> str:
66 """Convert a POSIX path description to a local path description.
68 Parameters
69 ----------
70 posix : `str`, `~pathlib.PurePath`
71 Path using the POSIX path separator.
73 Returns
74 -------
75 ospath : `str`
76 Path using OS path separator
77 """
78 if IS_POSIX:
79 return str(posix)
81 posixPath = PurePosixPath(posix)
82 paths = list(posixPath.parts)
84 # Have to convert the root directory after splitting
85 if paths[0] == posixPath.root:
86 paths[0] = OS_ROOT_PATH
88 # Trailing "/" is stripped so we need to add back an empty path
89 # for consistency
90 if str(posix).endswith(posixpath.sep):
91 paths.append("")
93 return os.path.join(*paths)
96class NoTransaction:
97 """A simple emulation of the
98 `~lsst.daf.butler.core.datastore.DatastoreTransaction` class.
100 Notes
101 -----
102 Does nothing. Used as a fallback in the absence of an explicit transaction
103 class.
104 """
106 def __init__(self) -> None:
107 return
109 @contextlib.contextmanager
110 def undoWith(self, name: str, undoFunc: Callable, *args: Any, **kwargs: Any) -> Iterator[None]:
111 """No-op context manager to replace
112 `~lsst.daf.butler.core.datastore.DatastoreTransaction`.
113 """
114 yield None
117class TransactionProtocol(Protocol):
118 """Protocol for type checking transaction interface."""
120 @contextlib.contextmanager
121 def undoWith(self, name: str, undoFunc: Callable, *args: Any, **kwargs: Any) -> Iterator[None]:
122 ...
125def makeTestTempDir(default_base: str | None = None) -> str:
126 """Create a temporary directory for test usage.
128 The directory will be created within ``LSST_RESOURCES_TEST_TMP`` if that
129 environment variable is set, falling back to ``LSST_RESOURCES_TMPDIR``
130 amd then ``default_base`` if none are set.
132 Parameters
133 ----------
134 default_base : `str`, optional
135 Default parent directory. Will use system default if no environment
136 variables are set and base is set to `None`.
138 Returns
139 -------
140 dir : `str`
141 Name of the new temporary directory.
142 """
143 base = default_base
144 for envvar in ("LSST_RESOURCES_TEST_TMP", "LSST_RESOURCES_TMPDIR"):
145 if envvar in os.environ and os.environ[envvar]:
146 base = os.environ[envvar]
147 break
148 return tempfile.mkdtemp(dir=base)
151def removeTestTempDir(root: str | None) -> None:
152 """Attempt to remove a temporary test directory, but do not raise if
153 unable to.
155 Unlike `tempfile.TemporaryDirectory`, this passes ``ignore_errors=True``
156 to ``shutil.rmtree`` at close, making it safe to use on NFS.
158 Parameters
159 ----------
160 root : `str`, optional
161 Name of the directory to be removed. If `None`, nothing will be done.
162 """
163 if root is not None and os.path.exists(root):
164 shutil.rmtree(root, ignore_errors=True)
167def ensure_directory_is_writeable(directory_path: str | bytes) -> None:
168 """Given the path to a directory, ensures that we are able to write it and
169 access files in it. Alters the directory permissions by adding the
170 owner-write and owner-traverse permission bits if they aren't already set
172 Parameters
173 ----------
174 directory_path : `str` or `bytes`
175 Path to the directory that will be made writeable
176 """
177 current_mode = os.stat(directory_path).st_mode
178 desired_mode = current_mode | stat.S_IWUSR | stat.S_IXUSR
179 if current_mode != desired_mode:
180 os.chmod(directory_path, desired_mode)