Coverage for python/lsst/daf/butler/core/utils.py: 39%
37 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-12 10:56 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-12 10:56 -0700
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 collections.abc import Callable
33from re import Pattern
34from types import EllipsisType
35from typing import Any, TypeVar
37from lsst.utils.iteration import ensure_iterable
39_LOG = logging.getLogger(__name__)
42F = TypeVar("F", bound=Callable)
45def transactional(func: F) -> F:
46 """Decorate a method and makes it transactional.
48 This depends on the class also defining a `transaction` method
49 that takes no arguments and acts as a context manager.
50 """
52 @functools.wraps(func)
53 def inner(self: Any, *args: Any, **kwargs: Any) -> Any:
54 with self.transaction():
55 return func(self, *args, **kwargs)
57 return inner # type: ignore
60def stripIfNotNone(s: str | None) -> str | None:
61 """Strip leading and trailing whitespace if the given object is not None.
63 Parameters
64 ----------
65 s : `str`, optional
66 Input string.
68 Returns
69 -------
70 r : `str` or `None`
71 A string with leading and trailing whitespace stripped if `s` is not
72 `None`, or `None` if `s` is `None`.
73 """
74 if s is not None:
75 s = s.strip()
76 return s
79def globToRegex(expressions: str | EllipsisType | None | list[str]) -> list[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[str | Pattern] = []
111 for e in expressions:
112 res: 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