legend-dataflow-scripts 0.4.0a1__tar.gz → 0.4.0a2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {legend_dataflow_scripts-0.4.0a1/src/legend_dataflow_scripts.egg-info → legend_dataflow_scripts-0.4.0a2}/PKG-INFO +1 -1
  2. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2/src/legend_dataflow_scripts.egg-info}/PKG-INFO +1 -1
  3. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legend_dataflow_scripts.egg-info/scm_file_list.json +1 -0
  4. legend_dataflow_scripts-0.4.0a2/src/legend_dataflow_scripts.egg-info/scm_version.json +8 -0
  5. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/_version.py +3 -3
  6. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/utils/alias_table.py +37 -27
  7. legend_dataflow_scripts-0.4.0a2/src/legenddataflowscripts/workflow/pre_compile_catalog.py +113 -0
  8. legend_dataflow_scripts-0.4.0a1/src/legend_dataflow_scripts.egg-info/scm_version.json +0 -8
  9. legend_dataflow_scripts-0.4.0a1/src/legenddataflowscripts/workflow/pre_compile_catalog.py +0 -44
  10. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/LICENSE +0 -0
  11. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/README.md +0 -0
  12. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/pyproject.toml +0 -0
  13. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/setup.cfg +0 -0
  14. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legend_dataflow_scripts.egg-info/SOURCES.txt +0 -0
  15. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legend_dataflow_scripts.egg-info/dependency_links.txt +0 -0
  16. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legend_dataflow_scripts.egg-info/entry_points.txt +0 -0
  17. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legend_dataflow_scripts.egg-info/not-zip-safe +0 -0
  18. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legend_dataflow_scripts.egg-info/requires.txt +0 -0
  19. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legend_dataflow_scripts.egg-info/top_level.txt +0 -0
  20. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/__init__.py +0 -0
  21. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/__init__.py +0 -0
  22. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/__init__.py +0 -0
  23. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/dsp/__init__.py +0 -0
  24. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/dsp/dplms.py +0 -0
  25. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/dsp/eopt.py +0 -0
  26. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/dsp/evtsel.py +0 -0
  27. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/dsp/nopt.py +0 -0
  28. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/dsp/pz.py +0 -0
  29. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/dsp/svm.py +0 -0
  30. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/dsp/svm_build.py +0 -0
  31. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/hit/__init__.py +0 -0
  32. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/hit/aoe.py +0 -0
  33. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/hit/ecal.py +0 -0
  34. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/hit/lq.py +0 -0
  35. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/par/geds/hit/qc.py +0 -0
  36. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/tier/__init__.py +0 -0
  37. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/tier/dsp.py +0 -0
  38. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/tier/hit.py +0 -0
  39. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/utils/__init__.py +0 -0
  40. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/utils/cfgtools.py +0 -0
  41. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/utils/convert_np.py +0 -0
  42. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/utils/discharge.py +0 -0
  43. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/utils/files.py +0 -0
  44. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/utils/lgdo_utils.py +0 -0
  45. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/utils/log.py +0 -0
  46. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/utils/plot_dict.py +0 -0
  47. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/utils/pulser_removal.py +0 -0
  48. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/workflow/__init__.py +0 -0
  49. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/workflow/execenv.py +0 -0
  50. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/workflow/filedb.py +0 -0
  51. {legend_dataflow_scripts-0.4.0a1 → legend_dataflow_scripts-0.4.0a2}/src/legenddataflowscripts/workflow/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: legend-dataflow-scripts
3
- Version: 0.4.0a1
3
+ Version: 0.4.0a2
4
4
  Summary: Python package for the processing scripts for LEGEND-200 data
5
5
  Author-email: George Marshall <ggmarsh@uw.edu>, Luigi Pertoldi <gipert@pm.me>
6
6
  Maintainer: The LEGEND Collaboration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: legend-dataflow-scripts
