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

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 

14import contextlib 

15import logging 

16import os 

17import posixpath 

18import shutil 

19import tempfile 

20from pathlib import Path, PurePath, PurePosixPath 

21from typing import Optional 

22 

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

24 

25from typing import Any, Callable, Iterator, Protocol, Union 

26 

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

28IS_POSIX = os.sep == posixpath.sep 

29 

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 

37 

38log = logging.getLogger(__name__) 

39 

40 

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

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

43 

44 Parameters 

45 ---------- 

46 ospath : `str` 

47 Path using the local path separator. 

48 

49 Returns 

50 ------- 

51 posix : `str` 

52 Path using POSIX path separator 

53 """ 

54 if IS_POSIX: 

55 return ospath 

56 

57 posix = PurePath(ospath).as_posix() 

58 

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 

64 

65 return posix 

66 

67 

68def posix2os(posix: Union[PurePath, str]) -> str: 

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

70 

71 Parameters 

72 ---------- 

73 posix : `str`, `PurePath` 

74 Path using the POSIX path separator. 

75 

76 Returns 

77 ------- 

78 ospath : `str` 

79 Path using OS path separator 

80 """ 

81 if IS_POSIX: 

82 return str(posix) 

83 

84 posixPath = PurePosixPath(posix) 

85 paths = list(posixPath.parts) 

86 

87 # Have to convert the root directory after splitting 

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

89 paths[0] = OS_ROOT_PATH 

90 

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

95 

96 return os.path.join(*paths) 

97 

98 

99class NoTransaction: 

100 """A simple emulation of the `~lsst.daf.butler.DatastoreTransaction` class. 

101 

102 Notes 

103 ----- 

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

105 class. 

106 """ 

107 

108 def __init__(self) -> None: # noqa: D107 

109 return 

110 

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 

115 

116 

117class TransactionProtocol(Protocol): 

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

119 

120 @contextlib.contextmanager 

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

122 ... 

123 

124 

125def makeTestTempDir(default_base: Optional[str] = None) -> str: 

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

127 

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. 

131 

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

137 

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) 

149 

150 

151def removeTestTempDir(root: Optional[str]) -> None: 

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

153 unable to. 

154 

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

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

157 

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)