Coverage for python/lsst/daf/butler/core/utils.py: 36%
35 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-06-06 09:38 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-06-06 09:38 +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 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 types import EllipsisType
33from typing import Any, Callable, List, Optional, Pattern, TypeVar, Union
35from lsst.utils.iteration import ensure_iterable
37_LOG = logging.getLogger(__name__)
40F = TypeVar("F", bound=Callable)
43def transactional(func: F) -> F:
44 """Decorate a method and makes it transactional.
46 This depends on the class also defining a `transaction` method
47 that takes no arguments and acts as a context manager.
48 """
50 @functools.wraps(func)
51 def inner(self: Any, *args: Any, **kwargs: Any) -> Any:
52 with self.transaction():
53 return func(self, *args, **kwargs)
55 return inner # type: ignore
58def stripIfNotNone(s: Optional[str]) -> Optional[str]:
59 """Strip leading and trailing whitespace if the given object is not None.
61 Parameters
62 ----------
63 s : `str`, optional
64 Input string.
66 Returns
67 -------
68 r : `str` or `None`
69 A string with leading and trailing whitespace stripped if `s` is not
70 `None`, or `None` if `s` is `None`.
71 """
72 if s is not None:
73 s = s.strip()
74 return s
77def globToRegex(
78 expressions: Union[str, EllipsisType, None, List[str]]
79) -> Union[List[Union[str, Pattern]], EllipsisType]:
80 """Translate glob-style search terms to regex.
82 If a stand-alone '``*``' is found in ``expressions``, or expressions is
83 empty or `None`, then the special value ``...`` will be returned,
84 indicating that any string will match.
86 Parameters
87 ----------
88 expressions : `str` or `list` [`str`]
89 A list of glob-style pattern strings to convert.
91 Returns
92 -------
93 expressions : `list` [`str` or `re.Pattern`] or ``...``
94 A list of regex Patterns or simple strings. Returns ``...`` if
95 the provided expressions would match everything.
96 """
97 if expressions is ... or expressions is None:
98 return ...
99 expressions = list(ensure_iterable(expressions))
100 if not expressions or "*" in expressions:
101 return ...
103 # List of special glob characters supported by fnmatch.
104 # See: https://docs.python.org/3/library/fnmatch.html
105 # The complication is that "[" on its own is not a glob
106 # unless there is a match "]".
107 magic = re.compile(r"[\*\?]|\[.*\]|\[!.*\]")
109 # Try not to convert simple string to a regex.
110 results: List[Union[str, Pattern]] = []
111 for e in expressions:
112 res: Union[str, Pattern]
113 if magic.search(e):
114 res = re.compile(fnmatch.translate(e))
115 else:
116 res = e
117 results.append(res)
118 return results