3
- Version: 0.4.0a1
3
+ Version: 0.4.0a2
4
4
  Summary: Python package for the processing scripts for LEGEND-200 data
5
5
  Author-email: George Marshall <ggmarsh@uw.edu>, Luigi Pertoldi <gipert@pm.me>
6
6
  Maintainer: The LEGEND Collaboration
@@ -60,6 +60,7 @@
60
60
  "tests/test_execenv.py",
61
61
  "tests/test_lgdo_utils.py",
62
62
  "tests/test_peak_selection.py",
63
+ "tests/test_pre_compile_catalog.py",
63
64
  "tests/test_util.py",
64
65
  "tests/test_utils_helpers.py"
65
66
  ]
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.4.0a2",
3
+ "distance": 0,
4
+ "node": "g4e182698a9613efc0e62adc5eff80c46ec6449c7",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-06"
8
+ }
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.4.0a1'
22
- __version_tuple__ = version_tuple = (0, 4, 0, 'a1')
21
+ __version__ = version = '0.4.0a2'
22
+ __version_tuple__ = version_tuple = (0, 4, 0, 'a2')
23
23
 
24
- __commit_id__ = commit_id = 'g30429c604'
24
+ __commit_id__ = commit_id = 'g4e182698a'
@@ -7,7 +7,7 @@ import h5py
7
7
 
8
8
 
9
9
  def convert_parents_to_structs(h5group):
10
- """Recursively annotate HDF5 parent groups with LGDO ``struct`` datatype attributes.
10
+ """Annotate HDF5 parent groups with LGDO ``struct`` datatype attributes.
11
11
 
12
12
  When a new alias (hard link) is created inside an HDF5 file the parent
13
13
  groups may not carry the ``datatype`` attribute expected by the LGDO reader.
@@ -15,38 +15,48 @@ def convert_parents_to_structs(h5group):
15
15
  every ancestor group carries a ``datatype`` attribute of the form
16
16
  ``struct{child1,child2,…}``.
17
17
 
18
+ The root group is only updated if it already carries a ``datatype``; the
19
+ walk always stops there.
20
+
18
21
  Parameters
19
22
  ----------
20
23
  h5group : h5py.Group
21
24
  Leaf group whose parent hierarchy should be annotated.
