Coverage for python/lsst/resources/utils.py: 41%
51 statements
« prev ^ index » next coverage.py v7.3.0, created at 2023-08-31 09:33 +0000
« prev ^ index » next coverage.py v7.3.0, created at 2023-08-31 09:33 +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.
32OS_ROOT_PATH = posixpath.sep if IS_POSIX else Path().resolve().root
34log = logging.getLogger(__name__)
37def os2posix(ospath: str) -> str:
38 """Convert a local path description to a POSIX path description.
40 Parameters
41 ----------
42 ospath : `str`
43 Path using the local path separator.
45 Returns
46 -------
47 posix : `str`
48 Path using POSIX path separator
49 """
50 if IS_POSIX:
51 return ospath
53 posix = PurePath(ospath).as_posix()
55 # PurePath strips trailing "/" from paths such that you can no
56 # longer tell if a path is meant to be referring to a directory
57 # Try to fix this.
58 if ospath.endswith(os.sep) and not posix.endswith(posixpath.sep):
59 posix += posixpath.sep
61 return posix
64def posix2os(posix: PurePath | str) -> str:
65 """Convert a POSIX path description to a local path description.
67 Parameters
68 ----------
69 posix : `str`, `~pathlib.PurePath`
70 Path using the POSIX path separator.
72 Returns
73 -------
74 ospath : `str`
75 Path using OS path separator
76 """
77 if IS_POSIX:
78 return str(posix)
80 posixPath = PurePosixPath(posix)
81 paths = list(posixPath.parts)
83 # Have to convert the root directory after splitting
84 if paths[0] == posixPath.root:
85 paths[0] = OS_ROOT_PATH
87 # Trailing "/" is stripped so we need to add back an empty path
88 # for consistency
89 if str(posix).endswith(posixpath.sep):
90 paths.append("")
92 return os.path.join(*paths)
95class NoTransaction:
96 """A simple emulation of the
97 `~lsst.daf.butler.core.datastore.DatastoreTransaction` class.
99 Notes
100 -----
101 Does nothing. Used as a fallback in the absence of an explicit transaction
102 class.
103 """
105 def __init__(self) -> None:
106 return
108 @contextlib.contextmanager
109 def undoWith(self, name: str, undoFunc: Callable, *args: Any, **kwargs: Any) -> Iterator[None]:
110 """No-op context manager to replace
111 `~lsst.daf.butler.core.datastore.DatastoreTransaction`.
112 """
113 yield None
116class TransactionProtocol(Protocol):
117 """Protocol for type checking transaction interface."""
119 @contextlib.contextmanager
120 def undoWith(self, name: str, undoFunc: Callable, *args: Any, **kwargs: Any) -> Iterator[None]:
121 ...
124def makeTestTempDir(default_base: str | None = None) -> str:
125 """Create a temporary directory for test usage.
127 The directory will be created within ``LSST_RESOURCES_TEST_TMP`` if that
128 environment variable is set, falling back to ``LSST_RESOURCES_TMPDIR``
129 amd then ``default_base`` if none are set.
131 Parameters
132 ----------
133 default_base : `str`, optional
134 Default parent directory. Will use system default if no environment
135 variables are set and base is set to `None`.
137 Returns
138 -------
139 dir : `str`
140 Name of the new temporary directory.
141 """
142 base = default_base
143 for envvar in ("LSST_RESOURCES_TEST_TMP", "LSST_RESOURCES_TMPDIR"):
144 if envvar in os.environ and os.environ[envvar]:
145 base = os.environ[envvar]
146 break
147 return tempfile.mkdtemp(dir=base)
150def removeTestTempDir(root: str | None) -> None:
151 """Attempt to remove a temporary test directory, but do not raise if
152 unable to.
154 Unlike `tempfile.TemporaryDirectory`, this passes ``ignore_errors=True``
155 to ``shutil.rmtree`` at close, making it safe to use on NFS.
157 Parameters
158 ----------
159 root : `str`, optional
160 Name of the directory to be removed. If `None`, nothing will be done.
161 """
162 if root is not None and os.path.exists(root):
163 shutil.rmtree(root, ignore_errors=True)