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.
- {pelutils-4.2.1 → pelutils-4.3.1}/PKG-INFO +25 -16
- {pelutils-4.2.1 → pelutils-4.3.1}/README.md +23 -15
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/__version__.py +1 -1
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/array/_blob.py +21 -0
- pelutils-4.3.1/pelutils/legacy/v3/__init__.py +10 -0
- pelutils-4.3.1/pelutils/legacy/v3/_datastorage.py +150 -0
- pelutils-4.3.1/pelutils/py.typed +0 -0
- pelutils-4.3.1/pelutils/serialization/__init__.py +51 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/stats/__init__.py +7 -7
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/stats/_distributions.py +26 -15
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/stats/_stats.py +7 -3
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/PKG-INFO +25 -16
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/SOURCES.txt +3 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/setup.py +1 -0
- pelutils-4.2.1/pelutils/serialization/__init__.py +0 -51
- {pelutils-4.2.1 → pelutils-4.3.1}/MANIFEST.in +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/array/blob.c +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/array/blob.h +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/array/unique.c +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/array/unique.h +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/deque/deque.c +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/deque/deque.h +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/hashmap/LICENSE +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/hashmap/hashmap.c +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/hashmap/hashmap.h +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/pelutils.c +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_c/types.h +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_entry_points/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/_entry_points/linecounter.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/array/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/array/_unique.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/array/_utils.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/job_parser/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/job_parser/_job_parser.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/job_parser/_structs.py +0 -0
- /pelutils-4.2.1/pelutils/py.typed → /pelutils-4.3.1/pelutils/legacy/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/logging/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/logging/_logger.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/logging/_rich_string.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/logging/_rotate.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_conditional_import.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_files.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_git.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_misc.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_platform.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/misc/_table.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/plots/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/plots/_figure.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/plots/_histogram.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/plots/_utils.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/serialization/_jsonl.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/serialization/_pretty_json.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/serialization/_universal_json_model.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/tests/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/tests/_tests.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/ticktock/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/ticktock/_ticktock.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/types/__init__.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils/types/_types.py +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/dependency_links.txt +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/entry_points.txt +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/requires.txt +0 -0
- {pelutils-4.2.1 → pelutils-4.3.1}/pelutils.egg-info/top_level.txt +0 -0
- {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.
|
|
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`
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
257
|
-
as in Jim Pitman's *Probability* (rather than
|
|
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
|
|
265
|
-
half_width =
|
|
273
|
+
# 95 % confidence interval half-width for a standard normal
|
|
274
|
+
half_width = z_score() # standard deviation is 1
|
|
266
275
|
|
|
267
|
-
#
|
|
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`
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
232
|
-
as in Jim Pitman's *Probability* (rather than
|
|
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
|
|
240
|
-
half_width =
|
|
247
|
+
# 95 % confidence interval half-width for a standard normal
|
|
248
|
+
half_width = z_score() # standard deviation is 1
|
|
241
249
|
|
|
242
|
-
#
|
|
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
|
+
__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
|
|
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
|
|
8
|
-
the conventions in Jim Pitman's *Probability* — and returns an ordinary frozen
|
|
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
|
|
20
|
+
dist.cdf(1.5) # a plain frozen SciPy distribution
|
|
21
21
|
|
|
22
|
-
# 95 % confidence-interval half-width for a standard normal
|
|
23
|
-
half_width =
|
|
22
|
+
# 95 % confidence-interval half-width for a standard normal
|
|
23
|
+
half_width = z_score() # standard deviation is 1
|
|
24
24
|
|
|
25
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
36
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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``,
|
|
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
|
|
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.
|
|
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`
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
257
|
-
as in Jim Pitman's *Probability* (rather than
|
|
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
|
|
265
|
-
half_width =
|
|
273
|
+
# 95 % confidence interval half-width for a standard normal
|
|
274
|
+
half_width = z_score() # standard deviation is 1
|
|
266
275
|
|
|
267
|
-
#
|
|
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
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|