Coverage for python/lsst/daf/butler/utils.py: 44%
40 statements
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-16 02:58 -0700
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-16 02:58 -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 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.
57 Parameters
58 ----------
59 func : `~collections.abc.Callable`
60 Method to decorate.
62 Returns
63 -------
64 decorated : `~collections.abc.Callable`
65 The decorated method.
66 """
68 @functools.wraps(func)
69 def inner(self: Any, *args: Any, **kwargs: Any) -> Any:
70 with self.transaction():
71 return func(self, *args, **kwargs)
73 return inner # type: ignore
76def stripIfNotNone(s: str | None) -> str | None:
77 """Strip leading and trailing whitespace if the given object is not None.
79 Parameters
80 ----------
81 s : `str`, optional
82 Input string.
84 Returns
85 -------
86 r : `str` or `None`
87 A string with leading and trailing whitespace stripped if `s` is not
88 `None`, or `None` if `s` is `None`.
89 """
90 if s is not None:
91 s = s.strip()
92 return s
95def globToRegex(expressions: str | EllipsisType | None | list[str]) -> list[str | Pattern] | EllipsisType:
96 """Translate glob-style search terms to regex.
98 If a stand-alone '``*``' is found in ``expressions``, or expressions is
99 empty or `None`, then the special value ``...`` will be returned,
100 indicating that any string will match.
102 Parameters
103 ----------
104 expressions : `str` or `list` [`str`]
105 A list of glob-style pattern strings to convert.
107 Returns
108 -------
109 expressions : `list` [`str` or `re.Pattern`] or ``...``
110 A list of regex Patterns or simple strings. Returns ``...`` if
111 the provided expressions would match everything.
112 """
113 if expressions is ... or expressions is None:
114 return ...
115 expressions = list(ensure_iterable(expressions))
116 if not expressions or "*" in expressions:
117 return ...
119 # List of special glob characters supported by fnmatch.
120 # See: https://docs.python.org/3/library/fnmatch.html
121 # The complication is that "[" on its own is not a glob
122 # unless there is a match "]".
123 magic = re.compile(r"[\*\?]|\[.*\]|\[!.*\]")
125 # Try not to convert simple string to a regex.
126 results: list[str | Pattern] = []
127 for e in expressions:
128 res: str | Pattern
129 if magic.search(e):
130 res = re.compile(fnmatch.translate(e))
131 else:
132 res = e
133 results.append(res)
134 return results
137class _Marker:
138 """Private class to use as a default value when you want to know that
139 a default parameter has been over-ridden.
140 """
143_DefaultMarker = _Marker()
144"""Default value to give to a parameter when you want to know if the value
145has been over-ridden.
146"""