legend-dataflow-scripts 0.4.0a2__tar.gz → 0.4.0a4__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 (50) hide show
  1. {legend_dataflow_scripts-0.4.0a2/src/legend_dataflow_scripts.egg-info → legend_dataflow_scripts-0.4.0a4}/PKG-INFO +1 -1
  2. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4/src/legend_dataflow_scripts.egg-info}/PKG-INFO +1 -1
  3. legend_dataflow_scripts-0.4.0a4/src/legend_dataflow_scripts.egg-info/scm_version.json +8 -0
  4. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/_version.py +3 -3
  5. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/dsp/dplms.py +3 -0
  6. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/dsp/eopt.py +3 -0
  7. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/dsp/evtsel.py +39 -10
  8. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/dsp/nopt.py +3 -0
  9. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/dsp/pz.py +3 -0
  10. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/hit/aoe.py +4 -1
  11. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/hit/ecal.py +8 -8
  12. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/hit/lq.py +7 -2
  13. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/hit/qc.py +3 -0
  14. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/tier/dsp.py +9 -6
  15. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/utils/cfgtools.py +4 -2
  16. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/workflow/pre_compile_catalog.py +6 -2
  17. legend_dataflow_scripts-0.4.0a2/src/legend_dataflow_scripts.egg-info/scm_version.json +0 -8
  18. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/LICENSE +0 -0
  19. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/README.md +0 -0
  20. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/pyproject.toml +0 -0
  21. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/setup.cfg +0 -0
  22. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legend_dataflow_scripts.egg-info/SOURCES.txt +0 -0
  23. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legend_dataflow_scripts.egg-info/dependency_links.txt +0 -0
  24. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legend_dataflow_scripts.egg-info/entry_points.txt +0 -0
  25. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legend_dataflow_scripts.egg-info/not-zip-safe +0 -0
  26. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legend_dataflow_scripts.egg-info/requires.txt +0 -0
  27. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legend_dataflow_scripts.egg-info/scm_file_list.json +0 -0
  28. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legend_dataflow_scripts.egg-info/top_level.txt +0 -0
  29. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/__init__.py +0 -0
  30. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/__init__.py +0 -0
  31. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/__init__.py +0 -0
  32. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/dsp/__init__.py +0 -0
  33. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/dsp/svm.py +0 -0
  34. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/dsp/svm_build.py +0 -0
  35. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/par/geds/hit/__init__.py +0 -0
  36. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/tier/__init__.py +0 -0
  37. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/tier/hit.py +0 -0
  38. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/utils/__init__.py +0 -0
  39. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/utils/alias_table.py +0 -0
  40. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/utils/convert_np.py +0 -0
  41. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/utils/discharge.py +0 -0
  42. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/utils/files.py +0 -0
  43. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/utils/lgdo_utils.py +0 -0
  44. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/utils/log.py +0 -0
  45. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/utils/plot_dict.py +0 -0
  46. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/utils/pulser_removal.py +0 -0
  47. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/workflow/__init__.py +0 -0
  48. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/workflow/execenv.py +0 -0
  49. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/src/legenddataflowscripts/workflow/filedb.py +0 -0
  50. {legend_dataflow_scripts-0.4.0a2 → legend_dataflow_scripts-0.4.0a4}/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.0a2
3
+ Version: 0.4.0a4
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.0a2
3
+ Version: 0.4.0a4
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.4.0a4",
3
+ "distance": 0,
4
+ "node": "gc92695af39516645152e957ab7dc0a603bfdb7a2",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-27"
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.0a2'
22
- __version_tuple__ = version_tuple = (0, 4, 0, 'a2')
21
+ __version__ = version = '0.4.0a4'
22
+ __version_tuple__ = version_tuple = (0, 4, 0, 'a4')
23
23
 
24
- __commit_id__ = commit_id = 'g4e182698a'
24
+ __commit_id__ = commit_id = 'gc92695af3'
@@ -6,6 +6,7 @@ import time
6
6
  from pathlib import Path
7
7
 
8
8
  import lh5
9
+ import matplotlib as mpl
9
10
  import numpy as np
