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

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. 

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 

36 

37log = logging.getLogger(__name__) 

38 

39 

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

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

42 

43 Parameters 

44 ---------- 

45 ospath : `str` 

46 Path using the local path separator. 

47 

48 Returns 

49 ------- 

50 posix : `str` 

51 Path using POSIX path separator 

52 """ 

53 if IS_POSIX: 

54 return ospath 

55 

56 posix = PurePath(ospath).as_posix() 

57 

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 

63 

64 return posix 

65 

66 

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

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

69 

70 Parameters 

71 ---------- 

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

73 Path using the POSIX path separator. 

74 

75 Returns 

76 ------- 

77 ospath : `str` 

78 Path using OS path separator 

79 """ 

80 if IS_POSIX: 

81 return str(posix) 

82 

83 posixPath = PurePosixPath(posix) 

84 paths = list(posixPath.parts) 

85 

86 # Have to convert the root directory after splitting 

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

88 paths[0] = OS_ROOT_PATH 

89 

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

94 

95 return os.path.join(*paths) 

96 

97 

98class NoTransaction: 

99 """A simple emulation of the 

100 `~lsst.daf.butler.core.datastore.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: 

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 

114 `~lsst.daf.butler.core.datastore.DatastoreTransaction`. 

115 """ 

116 yield None 

117 

118 

119class TransactionProtocol(Protocol): 

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

121 

122 @contextlib.contextmanager 

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

124 ... 

125 

126 

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

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

129 

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. 

133 

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

139 

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) 

151 

152 

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

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

155 unable to. 

156 

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

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

159 

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)