Coverage for python/lsst/daf/butler/utils.py: 41%

37 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-06 10:53 +0000

1# This file is part of daf_butler. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <http://www.gnu.org/licenses/>. 

27from __future__ import annotations 

28 

29__all__ = ( 

30 "stripIfNotNone", 

31 "transactional", 

32) 

33 

34import fnmatch 

35import functools 

36import logging 

37import re 

38from collections.abc import Callable 

39from re import Pattern 

40from types import EllipsisType 

41from typing import Any, TypeVar 

42 

43from lsst.utils.iteration import ensure_iterable 

44 

45_LOG = logging.getLogger(__name__) 

46 

47 

48F = TypeVar("F", bound=Callable) 

49 

50 

51def transactional(func: F) -> F: 

52 """Decorate a method and makes it transactional. 

53 

54 This depends on the class also defining a `transaction` method 

55 that takes no arguments and acts as a context manager. 

56 """ 

57 

58 @functools.wraps(func) 

59 def inner(self: Any, *args: Any, **kwargs: Any) -> Any: 

60 with self.transaction(): 

61 return func(self, *args, **kwargs) 

62 

63 return inner # type: ignore 

64 

65 

66def stripIfNotNone(s: str | None) -> str | None: 

67 """Strip leading and trailing whitespace if the given object is not None. 

68 

69 Parameters 

70 ---------- 

71 s : `str`, optional 

72 Input string. 

73 

74 Returns 

75 ------- 

76 r : `str` or `None` 

77 A string with leading and trailing whitespace stripped if `s` is not 

78 `None`, or `None` if `s` is `None`. 

79 """ 

80 if s is not None: 

81 s = s.strip() 

82 return s 

83 

84 

85def globToRegex(expressions: str | EllipsisType | None | list[str]) -> list[str | Pattern] | EllipsisType: 

86 """Translate glob-style search terms to regex. 

87 

88 If a stand-alone '``*``' is found in ``expressions``, or expressions is 

89 empty or `None`, then the special value ``...`` will be returned, 

90 indicating that any string will match. 

91 

92 Parameters 

93 ---------- 

94 expressions : `str` or `list` [`str`] 

95 A list of glob-style pattern strings to convert. 

96 

97 Returns 

98 ------- 

99 expressions : `list` [`str` or `re.Pattern`] or ``...`` 

100 A list of regex Patterns or simple strings. Returns ``...`` if 

101 the provided expressions would match everything. 

102 """ 

103 if expressions is ... or expressions is None: 

104 return ... 

105 expressions = list(ensure_iterable(expressions)) 

106 if not expressions or "*" in expressions: 

107 return ... 

108 

109 # List of special glob characters supported by fnmatch. 

110 # See: https://docs.python.org/3/library/fnmatch.html 

111 # The complication is that "[" on its own is not a glob 

112 # unless there is a match "]". 

113 magic = re.compile(r"[\*\?]|\[.*\]|\[!.*\]") 

114 

115 # Try not to convert simple string to a regex. 

116 results: list[str | Pattern] = [] 

117 for e in expressions: 

118 res: str | Pattern 

119 if magic.search(e): 

120 res = re.compile(fnmatch.translate(e)) 

121 else: 

122 res = e 

123 results.append(res) 

124 return results