pelutils 4.2.1__tar.gz → 4.3.1__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 (67) hide show
  1. {pelutils-4.2.1 → pelutils-4.3.1}/PKG-INFO +25 -16
  2. {pelutils-4.2.1 → pelutils-4.3.1}/README.md +23 -15
  3. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/__version__.py +1 -1
  4. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/array/_blob.py +21 -0
  5. pelutils-4.3.1/pelutils/legacy/v3/__init__.py +10 -0
  6. pelutils-4.3.1/pelutils/legacy/v3/_datastorage.py +150 -0
  7. pelutils-4.3.1/pelutils/py.typed +0 -0
  8. pelutils-4.3.1/pelutils/serialization/__init__.py +51 -0
  9. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/stats/__init__.py +7 -7
  10. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/stats/_distributions.py +26 -15
  11. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/stats/_stats.py +7 -3
  12. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/PKG-INFO +25 -16
  13. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/SOURCES.txt +3 -0
  14. {pelutils-4.2.1 → pelutils-4.3.1}/setup.py +1 -0
  15. pelutils-4.2.1/pelutils/serialization/__init__.py +0 -51
  16. {pelutils-4.2.1 → pelutils-4.3.1}/MANIFEST.in +0 -0
  17. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/__init__.py +0 -0
  18. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/__init__.py +0 -0
  19. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/array/blob.c +0 -0
  20. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/array/blob.h +0 -0
  21. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/array/unique.c +0 -0
  22. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/array/unique.h +0 -0
  23. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/deque/deque.c +0 -0
  24. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/deque/deque.h +0 -0
  25. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/hashmap/LICENSE +0 -0
  26. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/hashmap/hashmap.c +0 -0
  27. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/hashmap/hashmap.h +0 -0
  28. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/pelutils.c +0 -0
  29. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/types.h +0 -0
  30. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_entry_points/__init__.py +0 -0
  31. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_entry_points/linecounter.py +0 -0
  32. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/array/__init__.py +0 -0
  33. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/array/_unique.py +0 -0
  34. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/array/_utils.py +0 -0
  35. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/job_parser/__init__.py +0 -0
  36. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/job_parser/_job_parser.py +0 -0
  37. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/job_parser/_structs.py +0 -0
  38. /pelutils-4.2.1/pelutils/py.typed → /pelutils-4.3.1/pelutils/legacy/__init__.py +0 -0
  39. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/logging/__init__.py +0 -0
  40. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/logging/_logger.py +0 -0
  41. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/logging/_rich_string.py +0 -0
  42. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/logging/_rotate.py +0 -0
  43. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/__init__.py +0 -0
  44. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_conditional_import.py +0 -0
  45. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_files.py +0 -0
  46. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_git.py +0 -0
  47. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_misc.py +0 -0
  48. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_platform.py +0 -0
  49. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_table.py +0 -0
  50. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/plots/__init__.py +0 -0
  51. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/plots/_figure.py +0 -0
  52. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/plots/_histogram.py +0 -0
  53. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/plots/_utils.py +0 -0
  54. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/serialization/_jsonl.py +0 -0
  55. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/serialization/_pretty_json.py +0 -0
  56. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/serialization/_universal_json_model.py +0 -0
  57. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/tests/__init__.py +0 -0
  58. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/tests/_tests.py +0 -0
  59. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/ticktock/__init__.py +0 -0
  60. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/ticktock/_ticktock.py +0 -0
  61. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/types/__init__.py +0 -0
  62. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/types/_types.py +0 -0
  63. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/dependency_links.txt +0 -0
  64. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/entry_points.txt +0 -0
  65. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/requires.txt +0 -0
  66. {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/top_level.txt +0 -0
  67. {pelutils-4.2.1 → pelutils-4.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pelutils
3
- Version: 4.2.1
3
+ Version: 4.3.1
4
4
  Summary: The Swiss army knife of Python projects
5
5
  Home-page: https://github.com/peleiden/pelutils
6
6
  Download-URL: https://pypi.org/project/pelutils/
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
20
21
  Requires-Python: >=3.11
21
22
  Description-Content-Type: text/markdown
22
23
  Provides-Extra: dev
@@ -163,10 +164,15 @@ importantly one per thread, as profiling is not thread-safe.
163
164
 
164
165
  ## Serialisation
165
166
 
166
- `UniversalJsonModel` extends `pydantic.BaseModel` with `save`/`load` methods and can
167
- serialise attributes that pydantic cannot — numpy arrays, tensors, and arbitrary
168
- objects are base64-pickled inline, everything else stays plain, human-readable JSON.
169
- Long lists are wrapped to the line-length limit instead of one element per line.
167
+ `UniversalJsonModel` is for storing Pydantic models that contain a mixture of ordinary JSON
168
+ values and Python-specific values. Ordinary values remain JSON, so the file structure can be
169
+ inspected and diffed. Values that JSON cannot represent, such as NumPy arrays, tensors, and
170
+ other Python objects, are stored as base64-encoded pickle payloads.
171
+
172
+ The `save()` and `load()` methods support convient file-based workflows.
173
+ `save()` serialises the model to a JSON file, while `load()` reconstructs the model from one.
174
+ Use `to_json_dict()` and `from_json_dict(...)` when the serialized representation belongs inside
175
+ another structure or is handled by another storage layer.
170
176
 
171
177
  ```py
172
178
  import numpy as np
@@ -192,11 +198,14 @@ result.save("results/run-1.json")
192
198
  result = Result.load("results/run-1.json")
193
199
  ```
194
200
 
195
- Use `to_json_dict()` / `from_json_dict(...)` to convert to and from a plain dict
196
- without touching the filesystem — useful for nesting inside other structures. The
197
- `pretty_json` helper function is also available on its own. The `serialization`
198
- module also includes JSONL read/write helpers (`jsonl_dump`, `jsonl_load`, ...)
199
- with largely the same interface as is provided by the built-in `json` module.
201
+ `pretty_json` is a string-returning formatter for standalone dictionaries and lists. It fills the
202
+ same role as `json.dumps`, with compact nested containers and wrapped primitive lists; write the
203
+ returned string to a file when needed. The module also includes JSONL read/write helpers
204
+ (`jsonl_dump`, `jsonl_load`, ...) for newline-delimited records.
205
+
206
+ This format is intended for trusted configuration objects, application state, experiment results, etc., not as a
207
+ language-neutral interchange format. Pickle payloads are Python-specific and loading them can
208
+ execute arbitrary code. Do not load files from untrusted sources.
200
209
 
201
210
  ## Config and command-line argument parsing
202
211
 
@@ -253,18 +262,18 @@ values, index, inverse, counts = unique(
253
262
 
254
263
  ### Statistics
255
264
 
256
- Common statistical helpers, plus wrappers around scipy distributions reparametrised
257
- as in Jim Pitman's *Probability* (rather than scipy's `loc`/`scale`, which are
265
+ Common statistical helpers, plus wrappers around SciPy distributions reparametrised
266
+ as in Jim Pitman's *Probability* (rather than SciPy's `loc`/`scale`, which are
258
267
  unintuitive for many distributions).
259
268
 
260
269
  ```py
261
- from pelutils.stats import z_score
262
270
  from pelutils.stats import expon
271
+ from pelutils.stats import z_score
263
272
 
264
- # 95 % confidence interval half-width for a standard normal (defaults give ~1.96)
265
- half_width = std * z_score()
273
+ # 95 % confidence interval half-width for a standard normal
274
+ half_width = z_score() # standard deviation is 1
266
275
 
267
- # One-sided z value for an Exponential(λ=2) at the 1 % significance level
276
+ # Upper 1 % critical value for an Exponential(lambda=2)
268
277
  zval = z_score(alpha=0.01, two_sided=False, distribution=expon(lambda_=2))
269
278
  ```
270
279
 
@@ -138,10 +138,15 @@ importantly one per thread, as profiling is not thread-safe.
138
138
 
139
139
  ## Serialisation
140
140
 
141
- `UniversalJsonModel` extends `pydantic.BaseModel` with `save`/`load` methods and can
142
- serialise attributes that pydantic cannot — numpy arrays, tensors, and arbitrary
143
- objects are base64-pickled inline, everything else stays plain, human-readable JSON.
144
- Long lists are wrapped to the line-length limit instead of one element per line.
141
+ `UniversalJsonModel` is for storing Pydantic models that contain a mixture of ordinary JSON
142
+ values and Python-specific values. Ordinary values remain JSON, so the file structure can be
143
+ inspected and diffed. Values that JSON cannot represent, such as NumPy arrays, tensors, and
144
+ other Python objects, are stored as base64-encoded pickle payloads.
145
+
146
+ The `save()` and `load()` methods support convient file-based workflows.
147
+ `save()` serialises the model to a JSON file, while `load()` reconstructs the model from one.
148
+ Use `to_json_dict()` and `from_json_dict(...)` when the serialized representation belongs inside
149
+ another structure or is handled by another storage layer.
145
150
 
146
151
  ```py
147
152
  import numpy as np
@@ -167,11 +172,14 @@ result.save("results/run-1.json")
167
172
  result = Result.load("results/run-1.json")
168
173
  ```
169
174
 
170
- Use `to_json_dict()` / `from_json_dict(...)` to convert to and from a plain dict
171
- without touching the filesystem — useful for nesting inside other structures. The
172
- `pretty_json` helper function is also available on its own. The `serialization`
173
- module also includes JSONL read/write helpers (`jsonl_dump`, `jsonl_load`, ...)
174
- with largely the same interface as is provided by the built-in `json` module.
175
+ `pretty_json` is a string-returning formatter for standalone dictionaries and lists. It fills the
176
+ same role as `json.dumps`, with compact nested containers and wrapped primitive lists; write the
177
+ returned string to a file when needed. The module also includes JSONL read/write helpers
178
+ (`jsonl_dump`, `jsonl_load`, ...) for newline-delimited records.
179
+
180
+ This format is intended for trusted configuration objects, application state, experiment results, etc., not as a
181
+ language-neutral interchange format. Pickle payloads are Python-specific and loading them can
182
+ execute arbitrary code. Do not load files from untrusted sources.
175
183
 
176
184
  ## Config and command-line argument parsing
177
185
 
@@ -228,18 +236,18 @@ values, index, inverse, counts = unique(
228
236
 
229
237
  ### Statistics
230
238
 
231
- Common statistical helpers, plus wrappers around scipy distributions reparametrised
232
- as in Jim Pitman's *Probability* (rather than scipy's `loc`/`scale`, which are
239
+ Common statistical helpers, plus wrappers around SciPy distributions reparametrised
240
+ as in Jim Pitman's *Probability* (rather than SciPy's `loc`/`scale`, which are
233
241
  unintuitive for many distributions).
234
242
 
235
243
  ```py
236
- from pelutils.stats import z_score
237
244
  from pelutils.stats import expon
245
+ from pelutils.stats import z_score
238
246
 
239
- # 95 % confidence interval half-width for a standard normal (defaults give ~1.96)
240
- half_width = std * z_score()
247
+ # 95 % confidence interval half-width for a standard normal
248
+ half_width = z_score() # standard deviation is 1
241
249
 
242
- # One-sided z value for an Exponential(λ=2) at the 1 % significance level
250
+ # Upper 1 % critical value for an Exponential(lambda=2)
243
251
  zval = z_score(alpha=0.01, two_sided=False, distribution=expon(lambda_=2))
244
252
  ```
245
253
 
@@ -1,2 +1,2 @@
1
1
  # Do not put anything else in this file
2
- __version__ = "4.2.1"
2
+ __version__ = "4.3.1"
@@ -1,7 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import SupportsIndex
4
+
3
5
  import _pelutils_c as _c
4
6
  import numpy as np
7
+ from typing_extensions import override
5
8
 
6
9
  import pelutils._c as c_utils
7
10
  from pelutils.types import BoolArray, IntArray
@@ -171,3 +174,21 @@ class SparseGridBlobDetection:
171
174
  # If the error happens early in init (e.g. wrong call to __init__), _pointer_args has not yet been defined,
172
175
  # and the hashmap has not been created
173
176
  pass
177
+
178
+ def __copy__(self):
179
+ """Prevent shallow copies of the C-backed detector."""
180
+ raise TypeError("SparseGridBlobDetection instances cannot be copied or pickled")
181
+
182
+ def __deepcopy__(self, memo: dict[int, object]):
183
+ """Prevent deep copies of the C-backed detector."""
184
+ raise TypeError("SparseGridBlobDetection instances cannot be copied or pickled")
185
+
186
+ @override
187
+ def __reduce__(self):
188
+ """Prevent pickling of the C-backed detector."""
189
+ raise TypeError("SparseGridBlobDetection instances cannot be copied or pickled")
190
+
191
+ @override
192
+ def __reduce_ex__(self, protocol: SupportsIndex):
193
+ """Prevent protocol-specific pickling of the C-backed detector."""
194
+ raise TypeError("SparseGridBlobDetection instances cannot be copied or pickled")
@@ -0,0 +1,10 @@
1
+ """Legacy module for preserving functionality that no longer has a place in pelutils, but is hard to change.
2
+
3
+ This is especially relevant for functionality which leaves persistent data which may need to be loaded in later.
4
+ ``DataStorage`` is the obvious example here as it defines a custom serialisation/deserialisation protocol.
5
+ Upgrading to pelutils 4 without this module would make old persisted ``DataStorage`` instances impossible to load.
6
+ """
7
+
8
+ from ._datastorage import DataStorage
9
+
10
+ __all__ = ("DataStorage",)
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import pickle
5
+ from pathlib import Path
6
+ from typing import Self
7
+
8
+ try:
9
+ import rapidjson # pyright: ignore[reportMissingImports]
10
+ except ModuleNotFoundError:
11
+ import json as rapidjson
12
+
13
+
14
+ class DataStorage:
15
+ """The DataStorage class is an augmentation of the dataclass that incluces save and load functionality.
16
+
17
+ DataStorage classes must inherit from DataStorage and be annotated with ``@dataclass``. Data will be saved
18
+ to two files: A json files for json-serializable data and a pickle file for everything else. These files
19
+ are by default named after the class, but it is possible to use a custom name in the save and load methods.
20
+ The files are only created if necessary, so for instance if all data is json-serializable, no pickle file
21
+ will be created.
22
+
23
+ Data is in general preserved exactly as-is when saved data is loaded into memory with few exceptions.
24
+ Nnotably, tuples are considered json-serializble, and so will be saved to the json file and will be
25
+ loaded as lists.
26
+
27
+ Quick start
28
+ -----------
29
+
30
+ .. code-block:: python
31
+
32
+ @dataclass
33
+ class ResultData(DataStorage):
34
+ shots: int
35
+ goalscorers: list
36
+ dists: np.ndarray
37
+
38
+ rdata = ResultData(shots=1, goalscorers=["Max Fenger"], dists=np.ones(22)*10)
39
+ rdata.save("max")
40
+ # Now shots and goalscorers are saved in <pwd>/max/ResultData.json and dists in <pwd>/max/ResultData.pkl
41
+
42
+ # Then to load
43
+ rdata = ResultData.load("max")
44
+ print(rdata.goalscorers) # ["Max Fenger"]
45
+
46
+ """
47
+
48
+ def __init__(self, *args, **kwargs): # noqa: ARG002 # pyright: ignore[reportUnknownParameterType, reportMissingParameterType]
49
+ """This method is overwritten in any class decorated with @dataclass.
50
+
51
+ Therefore, if this method is called, it is an error.
52
+ """ # noqa: D401, D404
53
+ raise TypeError(f"DataStorage class {self.__class__.__name__} must be decorated with @dataclass")
54
+
55
+ @classmethod
56
+ def json_name(cls, save_name: str | None = None):
57
+ """Return the name of the json file to where json serializable data will be saved."""
58
+ return (save_name or cls.__name__) + ".json"
59
+
60
+ @classmethod
61
+ def pickle_name(cls, save_name: str | None = None):
62
+ """Return the name of the pickle file to where non json serializable data will be saved."""
63
+ return (save_name or cls.__name__) + ".pkl"
64
+
65
+ def save(self, loc: str | Path, save_name: str | None = None, *, indent: int | None = 4) -> list[str]:
66
+ """Save all the fields of the instatiated data classes as either json, pickle or designated serialization function.
67
+
68
+ Parameters
69
+ ----------
70
+ loc : str | Path
71
+ Directory in which to save data.
72
+ save_name : str | None, optional
73
+ If given, file name (excluding extension) to use for saving, by default None.
74
+ indent : int | None, optional
75
+ Indent used for the json file, by default 4. It is recommended to set to None when large objects are saved to json.
76
+
77
+ Returns
78
+ -------
79
+ list[str]
80
+ Produced files containing the data.
81
+ """
82
+ loc = str(loc)
83
+ os.makedirs(loc, exist_ok=True)
84
+
85
+ to_json = dict()
86
+ to_pickle = dict()
87
+
88
+ for key, data in self.__dict__.items():
89
+ try:
90
+ # Test whether the data is json serializable by dumping it to string.
91
+ rapidjson.dumps({key: data})
92
+ to_json[key] = data
93
+ except TypeError:
94
+ to_pickle[key] = data
95
+
96
+ # Save data
97
+ paths = list()
98
+ if to_json:
99
+ paths.append(os.path.join(loc, self.json_name(save_name)))
100
+ with open(paths[-1], "w", encoding="utf-8") as f:
101
+ # Save json. This does not guarantee writing to disk, so flushing
102
+ # and synchronization is also done to increase chance of writing
103
+ dump = rapidjson.dumps(to_json, indent=indent)
104
+ f.write(dump)
105
+ f.flush()
106
+ os.fsync(f.fileno())
107
+
108
+ if to_pickle:
109
+ paths.append(os.path.join(loc, self.pickle_name(save_name)))
110
+ with open(paths[-1], "wb") as f:
111
+ pickle.dump(to_pickle, f)
112
+ f.flush()
113
+ os.fsync(f.fileno())
114
+
115
+ return paths
116
+
117
+ @classmethod
118
+ def load(cls, loc: str | Path, save_name: str | None = None) -> Self:
119
+ """Instantiate the DataStorage-inherited class by loading all files saved by `save` of that same class.
120
+
121
+ Parameters
122
+ ----------
123
+ loc : str | Path
124
+ Directory from which to load data.
125
+ save_name : str | None, optional
126
+ If given, file name (excluding extension) to use for loading, by default None. It should match what was given to `save`.
127
+
128
+ Returns
129
+ -------
130
+ DataStorage
131
+ An instance of this class with the content of the files.
132
+ """
133
+ loc = str(loc)
134
+ json_file = os.path.join(loc, cls.json_name(save_name))
135
+ pickle_file = os.path.join(loc, cls.pickle_name(save_name))
136
+
137
+ fields = dict()
138
+
139
+ if os.path.isfile(json_file):
140
+ with open(json_file, encoding="utf-8") as f:
141
+ fields.update(rapidjson.load(f))
142
+
143
+ if os.path.isfile(pickle_file):
144
+ with open(pickle_file, "rb") as f:
145
+ fields.update(pickle.load(f))
146
+
147
+ if not os.path.isfile(json_file) and not os.path.isfile(pickle_file):
148
+ raise FileNotFoundError(f"Unable to load {cls.__name__}; neither {json_file} nor {pickle_file} found.")
149
+
150
+ return cls(**fields)
File without changes
@@ -0,0 +1,51 @@
1
+ """JSON persistence for Pydantic models containing non-JSON values.
2
+
3
+ JSON is useful when the structure of a file should remain inspectable, but it does not represent
4
+ values such as NumPy arrays, tensors, or arbitrary Python objects. ``UniversalJsonModel``
5
+ keeps JSON-native values in the document and stores unsupported field values as base64-encoded
6
+ pickle payloads. This preserves a readable JSON envelope without requiring callers to write
7
+ converters for every field type.
8
+
9
+ The usual interface is :meth:`UniversalJsonModel.save` and
10
+ :meth:`UniversalJsonModel.load`. ``save`` creates missing parent directories and writes the model to a JSON
11
+ file; ``load`` reconstructs the model from that file. Use :meth:`UniversalJsonModel.to_json_dict`
12
+ and :meth:`UniversalJsonModel.from_json_dict` when the serialized representation should be nested
13
+ inside another structure or handled by another storage layer.
14
+
15
+ For standalone dictionaries and lists, ``pretty_json`` converts Python objects to pretty-formatted JSON,
16
+ filling the same role as ``json.dumps``, but often making the resulting JSON more readable.
17
+ JSONL helpers handle files containing multiple JSON records separated by newlines.
18
+
19
+ Quick start
20
+ -----------
21
+
22
+ .. code-block:: python
23
+
24
+ import numpy as np
25
+ from pelutils.serialization import UniversalJsonModel
26
+ from pelutils.types import FloatArray
27
+
28
+ class Result(UniversalJsonModel):
29
+ accuracy: float
30
+ predictions: FloatArray # numpy arrays are handled automatically
31
+
32
+ result = Result(accuracy=0.97, predictions=np.arange(5, dtype=np.float16))
33
+ result.save("results/run-1.json")
34
+ result = Result.load("results/run-1.json")
35
+
36
+ This format is intended for trusted application data such as experiment results and cached state,
37
+ not as a language-neutral interchange format. Pickle payloads are Python-specific, and loading
38
+ them can execute arbitrary code. Use a format with an explicit schema and safe decoder when data
39
+ comes from outside the application.
40
+
41
+ .. warning::
42
+
43
+ ``UniversalJsonModel.load`` and the pickle fallback execute code while loading. Never load
44
+ files from an untrusted source.
45
+ """
46
+
47
+ from ._jsonl import jsonl_dump, jsonl_dumps, jsonl_load, jsonl_loads
48
+ from ._pretty_json import pretty_json
49
+ from ._universal_json_model import UniversalJsonModel
50
+
51
+ __all__ = ("UniversalJsonModel", "jsonl_dump", "jsonl_dumps", "jsonl_load", "jsonl_loads", "pretty_json")
@@ -1,11 +1,11 @@
1
- """Statistical helpers and scipy distributions parametrised the way textbooks are.
1
+ """Statistical helpers and SciPy distributions parametrised the way textbooks are.
2
2
 
3
3
  ``scipy.stats`` parametrises every distribution through generic ``loc``/``scale``
4
4
  arguments, which rarely match how a distribution is actually defined: an exponential's
5
5
  ``scale`` is ``1 / lambda``, a normal takes a standard deviation rather than a variance,
6
6
  and a gamma's ``a``/``scale`` bear no obvious relation to its shape and rate. This module
7
- wraps the scipy distributions so you pass their *natural* parameters instead — following
8
- the conventions in Jim Pitman's *Probability* — and returns an ordinary frozen scipy
7
+ wraps the SciPy distributions so you pass their *natural* parameters instead — following
8
+ the conventions in Jim Pitman's *Probability* — and returns an ordinary frozen SciPy
9
9
  distribution, so ``.pdf``, ``.cdf``, ``.rvs`` and friends all work as usual. It also
10
10
  provides :func:`z_score` for turning a significance level into a critical value.
11
11
 
@@ -17,12 +17,12 @@ Quick start
17
17
  from pelutils.stats import expon, norm, z_score
18
18
 
19
19
  dist = norm(mu=0, sigma2=4) # mean and *variance*, not standard deviation
20
- dist.cdf(1.5) # a plain frozen scipy distribution
20
+ dist.cdf(1.5) # a plain frozen SciPy distribution
21
21
 
22
- # 95 % confidence-interval half-width for a standard normal (defaults give ~1.96)
23
- half_width = std * z_score()
22
+ # 95 % confidence-interval half-width for a standard normal
23
+ half_width = z_score() # standard deviation is 1
24
24
 
25
- # One-sided z value for an Exponential(lambda=2) at the 1 % significance level
25
+ # Upper 1 % critical value for an Exponential(lambda=2)
26
26
  zval = z_score(alpha=0.01, two_sided=False, distribution=expon(lambda_=2))
27
27
 
28
28
  Continuous distributions: :func:`norm`, :func:`lognorm`, :func:`expon`, :func:`gamma`,
@@ -1,4 +1,4 @@
1
- """Functions returning scipy.stats distribution objects reparameterized to Jim Pitman's "Probability"."""
1
+ """Functions returning SciPy distribution objects reparameterized to Jim Pitman's "Probability"."""
2
2
 
3
3
  import numpy as np
4
4
  from scipy import stats
@@ -6,25 +6,25 @@ from scipy import stats
6
6
 
7
7
  # Continuous distributions
8
8
  def norm(mu: float, sigma2: float):
9
- """Return a normal distribution from mean and variance."""
9
+ """Return a normal distribution with mean ``mu`` and variance ``sigma2``."""
10
10
  assert sigma2 > 0
11
11
  return stats.norm(loc=mu, scale=np.sqrt(sigma2))
12
12
 
13
13
 
14
14
  def lognorm(mu: float, sigma2: float):
15
- """Return a log normal distribution from the mean and variance of the corresponding normal distribution."""
15
+ """Return a log-normal distribution where ``log(X)`` has mean ``mu`` and variance ``sigma2``."""
16
16
  assert sigma2 > 0
17
17
  return stats.lognorm(s=np.sqrt(sigma2), scale=np.exp(mu))
18
18
 
19
19
 
20
20
  def expon(lambda_: float):
21
- """Return an exponential distribution."""
21
+ """Return an exponential distribution with rate ``lambda_`` and mean ``1 / lambda_``."""
22
22
  assert lambda_ > 0
23
23
  return stats.expon(scale=1 / lambda_)
24
24
 
25
25
 
26
26
  def gamma(r: float, lambda_: float):
27
- """Return a gamma distribution."""
27
+ """Return a gamma distribution with shape ``r`` and rate ``lambda_``."""
28
28
  assert r > 0 and lambda_ > 0
29
29
  return stats.gamma(a=r, loc=0, scale=1 / lambda_)
30
30
 
@@ -32,15 +32,16 @@ def gamma(r: float, lambda_: float):
32
32
  def chi2(n: int | float):
33
33
  """Return a chi-squared distribution with the given number of degrees of freedom.
34
34
 
35
- The chi-squared distribution is generally defined only on the natural numbers, but here being defined as a special case
36
- of the gamma distribution, it generalises easily to positive rational numbers, hence why floats are accepted.
35
+ Although degrees of freedom are commonly given as an integer, the distribution
36
+ is here represented as a special case of the gamma distribution, making it defined
37
+ for any positive real ``n``.
37
38
  """
38
39
  assert n > 0
39
40
  return gamma(n / 2, 1 / 2)
40
41
 
41
42
 
42
43
  def rayleigh():
43
- """Return a rayleigh distribution."""
44
+ """Return a Rayleigh distribution."""
44
45
  return stats.rayleigh()
45
46
 
46
47
 
@@ -52,42 +53,52 @@ def beta(r: float, s: float):
52
53
 
53
54
  # Discrete distributions
54
55
  def bernoulli(p: float):
55
- """Return a Bernoulli distribution."""
56
+ """Return a Bernoulli distribution with success probability ``p``."""
56
57
  assert 0 <= p <= 1
57
58
  return stats.bernoulli(p)
58
59
 
59
60
 
60
61
  def binomial(n: int, p: float):
61
- """Return a binomial distribution."""
62
+ """Return a binomial distribution for ``n`` trials with success probability ``p``."""
62
63
  assert n >= 0 and 0 <= p <= 1
63
64
  return stats.binom(n=n, p=p)
64
65
 
65
66
 
66
67
  def poisson(mu: float):
67
- """Return a Poisson distribution."""
68
+ """Return a Poisson distribution with mean and rate ``mu``."""
68
69
  assert mu >= 0
69
70
  return stats.poisson(mu=mu)
70
71
 
71
72
 
72
73
  def hypergeom(n: int, N: int, G: int): # noqa: N803
73
- """Return a hypergeometric distribution."""
74
+ """Return a hypergeometric distribution.
75
+
76
+ Parameters
77
+ ----------
78
+ n:
79
+ Number of items drawn from the population.
80
+ N:
81
+ Population size.
82
+ G:
83
+ Number of successes in the population.
84
+ """
74
85
  assert N > n > 0 and N >= G > 0 and N > 1
75
86
  return stats.hypergeom(M=N, n=G, N=n)
76
87
 
77
88
 
78
89
  def geom0(p: float):
79
- """Return a geometric distribution defined on {0, 1, ...}."""
90
+ """Return a geometric distribution of failures before the first success, with success probability ``p``."""
80
91
  assert 0 < p <= 1
81
92
  return stats.geom(loc=-1, p=p)
82
93
 
83
94
 
84
95
  def geom1(p: float):
85
- """Return a geometric distribution defined on {1, 2, ...}."""
96
+ """Return a geometric distribution of the trial of the first success, with success probability ``p``."""
86
97
  assert 0 < p <= 1
87
98
  return stats.geom(p=p)
88
99
 
89
100
 
90
101
  def nbinom(r: int, p: float):
91
- """Return a negative binomial distribution."""
102
+ """Return a negative binomial distribution of failures before ``r`` successes, with success probability ``p``."""
92
103
  assert r > 0 and 0 <= p <= 1
93
104
  return stats.nbinom(n=r, p=p)
@@ -4,16 +4,20 @@ from ._distributions import norm
4
4
 
5
5
 
6
6
  def z_score(alpha: float = 0.05, two_sided: bool = True, distribution: Any | None = None) -> float: # pyright: ignore[reportExplicitAny]
7
- """Return the critical value (z score) for a given significance level.
7
+ """Return an upper critical value for a given significance level.
8
8
 
9
9
  Parameters
10
10
  ----------
11
11
  alpha : float, optional
12
12
  Significance level in ``[0, 1]``.
13
13
  two_sided : bool, optional
14
- If ``True``, split ``alpha`` across both tails; otherwise use a single tail.
14
+ If ``True``, use an upper-tail probability of ``alpha / 2``; otherwise use
15
+ ``alpha``. For symmetric distributions this gives the usual two-sided
16
+ cutoff for a distribution symmetric about zero, with the lower cutoff equal
17
+ to the negative of the returned value.
18
+ For asymmetric distributions, the returned value is only the upper cutoff.
15
19
  distribution : Any | None, optional
16
- A frozen scipy distribution to draw the quantile from. Defaults to ``N(0, 1)``,
20
+ A frozen SciPy distribution to draw the quantile from. Defaults to ``N(0, 1)``,
17
21
  in which case the two-sided default returns the familiar ``~1.96``.
18
22
  """
19
23
  if not 0 <= alpha <= 1:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pelutils
3
- Version: 4.2.1
3
+ Version: 4.3.1
4
4
  Summary: The Swiss army knife of Python projects
5
5
  Home-page: https://github.com/peleiden/pelutils
6
6
  Download-URL: https://pypi.org/project/pelutils/
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
20
21
  Requires-Python: >=3.11
21
22
  Description-Content-Type: text/markdown
22
23
  Provides-Extra: dev
@@ -163,10 +164,15 @@ importantly one per thread, as profiling is not thread-safe.
163
164
 
164
165
  ## Serialisation
165
166
 
166
- `UniversalJsonModel` extends `pydantic.BaseModel` with `save`/`load` methods and can
167
- serialise attributes that pydantic cannot — numpy arrays, tensors, and arbitrary
168
- objects are base64-pickled inline, everything else stays plain, human-readable JSON.
169
- Long lists are wrapped to the line-length limit instead of one element per line.
167
+ `UniversalJsonModel` is for storing Pydantic models that contain a mixture of ordinary JSON
168
+ values and Python-specific values. Ordinary values remain JSON, so the file structure can be
169
+ inspected and diffed. Values that JSON cannot represent, such as NumPy arrays, tensors, and
170
+ other Python objects, are stored as base64-encoded pickle payloads.
171
+
172
+ The `save()` and `load()` methods support convient file-based workflows.
173
+ `save()` serialises the model to a JSON file, while `load()` reconstructs the model from one.
174
+ Use `to_json_dict()` and `from_json_dict(...)` when the serialized representation belongs inside
175
+ another structure or is handled by another storage layer.
170
176
 
171
177
  ```py
172
178
  import numpy as np
@@ -192,11 +198,14 @@ result.save("results/run-1.json")
192
198
  result = Result.load("results/run-1.json")
193
199
  ```
194
200
 
195
- Use `to_json_dict()` / `from_json_dict(...)` to convert to and from a plain dict
196
- without touching the filesystem — useful for nesting inside other structures. The
197
- `pretty_json` helper function is also available on its own. The `serialization`
198
- module also includes JSONL read/write helpers (`jsonl_dump`, `jsonl_load`, ...)
199
- with largely the same interface as is provided by the built-in `json` module.
201
+ `pretty_json` is a string-returning formatter for standalone dictionaries and lists. It fills the
202
+ same role as `json.dumps`, with compact nested containers and wrapped primitive lists; write the
203
+ returned string to a file when needed. The module also includes JSONL read/write helpers
204
+ (`jsonl_dump`, `jsonl_load`, ...) for newline-delimited records.
205
+
206
+ This format is intended for trusted configuration objects, application state, experiment results, etc., not as a
207
+ language-neutral interchange format. Pickle payloads are Python-specific and loading them can
208
+ execute arbitrary code. Do not load files from untrusted sources.
200
209
 
201
210
  ## Config and command-line argument parsing
202
211
 
@@ -253,18 +262,18 @@ values, index, inverse, counts = unique(
253
262
 
254
263
  ### Statistics
255
264
 
256
- Common statistical helpers, plus wrappers around scipy distributions reparametrised
257
- as in Jim Pitman's *Probability* (rather than scipy's `loc`/`scale`, which are
265
+ Common statistical helpers, plus wrappers around SciPy distributions reparametrised
266
+ as in Jim Pitman's *Probability* (rather than SciPy's `loc`/`scale`, which are
258
267
  unintuitive for many distributions).
259
268
 
260
269
  ```py
261
- from pelutils.stats import z_score
262
270
  from pelutils.stats import expon
271
+ from pelutils.stats import z_score
263
272
 
264
- # 95 % confidence interval half-width for a standard normal (defaults give ~1.96)
265
- half_width = std * z_score()
273
+ # 95 % confidence interval half-width for a standard normal
274
+ half_width = z_score() # standard deviation is 1
266
275
 
267
- # One-sided z value for an Exponential(λ=2) at the 1 % significance level
276
+ # Upper 1 % critical value for an Exponential(lambda=2)
268
277
  zval = z_score(alpha=0.01, two_sided=False, distribution=expon(lambda_=2))
269
278
  ```
270
279
 
@@ -31,6 +31,9 @@ pelutils/array/_utils.py
31
31
  pelutils/job_parser/__init__.py
32
32
  pelutils/job_parser/_job_parser.py
33
33
  pelutils/job_parser/_structs.py
34
+ pelutils/legacy/__init__.py
35
+ pelutils/legacy/v3/__init__.py
36
+ pelutils/legacy/v3/_datastorage.py
34
37
  pelutils/logging/__init__.py
35
38
  pelutils/logging/_logger.py
36
39
  pelutils/logging/_rich_string.py
@@ -87,6 +87,7 @@ setup_args = dict(
87
87
  "Programming Language :: Python :: 3.12",
88
88
  "Programming Language :: Python :: 3.13",
89
89
  "Programming Language :: Python :: 3.14",
90
+ "Programming Language :: Python :: 3.15",
90
91
  ],
91
92
  )
92
93
 
@@ -1,51 +0,0 @@
1
- """JSON persistence for values that plain ``json`` and even ``pydantic`` cannot handle.
2
-
3
- Persisting data, whatever it may be, to disk usually usually comes down to a choice between
4
- human-readable or not. If efficiency is not crucial, human-readable is preferable, but often
5
- times, the data is not trivially serialisable to human-friendly formats. While ``pydantic``'s
6
- ``BaseModel.model_dump`` takes you some of the way, it falls short as soon as you introduce
7
- data types which are not JSON-serialisable by default, making pickling the default choice and
8
- ending up with an unreadable binary blob.
9
-
10
- Even if all data is easily serialisable to a readable format, you still have to deal with all
11
- the boilerplate of ensuring parent directories exist, and opening and closing files.
12
-
13
- Enter :class:`UniversalJsonModel` — a ``pydantic.BaseModel`` whose
14
- ``save``/``load`` methods serialise/deserialise *any* attribute: JSON-native values stay plain and
15
- human-readable, while anything else (numpy arrays, torch tensors, arbitrary objects) is
16
- pickled and base64-encoded inline. The output is a single, easily readable JSON file.
17
-
18
- Quick start
19
- -----------
20
-
21
- .. code-block:: python
22
-
23
- import numpy as np
24
- from pelutils.serialization import UniversalJsonModel
25
- from pelutils.types import FloatArray
26
-
27
- class Result(UniversalJsonModel):
28
- accuracy: float
29
- predictions: FloatArray # numpy arrays are handled automatically
30
-
31
- result = Result(accuracy=0.97, predictions=np.arange(5, dtype=np.float16))
32
- result.save("results/run-1.json")
33
- result = Result.load("results/run-1.json")
34
-
35
- The module also exposes :func:`pretty_json`, a function similar to the built-in `json.dumps`,
36
- but which formats the given object to a pretty JSON string;
37
- short containers stay on one line, and long primitive lists are packed across both the width and height
38
- of the file.
39
- JSONL helpers (:func:`jsonl_dump`, :func:`jsonl_load`, and their string variants) for files where each
40
- line is its own JSON object are also provided.
41
-
42
- .. warning::
43
-
44
- Loading pickles executes arbitrary code, so never ``load`` data from an untrusted source.
45
- """
46
-
47
- from ._jsonl import jsonl_dump, jsonl_dumps, jsonl_load, jsonl_loads
48
- from ._pretty_json import pretty_json
49
- from ._universal_json_model import UniversalJsonModel
50
-
51
- __all__ = ("UniversalJsonModel", "jsonl_dump", "jsonl_dumps", "jsonl_load", "jsonl_loads", "pretty_json")
File without changes
File without changes
File without changes
File without changes
File without changes