Coverage for python/lsst/resources/utils.py: 38%
53 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-06 01:38 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-06 01:38 -0800
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
14import contextlib
15import logging
16import os
17import posixpath
18import shutil
19import tempfile
20from pathlib import Path, PurePath, PurePosixPath
21from typing import Optional
23__all__ = ("os2posix", "posix2os", "NoTransaction", "TransactionProtocol")
25from typing import Any, Callable, Iterator, Protocol, Union
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.
33if IS_POSIX: 33 ↛ 36line 33 didn't jump to line 36, because the condition on line 33 was never false
34 OS_ROOT_PATH = posixpath.sep
35else:
36 OS_ROOT_PATH = Path().resolve().root
38log = logging.getLogger(__name__)
41def os2posix(ospath: str) -> str:
42 """Convert a local path description to a POSIX path description.
44 Parameters
45 ----------
46 ospath : `str`
47 Path using the local path separator.
49 Returns
50 -------
51 posix : `str`
52 Path using POSIX path separator
53 """
54 if IS_POSIX:
55 return ospath
57 posix = PurePath(ospath).as_posix()
59 # PurePath strips trailing "/" from paths such that you can no
60 # longer tell if a path is meant to be referring to a directory
61 # Try to fix this.
62 if ospath.endswith(os.sep) and not posix.endswith(posixpath.sep):
63 posix += posixpath.sep
65 return posix
68def posix2os(posix: Union[PurePath, str]) -> str:
69 """Convert a POSIX path description to a local path description.
71 Parameters
72 ----------
73 posix : `str`, `PurePath`
74 Path using the POSIX path separator.
76 Returns
77 -------
78 ospath : `str`
79 Path using OS path separator
80 """
81 if IS_POSIX:
82 return str(posix)
84 posixPath = PurePosixPath(posix)
85 paths = list(posixPath.parts)
87 # Have to convert the root directory after splitting
88 if paths[0] == posixPath.root:
89 paths[0] = OS_ROOT_PATH
91 # Trailing "/" is stripped so we need to add back an empty path
92 # for consistency
93 if str(posix).endswith(posixpath.sep):
94 paths.append("")
96 return os.path.join(*paths)
99class NoTransaction:
100 """A simple emulation of the `~lsst.daf.butler.DatastoreTransaction` class.
102 Notes
103 -----
104 Does nothing. Used as a fallback in the absence of an explicit transaction
105 class.
106 """
108 def __init__(self) -> None: # noqa: D107
109 return
111 @contextlib.contextmanager
112 def undoWith(self, name: str, undoFunc: Callable, *args: Any, **kwargs: Any) -> Iterator[None]:
113 """No-op context manager to replace `DatastoreTransaction`."""
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: Optional[str] = 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: Optional[str]) -> 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)