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
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:
77 return f
"{type(self).__name__}({self.regex})"
81 """An implementation of `FormattableRegEx` formed from a concatenation of
82 actual regular terms and %-style format strings.
87 __slots__ = (
"_terms",)
90 """Add a regular expression term.
95 """Add a %-style format template term.
99 def format(self, dataId: dict) -> re.Pattern:
101 return re.compile(
"".join(re.escape(s % dataId)
if isSub
else s
102 for s, isSub
in self.
_terms))
105 """Return a possibly-simplified version of this object.
107 If `addSubstitionTerm` was never called, this returns a simple
110 if not any(isSub
for _, isSub
in self.
_terms):
117 """An object that matches Gen2 file names and extracts Gen2 data IDs.
122 Either a full Gen2 path template or the part of one the corresponds to
123 a single path element (a subdirectory or file name).
124 allKeys : `dict` [`str`, `type`]
125 A dictionary that provides types for all Gen2 data ID keys that are
126 substituted into the given template. Additional key-value pairs may
127 be present and will be ignored.
128 previousKeys : `dict` [`str`, `type`], optional
129 A dictionary containing key strings and types for Gen2 data ID keys
130 that have been extracted from previous path elements of the same
131 template. Values for these keys must be provided via the
132 ``lastDataId`` argument when calling `parse`.
134 def __init__(self, template: str, allKeys: Dict[str, type], *,
135 previousKeys: Optional[Dict[str, type]] =
None):
142 for match
in self.TEMPLATE_RE.finditer(self.
template):
145 self.
regex.addRegexTerm(re.escape(self.
template[last:match.start()]))
149 name = match.group(
"name")
152 elif match.group(
"type")
in "id":
158 if name
not in self.
keys:
159 if previousKeys
and name
in previousKeys:
164 start, stop = match.span()
168 self.
regex.addRegexTerm(
r"(?P<%s>%s)" % (name, pattern))
169 self.
keys[name] = allKeys[name]
173 self.
regex.addRegexTerm(
r"(?P=<%s>)" % name)
182 __slots__ = (
"keys",
"template",
"regex")
184 TEMPLATE_RE: ClassVar[re.Pattern] = re.compile(
r"\%\((?P<name>\w+)\)[^\%]*?(?P<type>[idrs])")
185 """Regular expression that matches a single substitution in
186 Gen2 CameraMapper template, such as "%(tract)04d".
190 return f
"{type(self).__name__}({self.regex})"
192 def parse(self, name: str, lastDataId: dict, *, log: Optional[Log] =
None) -> Optional[dict]:
193 """Parse the path element.
198 The path name to parse.
200 The cumulative Gen2 data ID obtaining by calling `parse` on parsers
201 for parent directories of the same path.
202 log : `Log`, optional
203 Log to use to report warnings and debug information.
207 dataId : `dict` or `None`
208 Gen2 data ID that combines key-value pairs obtained from this path
209 with those from ``lastDataId``. `None` if ``name`` is not matched
210 by this parser. If the keys extracted are inconsistent with those
211 in ``lastDataID``, a warning is sent to ``log`` and `None` is
217 newDataId = {k: v(m.group(k))
for k, v
in self.
keys.
items()}
218 for commonKey
in lastDataId.keys() & newDataId.keys():
219 if newDataId[commonKey] != lastDataId[commonKey]:
221 log.warn(
"Inconsistent value %s=%r when parsing %r with %r.",
222 commonKey, newDataId[commonKey], name, lastDataId)
224 newDataId.update(lastDataId)
227 keys: Dict[str, type]
228 """Dictionary mapping Gen2 data ID key to the type of its associated
229 value, covering only those keys that can be extracted from this path
234 """The portion of the original Gen2 filename template that this parser was
239 """A regular expression that can be used to match the path element and
240 populate the Gen2 data ID items whose keys are in ``keys``.