10
11
  import pygama.math.distributions as pmd # noqa: F401
11
12
  from dbetto.catalog import Props
@@ -25,6 +26,8 @@ from ....utils import (
25
26
  take_table_rows,
26
27
  )
27
28
 
29
+ mpl.use("agg") # no display needed; avoids X11 when DISPLAY is set
30
+
28
31
 
29
32
  def par_geds_dsp_dplms() -> None:
30
33
  """Compute DPLMS optimal filter coefficients for HPGe detectors.
@@ -7,6 +7,7 @@ import warnings
7
7
  from pathlib import Path
8
8
 
9
9
  import lh5
10
+ import matplotlib as mpl
10
11
  import numpy as np
11
12
  import pygama.pargen.energy_optimisation as om # noqa: F401
12
13
  import sklearn.gaussian_process.kernels as ker
@@ -27,6 +28,8 @@ from ....utils import (
27
28
  require_peaks_present,
28
29
  )
29
30
 
31
+ mpl.use("agg") # no display needed; avoids X11 when DISPLAY is set
32
+
30
33
  warnings.filterwarnings(action="ignore", category=RuntimeWarning)
31
34
  try:
32
35
  warnings.filterwarnings(action="ignore", category=np.exceptions.RankWarning)
@@ -133,6 +133,34 @@ def get_out_data(
133
133
  return out_tbl, int(np.count_nonzero(final_mask))
134
134
 
135
135
 
136
+ def initial_bin_width(energy):
137
+ """
138
+ Freedman-Diaconis starting bin width for the peak-finding histogram.
139
+
140
+ Parameters
141
+ ----------
142
+ energy : numpy.ndarray
143
+ uncalibrated energies of the candidate events.
144
+
145
+ Returns
146
+ -------
147
+ float or None
148
+ bin width capped at 2 ADC, or ``None`` when the sample is degenerate
149
+ (fewer than two finite values, or no spread) and no histogram can be built.
150
+ """
151
+ energy = energy[np.isfinite(energy)]
152
+ if len(energy) < 2:
153
+ return None
154
+ width = (
155
+ 2
156
+ * (np.percentile(energy, 75) - np.percentile(energy, 25))
157
+ * len(energy) ** (-1 / 3)
158
+ )
159
+ if not width > 0:
160
+ return None
161
+ return min(width, 2)
162
+
163
+
136
164
  def build_peak_dicts(peaks_kev, kev_widths, masks, log=None):
137
165
  """Build the per-peak accumulator state for the file x peak read loop.
138
166
 
@@ -455,16 +483,17 @@ def par_geds_dsp_evtsel() -> None:
455
483
  )
456
484
  energy = tb_out[energy_parameter].nda
457
485
 
