Coverage for python/lsst/daf/butler/utils.py: 41%
37 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-27 09:44 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-27 09:44 +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
29__all__ = (
30 "stripIfNotNone",
31 "transactional",
32)
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
43from lsst.utils.iteration import ensure_iterable
45_LOG = logging.getLogger(__name__)
48F = TypeVar("F", bound=Callable)
51def transactional(func: F) -> F:
52 """Decorate a method and makes it transactional.
54 This depends on the class also defining a `transaction` method
55 that takes no arguments and acts as a context manager.
56 """
58 @functools.wraps(func)
59 def inner(self: Any, *args: Any, **kwargs: Any) -> Any:
60 with self.transaction():
61 return func(self, *args, **kwargs)
63 return inner # type: ignore
66def stripIfNotNone(s: str | None) -> str | None:
67 """Strip leading and trailing whitespace if the given object is not None.
69 Parameters
70 ----------
71 s : `str`, optional
72 Input string.
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
85def globToRegex(expressions: str | EllipsisType | None | list[str]) -> list[str | Pattern] | EllipsisType:
86 """Translate glob-style search terms to regex.
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.
92 Parameters
93 ----------
94 expressions : `str` or `list` [`str`]
95 A list of glob-style pattern strings to convert.
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 ...
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"[\*\?]|\[.*\]|\[!.*\]")
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