Coverage for python/lsst/resources/utils.py: 38%
53 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-10 09:42 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-10 09:42 +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 tempfile
22from collections.abc import Callable, Iterator
23from pathlib import Path, PurePath, PurePosixPath
24from typing import Any, Protocol
26# Determine if the path separator for the OS looks like POSIX
27IS_POSIX = os.sep == posixpath.sep
29# Root path for this operating system. This can use getcwd which
30# can fail in some situations so in the default case assume that
31# posix means posix and only determine explicitly in the non-posix case.
32if IS_POSIX: 32 ↛ 35line 32 didn't jump to line 35, because the condition on line 32 was never false
33 OS_ROOT_PATH = posixpath.sep
34else:
35 OS_ROOT_PATH = Path().resolve().root
37log = logging.getLogger(__name__)
40def os2posix(ospath: str) -> str:
41 """Convert a local path description to a POSIX path description.
43 Parameters
44 ----------
45 ospath : `str`
46 Path using the local path separator.
48 Returns
49 -------
50 posix : `str`
51 Path using POSIX path separator
52 """
53 if IS_POSIX:
54 return ospath
56 posix = PurePath(ospath).as_posix()
58 # PurePath strips trailing "/" from paths such that you can no
59 # longer tell if a path is meant to be referring to a directory
60 # Try to fix this.
61 if ospath.endswith(os.sep) and not posix.endswith(posixpath.sep):
62 posix += posixpath.sep
64 return posix
67def posix2os(posix: PurePath | str) -> str:
68 """Convert a POSIX path description to a local path description.
70 Parameters
71 ----------
72 posix : `str`, `~pathlib.PurePath`
73 Path using the POSIX path separator.
75 Returns
76 -------
77 ospath : `str`
78 Path using OS path separator
79 """
80 if IS_POSIX:
81 return str(posix)
83 posixPath = PurePosixPath(posix)
84 paths = list(posixPath.parts)
86 # Have to convert the root directory after splitting
87 if paths[0] == posixPath.root:
88 paths[0] = OS_ROOT_PATH
90 # Trailing "/" is stripped so we need to add back an empty path
91 # for consistency
92 if str(posix).endswith(posixpath.sep):
93 paths.append("")
95 return os.path.join(*paths)
98class NoTransaction:
99 """A simple emulation of the
100 `~lsst.daf.butler.core.datastore.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:
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
114 `~lsst.daf.butler.core.datastore.DatastoreTransaction`.
115 """
116 yield None
119class TransactionProtocol(Protocol):
120 """Protocol for type checking transaction interface."""
122 @contextlib.contextmanager
123 def undoWith(self, name: str, undoFunc: Callable, *args: Any, **kwargs: Any) -> Iterator[None]:
124 ...
127def makeTestTempDir(default_base: str | None = None) -> str:
128 """Create a temporary directory for test usage.
130 The directory will be created within ``LSST_RESOURCES_TEST_TMP`` if that
131 environment variable is set, falling back to ``LSST_RESOURCES_TMPDIR``
132 amd then ``default_base`` if none are set.
134 Parameters
135 ----------
136 default_base : `str`, optional
137 Default parent directory. Will use system default if no environment
138 variables are set and base is set to `None`.
140 Returns
141 -------
142 dir : `str`
143 Name of the new temporary directory.
144 """
145 base = default_base
146 for envvar in ("LSST_RESOURCES_TEST_TMP", "LSST_RESOURCES_TMPDIR"):
147 if envvar in os.environ and os.environ[envvar]:
148 base = os.environ[envvar]
149 break
150 return tempfile.mkdtemp(dir=base)
153def removeTestTempDir(root: str | None) -> None:
154 """Attempt to remove a temporary test directory, but do not raise if
155 unable to.
157 Unlike `tempfile.TemporaryDirectory`, this passes ``ignore_errors=True``
158 to ``shutil.rmtree`` at close, making it safe to use on NFS.
160 Parameters
161 ----------
162 root : `str`, optional
163 Name of the directory to be removed. If `None`, nothing will be done.
164 """
165 if root is not None and os.path.exists(root):
166 shutil.rmtree(root, ignore_errors=True)