21 """Classes that transform (part of) a Gen2 filename template into a regular 22 expression that we can use to extract Gen2 data IDs from files. 24 from __future__
import annotations
26 __all__ = [
"PathElementParser"]
29 from abc
import ABC, abstractmethod
31 from typing
import ClassVar, Dict, Optional
33 from lsst.log
import Log
37 """An interface that generates a regular expression from a template and 40 This is used by `PathElementParser` to abstract over whether a path 41 element's regex needs to include values from a data ID extracted from 42 parent path elements or not. 46 def format(self, dataId: dict) -> re.Pattern:
47 """Substitute values from the given data ID and return a regular 53 A dictionary whose entries may be used to format the regular 54 expression. May include unused entries. 56 raise NotImplementedError()
60 """A trivial implementation of `FormattableRegEx` that does no formatting. 65 The fixed regular expression to return. 70 __slots__ = (
"regex",)
72 def format(self, dataId: dict) -> re.Pattern:
78 """An implementation of `FormattableRegEx` formed from a concatenation of 79 actual regular terms and %-style format strings. 84 __slots__ = (
"_terms",)
87 """Add a regular expression term. 89 self.
_terms.append((regex,
False))
92 """Add a %-style format template term. 94 self.
_terms.append((template,
True))
96 def format(self, dataId: dict) -> re.Pattern:
98 return re.compile(
"".join(re.escape(s % dataId)
if isSub
else s
99 for s, isSub
in self.
_terms))
102 """Return a possibly-simplified version of this object. 104 If `addSubstitionTerm` was never called, this returns a simple 107 if not any(isSub
for _, isSub
in self.
_terms):
114 """An object that matches Gen2 file names and extracts Gen2 data IDs. 119 Either a full Gen2 path template or the part of one the corresponds to 120 a single path element (a subdirectory or file name). 121 allKeys : `dict` [`str`, `type`] 122 A dictionary that provides types for all Gen2 data ID keys that are 123 substituted into the given template. Additional key-value pairs may 124 be present and will be ignored. 125 previousKeys : `dict` [`str`, `type`], optional 126 A dictionary containing key strings and types for Gen2 data ID keys 127 that have been extracted from previous path elements of the same 128 template. Values for these keys must be provided via the 129 ``lastDataId`` argument when calling `parse`. 131 def __init__(self, template: str, allKeys: Dict[str, type], *,
132 previousKeys: Optional[Dict[str, type]] =
None):
139 for match
in self.TEMPLATE_RE.finditer(self.
template):
142 self.
regex.addRegexTerm(re.escape(self.
template[last:match.start()]))
146 name = match.group(
"name")
149 elif match.group(
"type")
in "id":
155 if name
not in self.
keys:
156 if previousKeys
and name
in previousKeys:
161 start, stop = match.span()
165 self.
regex.addRegexTerm(
r"(?P<%s>%s)" % (name, pattern))
166 self.
keys[name] = allKeys[name]
170 self.
regex.addRegexTerm(
r"(?P=<%s>)" % name)
179 __slots__ = (
"keys",
"template",
"regex")
181 TEMPLATE_RE: ClassVar[re.Pattern] = re.compile(
r"\%\((?P<name>\w+)\)[^\%]*?(?P<type>[idrs])")
182 """Regular expression that matches a single substitution in 183 Gen2 CameraMapper template, such as "%(tract)04d". 186 def parse(self, name: str, lastDataId: dict, *, log: Optional[Log] =
None) -> Optional[dict]:
187 """Parse the path element. 192 The path name to parse. 194 The cumulative Gen2 data ID obtaining by calling `parse` on parsers 195 for parent directories of the same path. 196 log : `Log`, optional 197 Log to use to report warnings and debug information. 201 dataId : `dict` or `None` 202 Gen2 data ID that combines key-value pairs obtained from this path 203 with those from ``lastDataId``. `None` if ``name`` is not matched 204 by this parser. If the keys extracted are inconsistent with those 205 in ``lastDataID``, a warning is sent to ``log`` and `None` is 208 m = self.
regex.format(lastDataId).fullmatch(name)
211 newDataId = {k: v(m.group(k))
for k, v
in self.
keys.items()}
212 for commonKey
in lastDataId.keys() & newDataId.keys():
213 if newDataId[commonKey] != lastDataId[commonKey]:
215 log.warn(
"Inconsistent value %s=%r when parsing %r with %r.",
216 commonKey, newDataId[commonKey], name, lastDataId)
218 newDataId.update(lastDataId)
221 keys: Dict[str, type]
222 """Dictionary mapping Gen2 data ID key to the type of its associated 223 value, covering only those keys that can be extracted from this path 228 """The portion of the original Gen2 filename template that this parser was 233 """A regular expression that can be used to match the path element and 234 populate the Gen2 data ID items whose keys are in ``keys``.