Coverage for python/lsst/daf/butler/core/utils.py: 36%
36 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-23 03:00 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-23 03:00 -0800
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 program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21from __future__ import annotations
23__all__ = (
24 "stripIfNotNone",
25 "transactional",
26)
28import fnmatch
29import functools
30import logging
31import re
32from typing import TYPE_CHECKING, Any, Callable, List, Optional, Pattern, TypeVar, Union
34from lsst.utils.iteration import ensure_iterable
36if TYPE_CHECKING: 36 ↛ 37line 36 didn't jump to line 37, because the condition on line 36 was never true
37 from lsst.utils.ellipsis import Ellipsis, EllipsisType
40_LOG = logging.getLogger(__name__)
43F = TypeVar("F", bound=Callable)
46def transactional(func: F) -> F:
47 """Decorate a method and makes it transactional.
49 This depends on the class also defining a `transaction` method
50 that takes no arguments and acts as a context manager.
51 """
53 @functools.wraps(func)
54 def inner(self: Any, *args: Any, **kwargs: Any) -> Any:
55 with self.transaction():
56 return func(self, *args, **kwargs)
58 return inner # type: ignore
61def stripIfNotNone(s: Optional[str]) -> Optional[str]:
62 """Strip leading and trailing whitespace if the given object is not None.
64 Parameters
65 ----------
66 s : `str`, optional
67 Input string.
69 Returns
70 -------
71 r : `str` or `None`
72 A string with leading and trailing whitespace stripped if `s` is not
73 `None`, or `None` if `s` is `None`.
74 """
75 if s is not None:
76 s = s.strip()
77 return s
80def globToRegex(
81 expressions: Union[str, EllipsisType, None, List[str]]
82) -> Union[List[Union[str, Pattern]], EllipsisType]:
83 """Translate glob-style search terms to regex.
85 If a stand-alone '``*``' is found in ``expressions``, or expressions is
86 empty or `None`, then the special value ``...`` will be returned,
87 indicating that any string will match.
89 Parameters
90 ----------
91 expressions : `str` or `list` [`str`]
92 A list of glob-style pattern strings to convert.
94 Returns
95 -------
96 expressions : `list` [`str` or `re.Pattern`] or ``...``
97 A list of regex Patterns or simple strings. Returns ``...`` if
98 the provided expressions would match everything.
99 """
100 if expressions is Ellipsis or expressions is None:
101 return Ellipsis
102 expressions = list(ensure_iterable(expressions))
103 if not expressions or "*" in expressions:
104 return Ellipsis
106 # List of special glob characters supported by fnmatch.
107 # See: https://docs.python.org/3/library/fnmatch.html
108 # The complication is that "[" on its own is not a glob
109 # unless there is a match "]".
110 magic = re.compile(r"[\*\?]|\[.*\]|\[!.*\]")
112 # Try not to convert simple string to a regex.
113 results: List[Union[str, Pattern]] = []
114 for e in expressions:
115 res: Union[str, Pattern]
116 if magic.search(e):
117 res = re.compile(fnmatch.translate(e))
118 else:
119 res = e
120 results.append(res)
121 return results