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

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. 

11 

12from __future__ import annotations 

13 

14__all__ = ("os2posix", "posix2os", "NoTransaction", "TransactionProtocol") 

15 

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 

25 

26# Determine if the path separator for the OS looks like POSIX 

27IS_POSIX = os.sep == posixpath.sep 

28 

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 

33 

34log = logging.getLogger(__name__) 

35 

36 

37def os2posix(ospath: str) -> str: 

38 """Convert a local path description to a POSIX path description. 

39 

40 Parameters 

41 ---------- 

42 ospath : `str` 

43 Path using the local path separator. 

44 

45 Returns 

46 ------- 

47 posix : `str` 

48 Path using POSIX path separator 

49 """ 

50 if IS_POSIX: 

51 return ospath 

52 

53 posix = PurePath(ospath).as_posix() 

54 

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 

60 

61 return posix 

62 

63 

64def posix2os(posix: PurePath | str) -> str: 

65 """Convert a POSIX path description to a local path description. 

66 

67 Parameters 

68 ---------- 

69 posix : `str`, `~pathlib.PurePath` 

70 Path using the POSIX path separator. 

71 

72 Returns 

73 ------- 

74 ospath : `str` 

75 Path using OS path separator 

76 """ 

77 if IS_POSIX: 

78 return str(posix) 

79 

80 posixPath = PurePosixPath(posix) 

81 paths = list(posixPath.parts) 

82 

83 # Have to convert the root directory after splitting 

84 if paths[0] == posixPath.root: 

85 paths[0] = OS_ROOT_PATH 

86 

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("") 

91 

92 return os.path.join(*paths) 

93 

94 

95class NoTransaction: 

96 """A simple emulation of the 

97 `~lsst.daf.butler.core.datastore.DatastoreTransaction` class. 

98 

99 Notes 

100 ----- 

101 Does nothing. Used as a fallback in the absence of an explicit transaction 

102 class. 

103 """ 

104 

105 def __init__(self) -> None: 

106 return 

107 

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 

114 

115 

116class TransactionProtocol(Protocol): 

117 """Protocol for type checking transaction interface.""" 

118 

119 @contextlib.contextmanager 

120 def undoWith(self, name: str, undoFunc: Callable, *args: Any, **kwargs: Any) -> Iterator[None]: 

121 ... 

122 

123 

124def makeTestTempDir(default_base: str | None = None) -> str: 

125 """Create a temporary directory for test usage. 

126 

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. 

130 

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`. 

136 

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) 

148 

149 

150def removeTestTempDir(root: str | None) -> None: 

151 """Attempt to remove a temporary test directory, but do not raise if 

152 unable to. 

153 

154 Unlike `tempfile.TemporaryDirectory`, this passes ``ignore_errors=True`` 

155 to ``shutil.rmtree`` at close, making it safe to use on NFS. 

156 

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)