458
- init_bin_width = (
459
- 2
460
- * (
461
- np.nanpercentile(energy, 75)
462
- - np.nanpercentile(energy, 25)
486
+ init_bin_width = initial_bin_width(energy)
487
+ if init_bin_width is None: # too few events to histogram
488
+ msg = (
489
+ f"{peak}: only {np.isfinite(energy).sum()} "
490
+ "usable events, skipping peak"
463
491
  )
464
- * len(energy) ** (-1 / 3)
465
- )
466
-
467
- init_bin_width = min(init_bin_width, 2)
492
+ log.warning(msg)
493
+ peak_dict["obj_buf"] = None
494
+ peak_dict["obj_buf_start"] = 0
495
+ peak_dict["idxs"] = None
496
+ continue
468
497
 
469
498
  hist, bins, var = pgh.get_hist(
470
499
  energy,
@@ -487,7 +516,7 @@ def par_geds_dsp_evtsel() -> None:
487
516
  mu = peak_top_pars[0]
488
517
  if mu > np.nanmax(bins) or mu < np.nanmin(bins):
489
518
  raise ValueError
490
- except Exception:
519
+ except (ValueError, TypeError, IndexError):
491
520
  mu = np.nan
492
521
  if mu is None or np.isnan(mu):
493
522
  log.debug("Fit failed, using max guess")
@@ -6,6 +6,7 @@ import time
6
6
  from pathlib import Path
7
7
 
8
8
  import lh5
9
+ import matplotlib as mpl
9
10
  import numpy as np
10
11
  import pygama.pargen.noise_optimization as pno
11
12
  from dbetto.catalog import Props
@@ -20,6 +21,8 @@ from ....utils import (
20
21
  require_config_keys,
21
22
  )
22
23
 
24
+ mpl.use("agg") # no display needed; avoids X11 when DISPLAY is set
25
+
23
26
 
24
27
  def par_geds_dsp_nopt() -> None:
25
28
  """Optimise DSP filter parameters for noise performance.
@@ -6,6 +6,7 @@ import pickle as pkl
6
6
  from pathlib import Path
7
7
 
8
8
  import lh5
9
+ import matplotlib as mpl
9
10
  import numpy as np
10
11
  from dbetto.catalog import Props
11
12
  from dspeed import build_dsp
@@ -24,6 +25,8 @@ from ....utils import (
24
25
  take_table_rows,
25
26
  )
26
27
 
28
+ mpl.use("agg") # no display needed; avoids X11 when DISPLAY is set
29
+
27
30
 
28
31
  def par_geds_dsp_pz() -> None:
29
32
  """Determine the pole-zero (PZ) decay constant(s) for HPGe waveforms.
@@ -10,9 +10,10 @@ import time
10
10
  import warnings
11
11
  from pathlib import Path
12
12
 
13
+ import matplotlib as mpl
13
14
  import numpy as np
14
15
  from dbetto.catalog import Props
15
- from pygama.pargen.AoE_cal import * # noqa: F403
16
+ from pygama.pargen.AoE_cal import *
16
17
  from pygama.pargen.AoE_cal import CalAoE
17
18
  from pygama.pargen.utils import load_data
18
19
 
@@ -28,6 +29,8 @@ from ....utils import (
28
29
  require_unique_suffixes,
29
30
  )
30
31
 
32
+ mpl.use("agg") # no display needed; avoids X11 when DISPLAY is set
33
+
31
34
  log = logging.getLogger(__name__)
32
35
 
33
36
  warnings.filterwarnings(action="ignore", category=RuntimeWarning)
@@ -5,7 +5,7 @@ import copy
5
5
  import inspect
6
6
  import pickle as pkl
7
7
  import warnings
8
- from datetime import datetime
8
+ from datetime import UTC, datetime
9
9
  from pathlib import Path
10
10
 
11
11
  import lh5
@@ -104,14 +104,14 @@ def plot_2614_timemap(
104
104
 
105
105
  ticks, _ = plt.xticks()
106
106
  plt.xlabel(
107
- f"Time starting : {datetime.utcfromtimestamp(ticks[0]).strftime('%d/%m/%y %H:%M')}"
107
+ f"Time starting : {datetime.fromtimestamp(ticks[0], tz=UTC).strftime('%d/%m/%y %H:%M')}"
108
108
  )
109
109
  plt.ylabel("Energy(keV)")
110
110
  plt.ylim([erange[0], erange[1]])
111
111
 
112
112
  plt.xticks(
113
113
  ticks,
114
- [datetime.utcfromtimestamp(tick).strftime("%H:%M") for tick in ticks],
114
+ [datetime.fromtimestamp(tick, tz=UTC).strftime("%H:%M") for tick in ticks],
115
115
  )
116
116
  plt.close()
117
117
  return fig
@@ -188,13 +188,13 @@ def plot_pulser_timemap(
188
188
  plt.ylim([mean - n_spread * spread, mean + n_spread * spread])
189
189
  ticks, _ = plt.xticks()
190
190
  plt.xlabel(
191
- f"Time starting : {datetime.utcfromtimestamp(ticks[0]).strftime('%d/%m/%y %H:%M')}"
191
+ f"Time starting : {datetime.fromtimestamp(ticks[0], tz=UTC).strftime('%d/%m/%y %H:%M')}"
192
192
  )
193
193
  plt.ylabel("Energy(keV)")
194
194
 
195
195
  plt.xticks(
196
196
  ticks,
197
- [datetime.utcfromtimestamp(tick).strftime("%H:%M") for tick in ticks],
197
+ [datetime.fromtimestamp(tick, tz=UTC).strftime("%H:%M") for tick in ticks],
198
198
  )
199
199
  plt.close()
200
200
  return fig
@@ -488,14 +488,14 @@ def plot_baseline_timemap(
488
488
 
489
489
  ticks, _ = plt.xticks()
490
490
  plt.xlabel(
491
- f"Time starting : {datetime.utcfromtimestamp(ticks[0]).strftime('%d/%m/%y %H:%M')}"
491
+ f"Time starting : {datetime.fromtimestamp(ticks[0], tz=UTC).strftime('%d/%m/%y %H:%M')}"
492
492
  )
493
493
  plt.ylabel("Baseline Value")
494
494
  plt.ylim([mean - n_spread * spread, mean + n_spread * spread])
495
495
 
496
496
  plt.xticks(
497
497
  ticks,
498
- [datetime.utcfromtimestamp(tick).strftime("%H:%M") for tick in ticks],
498
+ [datetime.fromtimestamp(tick, tz=UTC).strftime("%H:%M") for tick in ticks],
499
499
  )
500
500
  plt.close()
501
501
  return fig
@@ -683,7 +683,7 @@ def get_results_dict(ecal_class, data, cal_energy_param, selection_string):
683
683
 
684
684
  pk_dict = results_dict["peak_parameters"]
685
685
 
686
- for _, dic in pk_dict.items():
686
+ for dic in pk_dict.values():
687
687
  dic["function"] = dic["function"].name
688
688
  dic["parameters"] = dic["parameters"].to_dict()
689
689
  dic["uncertainties"] = dic["uncertainties"].to_dict()
@@ -9,12 +9,13 @@ import time
9
9
  import warnings
10
10
  from pathlib import Path
11
11
 
12
+ import matplotlib as mpl
12
13
  import numpy as np
13
14
  import pandas as pd
14
15
  from dbetto.catalog import Props
15
16
  from pygama.math.distributions import gaussian
16
- from pygama.pargen.AoE_cal import * # noqa: F403
17
- from pygama.pargen.lq_cal import * # noqa: F403
17
+ from pygama.pargen.AoE_cal import *
18
+ from pygama.pargen.lq_cal import *
18
19
  from pygama.pargen.lq_cal import LQCal
19
20
  from pygama.pargen.utils import load_data
20
21
 
@@ -30,6 +31,8 @@ from ....utils import (
30
31
  require_unique_suffixes,
31
32
  )
32
33
 
34
+ mpl.use("agg") # no display needed; avoids X11 when DISPLAY is set
35
+
33
36
  log = logging.getLogger(__name__)
34
37
  warnings.filterwarnings(action="ignore", category=RuntimeWarning)
35
38
 
@@ -306,6 +309,8 @@ def run_lq_calibration(
306
309
  ][configs["cal_energy_param"]]["eres_linear"]
307
310
  except KeyError:
308
311
  lq_obj.eres_func = {}
312
+ for panel in getattr(lq_obj, "gauss_trends_hist", []):
313
+ panel.pop("model", None) # nested fit func, can't pickle
309
314
  out_dicts[name] = out_dict
310
315
  lq_objs[name] = copy.deepcopy(lq_obj)
311
316
  lq_plot_dict[name] = copy.deepcopy(lq_plots)
@@ -9,6 +9,7 @@ import time
9
9
  import warnings
10
10
  from pathlib import Path
11
11
 
12
+ import matplotlib as mpl
12
13
  import numpy as np
13
14
  from dbetto.catalog import Props
14
15
  from lh5 import ls
@@ -28,6 +29,8 @@ from ....utils import (
28
29
  prepare_output_paths,
29
30
  )
30
31
 
32
+ mpl.use("agg") # no display needed; avoids X11 when DISPLAY is set
33
+
31
34
  log = logging.getLogger(__name__)
32
35
  warnings.filterwarnings(action="ignore", category=RuntimeWarning)
33
36
 
@@ -3,7 +3,8 @@ from __future__ import annotations
3
3
  import argparse
4
4
  import time
5
5
  import warnings
6
- from multiprocessing import Pool
6
+ from concurrent.futures import ProcessPoolExecutor
7
+ from multiprocessing import get_context
7
8
  from pathlib import Path
8
9
 
9
10
  import lh5
@@ -51,7 +52,7 @@ def build_dsp_wrapper(kwargs):
51
52
  """Unpack *kwargs* and delegate to :func:`dspeed.build_dsp`.
52
53
 
53
54
  This thin wrapper exists so that a single-argument callable can be passed
54
- to :meth:`multiprocessing.Pool.map`.
55
+ to :meth:`concurrent.futures.Executor.map`.
55
56
 
56
57
  Parameters
57
58
  ----------
@@ -239,10 +240,12 @@ def build_tier_dsp() -> None:
239
240
  }
240
241
  process_kwargs_list.append(kwargs)
241
242
 
242
- # Create a multiprocessing pool
243
- with Pool(processes=args.n_processes) as pool:
244
- # Use starmap to pass multiple arguments to the process function
245
- pool.map(build_dsp_wrapper, process_kwargs_list)
243
+ # fork pinned: default start method changes in py3.14
244
+ # a killed worker raises BrokenProcessPool here; Pool.map would hang forever
245
+ with ProcessPoolExecutor(
246
+ max_workers=args.n_processes, mp_context=get_context("fork")
247
+ ) as pool:
248
+ list(pool.map(build_dsp_wrapper, process_kwargs_list))
246
249
 
247
250
  # merge the DSPs
248
251
  log.info("Merging DSPs")
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import copy
3
4
  from collections.abc import Iterable, Mapping
4
5
  from pathlib import Path
5
6
 
@@ -133,14 +134,15 @@ def get_rule_config(configs_path, rule_name, timestamp, datatype):
133
134
 
134
135
  Wraps ``TextDB(...).on(...)["snakemake_rules"][rule_name]`` so that a
135
136
  missing key names the rule, timestamp, datatype and config path instead
136
- of raising a bare :class:`KeyError`.
137
+ of raising a bare :class:`KeyError`. Returns a writable deep copy, since
138
+ ``TextDB.on()`` results are shared and read-only.
137
139
  """
138
140
  if not Path(configs_path).is_dir():
139
141
  msg = f"config directory {configs_path} does not exist"
140
142
  raise FileNotFoundError(msg)
141
143
  configs = TextDB(configs_path, lazy=True).on(timestamp, category=datatype)
142
144
  try:
143
- return configs["snakemake_rules"][rule_name]
145
+ return copy.deepcopy(configs["snakemake_rules"][rule_name])
144
146
  except KeyError as err:
145
147
  msg = (
146
148
  f"config resolved from {configs_path} for timestamp {timestamp} "
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import hashlib
4
+ import logging
4
5
  import os
5
6
  import pickle
6
7
  import tempfile
@@ -11,6 +12,8 @@ from pathlib import Path
11
12
  from dbetto import TextDB
12
13
  from dbetto.catalog import Catalog
13
14
 
15
+ log = logging.getLogger(__name__)
16
+
14
17
 
15
18
  def _content_digest(validity_path: Path) -> str:
16
19
  """Digest of the directory contents (relative path, mtime and size of
@@ -80,7 +83,7 @@ def pre_compile_catalog(validity_path: str | Path):
80
83
  if isinstance(cached, Catalog):
81
84
  return cached
82
85
  except Exception: # unreadable pickle: rebuild below
83
- pass
86
+ log.debug("could not read catalog cache %s", cache_file, exc_info=True)
84
87
 
85
88
  catalog = Catalog.read_from(validity_path / "validity.yaml")
86
89
  entries = {}
@@ -108,6 +111,7 @@ def pre_compile_catalog(validity_path: str | Path):
108
111
  Path(tmp_name).unlink(missing_ok=True)
109
112
  raise
110
113
  except Exception:
111
- pass # caching is best-effort; a failed write must not break parsing
114
+ # caching is best-effort; a failed write must not break parsing
115
+ log.debug("could not write catalog cache %s", cache_file, exc_info=True)
112
116
 
113
117
  return compiled
@@ -1,8 +0,0 @@
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
- }