22
25
  """
23
- child = h5group.name.split("/")[-1]
24
- if h5group.parent.name != "/" and len(h5group.parent.attrs) == 0:
25
- h5group.parent.attrs.update({"datatype": "struct{" + child + "}"})
26
- elif len(h5group.parent.attrs) > 0:
27
- datatype = h5group.parent.attrs["datatype"]
28
- if not (datatype.startswith("struct{") and datatype.endswith("}")):
29
- msg = (
30
- f"cannot register alias {child!r}: parent group "
31
- f"{h5group.parent.name!r} has non-struct datatype {datatype!r}"
26
+ # the walk must be iterative: in h5py the root group is its own parent, so
27
+ # recursing on ``h5group.parent`` never terminates once ``/`` is reached
28
+ while h5group.name != "/":
29
+ parent = h5group.parent
30
+ child = h5group.name.rsplit("/", 1)[-1]
31
+
32
+ if "datatype" not in parent.attrs:
33
+ # never invent a datatype on the file root
34
+ if parent.name == "/":
35
+ return
36
+ parent.attrs.update({"datatype": "struct{" + child + "}"})
37
+ else:
38
+ datatype = parent.attrs["datatype"]
39
+ if not (datatype.startswith("struct{") and datatype.endswith("}")):
40
+ msg = (
41
+ f"cannot register alias {child!r}: parent group "
42
+ f"{parent.name!r} has non-struct datatype {datatype!r}"
43
+ )
44
+ raise ValueError(msg)
45
+ # membership must be tested against the parsed field list, not by
46
+ # substring (e.g. 'raw' is a substring of 'raw_blind')
47
+ fields = [
48
+ field.strip()
49
+ for field in datatype[len("struct{") : -1].split(",")
50
+ if field.strip()
51
+ ]
52
+ if child in fields:
53
+ # ancestors were annotated when this member was first added
54
+ return
55
+ parent.attrs.update(
56
+ {"datatype": "struct{" + ",".join([*fields, child]) + "}"}
32
57
  )
33
- raise ValueError(msg)
34
- # membership must be tested against the parsed field list, not by
35
- # substring (e.g. 'raw' is a substring of 'raw_blind')
36
- fields = [
37
- field.strip()
38
- for field in datatype[len("struct{") : -1].split(",")
39
- if field.strip()
40
- ]
41
- if child in fields:
42
- return
43
- h5group.parent.attrs.update(
44
- {"datatype": "struct{" + ",".join([*fields, child]) + "}"}
45
- )
46
- else:
47
- return
48
- convert_parents_to_structs(h5group.parent)
49
- return
58
+
59
+ h5group = parent
50
60
 
51
61
 
52
62
  def alias_table(file: str | Path, mapping: str):
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import os
5
+ import pickle
6
+ import tempfile
7
+ from datetime import UTC, datetime
8
+ from importlib.metadata import version
9
+ from pathlib import Path
10
+
11
+ from dbetto import TextDB
12
+ from dbetto.catalog import Catalog
13
+
14
+
15
+ def _content_digest(validity_path: Path) -> str:
16
+ """Digest of the directory contents (relative path, mtime and size of
17
+ every file) plus the dbetto version, identifying one compiled state of
18
+ the catalog."""
19
+ state = [version("dbetto")]
20
+ for f in sorted(validity_path.rglob("*")):
21
+ if f.is_file():
22
+ st = f.stat()
23
+ state.append(
24
+ f"{f.relative_to(validity_path)}:{st.st_mtime_ns}:{st.st_size}"
25
+ )
26
+ return hashlib.sha256("\n".join(state).encode()).hexdigest()[:16]
27
+
28
+
29
+ def _cache_dir(validity_path: Path) -> Path:
30
+ """Per-directory cache namespace, keyed on the resolved directory path, so
31
+ same-named directories in different locations cannot evict each other's
32
+ pickles."""
33
+ base = os.environ.get("XDG_CACHE_HOME") or str(Path.home() / ".cache")
34
+ dir_key = hashlib.sha256(str(validity_path.resolve()).encode()).hexdigest()[:16]
35
+ return (
36
+ Path(base)
37
+ / "legend-dataflow"
38
+ / "precompiled-catalogs"
39
+ / f"{validity_path.name}-{dir_key}"
40
+ )
41
+
42
+
43
+ def pre_compile_catalog(validity_path: str | Path):
44
+ """Pre-compile a dbetto validity catalog for fast repeated access.
45
+
46
+ Reads the ``validity.yaml`` catalog from *validity_path* and, for each
47
+ system and each entry in the catalog, eagerly loads the corresponding
48
+ :class:`dbetto.TextDB` state (instead of loading it lazily on first
49
+ access). The resulting :class:`dbetto.catalog.Catalog` can be serialised
50
+ and reused across many Snakemake jobs without re-parsing YAML on every
51
+ invocation.
52
+
53
+ The compiled catalog is cached on disk under
54
+ ``$XDG_CACHE_HOME/legend-dataflow/precompiled-catalogs``, in a
55
+ subdirectory per (resolved) input directory, keyed on the directory
56
+ contents (file mtimes/sizes) and the dbetto version, so repeated
57
+ workflow parses skip the eager compilation entirely.
58
+
59
+ Parameters
60
+ ----------
61
+ validity_path : str or pathlib.Path
62
+ Directory containing the ``validity.yaml`` file and all referenced
63
+ database files.
64
+
65
+ Returns
66
+ -------
67
+ dbetto.catalog.Catalog
68
+ Pre-compiled catalog with all entries eagerly resolved.
69
+ """
70
+ if isinstance(validity_path, str):
71
+ validity_path = Path(validity_path)
72
+
73
+ cache_dir = _cache_dir(validity_path)
74
+ cache_file = cache_dir / f"{_content_digest(validity_path)}.pkl"
75
+ if cache_file.is_file() and not cache_file.is_symlink():
76
+ try:
77
+ with cache_file.open("rb") as f:
78
+ cached = pickle.load(f)
79
+ # tampered/corrupt caches must never be handed back as a catalog
80
+ if isinstance(cached, Catalog):
81
+ return cached
82
+ except Exception: # unreadable pickle: rebuild below
83
+ pass
84
+
85
+ catalog = Catalog.read_from(validity_path / "validity.yaml")
86
+ entries = {}
87
+ textdb = TextDB(validity_path, lazy=False)
88
+ for system in catalog.entries:
89
+ entries[system] = []
90
+ for entry in catalog.entries[system]:
91
+ db = textdb.on(
92
+ datetime.fromtimestamp(entry.valid_from, tz=UTC), category=system
93
+ )
94
+ new_entry = Catalog.Entry(entry.valid_from, db)
95
+ entries[system].append(new_entry)
96
+ compiled = Catalog(entries)
97
+
98
+ try:
99
+ cache_dir.mkdir(parents=True, exist_ok=True)
100
+ for stale in cache_dir.glob("*.pkl"):
101
+ stale.unlink(missing_ok=True)
102
+ fd, tmp_name = tempfile.mkstemp(dir=cache_dir, suffix=".tmp")
103
+ try:
104
+ with os.fdopen(fd, "wb") as f:
105
+ pickle.dump(compiled, f, protocol=pickle.HIGHEST_PROTOCOL)
106
+ Path(tmp_name).replace(cache_file)
107
+ except BaseException:
108
+ Path(tmp_name).unlink(missing_ok=True)
109
+ raise
110
+ except Exception:
111
+ pass # caching is best-effort; a failed write must not break parsing
112
+
113
+ return compiled
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "0.4.0a1",
3
- "distance": 0,
4
- "node": "g30429c60446a5d3e19023e462981054d0cda7d7d",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-08-16"
8
- }
@@ -1,44 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import UTC, datetime
4
- from pathlib import Path
5
-
6
- from dbetto import TextDB
7
- from dbetto.catalog import Catalog
8
-
9
-
10
- def pre_compile_catalog(validity_path: str | Path):
11
- """Pre-compile a dbetto validity catalog for fast repeated access.
12
-
13
- Reads the ``validity.yaml`` catalog from *validity_path* and, for each
14
- system and each entry in the catalog, eagerly loads the corresponding
15
- :class:`dbetto.TextDB` state (instead of loading it lazily on first
16
- access). The resulting :class:`dbetto.catalog.Catalog` can be serialised
17
- and reused across many Snakemake jobs without re-parsing YAML on every
18
- invocation.
19
-
20
- Parameters
21
- ----------
22
- validity_path : str or pathlib.Path
23
- Directory containing the ``validity.yaml`` file and all referenced
24
- database files.
25
-
26
- Returns
27
- -------
28
- dbetto.catalog.Catalog
29
- Pre-compiled catalog with all entries eagerly resolved.
30
- """
31
- if isinstance(validity_path, str):
32
- validity_path = Path(validity_path)
33
- catalog = Catalog.read_from(validity_path / "validity.yaml")
34
- entries = {}
35
- textdb = TextDB(validity_path, lazy=False)
36
- for system in catalog.entries:
37
- entries[system] = []
38
- for entry in catalog.entries[system]:
39
- db = textdb.on(
40
- datetime.fromtimestamp(entry.valid_from, tz=UTC), system=system
41
- )
42
- new_entry = Catalog.Entry(entry.valid_from, db)
43
- entries[system].append(new_entry)
44
- return Catalog(entries)