lazyscribe 2.0.0__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.
- lazyscribe-2.0.0/PKG-INFO +136 -0
- lazyscribe-2.0.0/README.md +78 -0
- lazyscribe-2.0.0/lazyscribe/__init__.py +9 -0
- lazyscribe-2.0.0/lazyscribe/_meta.py +3 -0
- lazyscribe-2.0.0/lazyscribe/_utils.py +132 -0
- lazyscribe-2.0.0/lazyscribe/artifacts/__init__.py +51 -0
- lazyscribe-2.0.0/lazyscribe/artifacts/base.py +148 -0
- lazyscribe-2.0.0/lazyscribe/artifacts/json.py +134 -0
- lazyscribe-2.0.0/lazyscribe/artifacts/pickle.py +146 -0
- lazyscribe-2.0.0/lazyscribe/exception.py +33 -0
- lazyscribe-2.0.0/lazyscribe/experiment.py +532 -0
- lazyscribe-2.0.0/lazyscribe/linked.py +120 -0
- lazyscribe-2.0.0/lazyscribe/project.py +431 -0
- lazyscribe-2.0.0/lazyscribe/registry.py +106 -0
- lazyscribe-2.0.0/lazyscribe/release.py +406 -0
- lazyscribe-2.0.0/lazyscribe/repository.py +678 -0
- lazyscribe-2.0.0/lazyscribe/test.py +92 -0
- lazyscribe-2.0.0/pyproject.toml +147 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: lazyscribe
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Lightweight and lazy experiment logging
|
|
5
|
+
Author: Akshay Gupta
|
|
6
|
+
Author-email: Akshay Gupta <akgcodes@gmail.com>
|
|
7
|
+
License: MIT license
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Natural Language :: English
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: attrs>=21.2.0,<=25.4.0
|
|
18
|
+
Requires-Dist: fsspec>=0.4.0,<=2025.12.0
|
|
19
|
+
Requires-Dist: python-slugify>=5.0.0,<=8.0.4
|
|
20
|
+
Requires-Dist: tomli>=2.2.1,<=2.3.0 ; python_full_version < '3.11'
|
|
21
|
+
Requires-Dist: commitizen ; extra == 'build'
|
|
22
|
+
Requires-Dist: uv ; extra == 'build'
|
|
23
|
+
Requires-Dist: lazyscribe[build] ; extra == 'dev'
|
|
24
|
+
Requires-Dist: lazyscribe[docs] ; extra == 'dev'
|
|
25
|
+
Requires-Dist: lazyscribe[qa] ; extra == 'dev'
|
|
26
|
+
Requires-Dist: lazyscribe[tests] ; extra == 'dev'
|
|
27
|
+
Requires-Dist: furo ; extra == 'docs'
|
|
28
|
+
Requires-Dist: matplotlib ; extra == 'docs'
|
|
29
|
+
Requires-Dist: pandas ; extra == 'docs'
|
|
30
|
+
Requires-Dist: pillow ; extra == 'docs'
|
|
31
|
+
Requires-Dist: scikit-learn ; extra == 'docs'
|
|
32
|
+
Requires-Dist: sphinx ; extra == 'docs'
|
|
33
|
+
Requires-Dist: sphinx-gallery ; extra == 'docs'
|
|
34
|
+
Requires-Dist: sphinx-inline-tabs ; extra == 'docs'
|
|
35
|
+
Requires-Dist: time-machine ; extra == 'docs'
|
|
36
|
+
Requires-Dist: edgetest ; extra == 'qa'
|
|
37
|
+
Requires-Dist: edgetest-pip-tools ; extra == 'qa'
|
|
38
|
+
Requires-Dist: mypy ; extra == 'qa'
|
|
39
|
+
Requires-Dist: pip-tools ; extra == 'qa'
|
|
40
|
+
Requires-Dist: pre-commit ; extra == 'qa'
|
|
41
|
+
Requires-Dist: pyproject-fmt ; extra == 'qa'
|
|
42
|
+
Requires-Dist: ruff ; extra == 'qa'
|
|
43
|
+
Requires-Dist: types-python-slugify ; extra == 'qa'
|
|
44
|
+
Requires-Dist: pytest ; extra == 'tests'
|
|
45
|
+
Requires-Dist: pytest-cov ; extra == 'tests'
|
|
46
|
+
Requires-Dist: requests ; extra == 'tests'
|
|
47
|
+
Requires-Dist: scikit-learn ; extra == 'tests'
|
|
48
|
+
Requires-Dist: time-machine ; extra == 'tests'
|
|
49
|
+
Requires-Python: >=3.10.0
|
|
50
|
+
Project-URL: documentation, https://lazyscribe.github.io/lazyscribe/
|
|
51
|
+
Project-URL: repository, https://github.com/lazyscribe/lazyscribe
|
|
52
|
+
Provides-Extra: build
|
|
53
|
+
Provides-Extra: dev
|
|
54
|
+
Provides-Extra: docs
|
|
55
|
+
Provides-Extra: qa
|
|
56
|
+
Provides-Extra: tests
|
|
57
|
+
Description-Content-Type: text/markdown
|
|
58
|
+
|
|
59
|
+
[](LICENSE) [](https://pypi.org/project/lazyscribe/) [](https://pypi.org/project/lazyscribe/) [](https://lazyscribe.github.io/lazyscribe/) [](https://codecov.io/github/lazyscribe/lazyscribe)
|
|
60
|
+
|
|
61
|
+
# Lightweight, lazy experiment logging
|
|
62
|
+
|
|
63
|
+
``lazyscribe`` is a lightweight package for model experiment logging. It creates a single JSON
|
|
64
|
+
file per project, and an experiment is only added to the file when code finishes (errors won't
|
|
65
|
+
result in partially finished experiments in your project log).
|
|
66
|
+
|
|
67
|
+
``lazyscribe`` also has functionality to allow for multiple people to work on a single project.
|
|
68
|
+
You can merge projects together and update the list of experiments to create a single, authoritative
|
|
69
|
+
view of all executed experiments.
|
|
70
|
+
|
|
71
|
+
# Installation
|
|
72
|
+
|
|
73
|
+
Python 3.10 and above is required. Use `pip` to install:
|
|
74
|
+
```console
|
|
75
|
+
$ python -m pip install lazyscribe
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
# Basic Usage
|
|
79
|
+
|
|
80
|
+
The basic usage involves instantiating a ``Project`` and using the context manager to log
|
|
81
|
+
an experiment:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import json
|
|
85
|
+
|
|
86
|
+
from lazyscribe import Project
|
|
87
|
+
|
|
88
|
+
project = Project(fpath="project.json")
|
|
89
|
+
with project.log(name="My experiment") as exp:
|
|
90
|
+
exp.log_metric("auroc", 0.5)
|
|
91
|
+
exp.log_parameter("algorithm", "lightgbm")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
You've created an experiment! You can view the experimental data by using ``list``:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
print(json.dumps(list(project), indent=4))
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
[
|
|
102
|
+
{
|
|
103
|
+
"name": "My experiment",
|
|
104
|
+
"author": "<AUTHOR>",
|
|
105
|
+
"last_updated_by": "<AUTHOR>",
|
|
106
|
+
"metrics": {
|
|
107
|
+
"auroc": 0.5
|
|
108
|
+
},
|
|
109
|
+
"parameters": {
|
|
110
|
+
"algorithm": "lightgbm"
|
|
111
|
+
},
|
|
112
|
+
"created_at": "<CREATED_AT>",
|
|
113
|
+
"last_updated": "<LAST_UPDATED>",
|
|
114
|
+
"dependencies": [],
|
|
115
|
+
"short_slug": "my-experiment",
|
|
116
|
+
"slug": "my-experiment-<CREATED_AT>",
|
|
117
|
+
"tests": [],
|
|
118
|
+
"artifacts": []
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Once you've finished, save the project to ``project.json``:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
project.save()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Later on, you can read the project back in read-only mode (`"r"`), append mode (`"a"`),
|
|
130
|
+
or editable mode (`"w+"`):
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
project = Project("project.json", mode="r")
|
|
134
|
+
with project.log(name="New experiment") as exp: # Raises a ReadOnlyError
|
|
135
|
+
...
|
|
136
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[](LICENSE) [](https://pypi.org/project/lazyscribe/) [](https://pypi.org/project/lazyscribe/) [](https://lazyscribe.github.io/lazyscribe/) [](https://codecov.io/github/lazyscribe/lazyscribe)
|
|
2
|
+
|
|
3
|
+
# Lightweight, lazy experiment logging
|
|
4
|
+
|
|
5
|
+
``lazyscribe`` is a lightweight package for model experiment logging. It creates a single JSON
|
|
6
|
+
file per project, and an experiment is only added to the file when code finishes (errors won't
|
|
7
|
+
result in partially finished experiments in your project log).
|
|
8
|
+
|
|
9
|
+
``lazyscribe`` also has functionality to allow for multiple people to work on a single project.
|
|
10
|
+
You can merge projects together and update the list of experiments to create a single, authoritative
|
|
11
|
+
view of all executed experiments.
|
|
12
|
+
|
|
13
|
+
# Installation
|
|
14
|
+
|
|
15
|
+
Python 3.10 and above is required. Use `pip` to install:
|
|
16
|
+
```console
|
|
17
|
+
$ python -m pip install lazyscribe
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
# Basic Usage
|
|
21
|
+
|
|
22
|
+
The basic usage involves instantiating a ``Project`` and using the context manager to log
|
|
23
|
+
an experiment:
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import json
|
|
27
|
+
|
|
28
|
+
from lazyscribe import Project
|
|
29
|
+
|
|
30
|
+
project = Project(fpath="project.json")
|
|
31
|
+
with project.log(name="My experiment") as exp:
|
|
32
|
+
exp.log_metric("auroc", 0.5)
|
|
33
|
+
exp.log_parameter("algorithm", "lightgbm")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You've created an experiment! You can view the experimental data by using ``list``:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
print(json.dumps(list(project), indent=4))
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
[
|
|
44
|
+
{
|
|
45
|
+
"name": "My experiment",
|
|
46
|
+
"author": "<AUTHOR>",
|
|
47
|
+
"last_updated_by": "<AUTHOR>",
|
|
48
|
+
"metrics": {
|
|
49
|
+
"auroc": 0.5
|
|
50
|
+
},
|
|
51
|
+
"parameters": {
|
|
52
|
+
"algorithm": "lightgbm"
|
|
53
|
+
},
|
|
54
|
+
"created_at": "<CREATED_AT>",
|
|
55
|
+
"last_updated": "<LAST_UPDATED>",
|
|
56
|
+
"dependencies": [],
|
|
57
|
+
"short_slug": "my-experiment",
|
|
58
|
+
"slug": "my-experiment-<CREATED_AT>",
|
|
59
|
+
"tests": [],
|
|
60
|
+
"artifacts": []
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Once you've finished, save the project to ``project.json``:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
project.save()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Later on, you can read the project back in read-only mode (`"r"`), append mode (`"a"`),
|
|
72
|
+
or editable mode (`"w+"`):
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
project = Project("project.json", mode="r")
|
|
76
|
+
with project.log(name="New experiment") as exp: # Raises a ReadOnlyError
|
|
77
|
+
...
|
|
78
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Main module."""
|
|
2
|
+
|
|
3
|
+
from lazyscribe._meta import __version__ # noqa: F401
|
|
4
|
+
from lazyscribe.experiment import Experiment
|
|
5
|
+
from lazyscribe.project import Project
|
|
6
|
+
from lazyscribe.repository import Repository
|
|
7
|
+
from lazyscribe.test import Test
|
|
8
|
+
|
|
9
|
+
__all__: list[str] = ["Experiment", "Project", "Repository", "Test"]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Util methods."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import json
|
|
5
|
+
from collections.abc import Iterator
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from attrs import Attribute, asdict, fields, filters
|
|
10
|
+
|
|
11
|
+
from lazyscribe.artifacts.base import Artifact
|
|
12
|
+
from lazyscribe.exception import ArtifactLoadError
|
|
13
|
+
from lazyscribe.registry import registry
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def serializer(inst: type, field: "Attribute[Any]", value: Any) -> Any:
|
|
17
|
+
"""Datetime and dependencies converter for :meth:`attrs.asdict`.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
inst : type
|
|
22
|
+
Included for compatibility.
|
|
23
|
+
field : attrs.Attribute[Any]
|
|
24
|
+
The field name.
|
|
25
|
+
value : Any
|
|
26
|
+
The field value.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
Any
|
|
31
|
+
Converted value for easy serialization.
|
|
32
|
+
"""
|
|
33
|
+
if isinstance(value, datetime):
|
|
34
|
+
return value.isoformat(timespec="seconds")
|
|
35
|
+
if field is not None and field.name == "dependencies":
|
|
36
|
+
deps: list[str] = []
|
|
37
|
+
for exp in value.values():
|
|
38
|
+
if (project := registry.search(exp.project)) is not None:
|
|
39
|
+
deps.append(f"{project}|{exp.slug}")
|
|
40
|
+
else:
|
|
41
|
+
deps.append(f"{exp.project}|{exp.slug}")
|
|
42
|
+
return deps
|
|
43
|
+
if field is not None and field.name == "tests":
|
|
44
|
+
tests: list[dict[str, Any]] = [asdict(test) for test in value]
|
|
45
|
+
return tests
|
|
46
|
+
if field is not None and field.name == "artifacts":
|
|
47
|
+
art: list[dict[str, Any]] = list(serialize_artifacts(value))
|
|
48
|
+
return art
|
|
49
|
+
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def serialize_artifacts(alist: list[Artifact]) -> Iterator[dict[str, Any]]:
|
|
54
|
+
"""Serialize list of artifacts."""
|
|
55
|
+
yield from (
|
|
56
|
+
{
|
|
57
|
+
**asdict(
|
|
58
|
+
artifact,
|
|
59
|
+
filter=filters.exclude(
|
|
60
|
+
fields(type(artifact)).value,
|
|
61
|
+
fields(type(artifact)).writer_kwargs,
|
|
62
|
+
fields(type(artifact)).dirty,
|
|
63
|
+
),
|
|
64
|
+
value_serializer=lambda _, __, value: value.isoformat(
|
|
65
|
+
timespec="seconds"
|
|
66
|
+
)
|
|
67
|
+
if isinstance(value, datetime)
|
|
68
|
+
else value,
|
|
69
|
+
),
|
|
70
|
+
"handler": artifact.alias,
|
|
71
|
+
}
|
|
72
|
+
for artifact in alist
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def utcnow() -> datetime:
|
|
77
|
+
"""Return the naive datetime now in UTC.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
datetime.datetime
|
|
82
|
+
Now in UTC, without timezone info.
|
|
83
|
+
"""
|
|
84
|
+
return datetime.now(timezone.utc).replace(tzinfo=None)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def validate_artifact_environment(artifact: Artifact) -> None:
|
|
88
|
+
"""Validate the artifact handler environment.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
artifact : Artifact
|
|
93
|
+
An artifact handler instantiated from project and/or repository metadata.
|
|
94
|
+
|
|
95
|
+
Raises
|
|
96
|
+
------
|
|
97
|
+
lazyscribe.exception.ArtifactLoadError
|
|
98
|
+
Raised if the runtime environment does not match artifact metadata.
|
|
99
|
+
"""
|
|
100
|
+
# Construct the handler with relevant parameters.
|
|
101
|
+
artifact_attrs: dict[str, Any] = {
|
|
102
|
+
x: y
|
|
103
|
+
for x, y in inspect.getmembers(artifact)
|
|
104
|
+
if not x.startswith("_") and not inspect.ismethod(y)
|
|
105
|
+
}
|
|
106
|
+
# Exclude parameters that don't define equality
|
|
107
|
+
exclude_names: list[str] = [
|
|
108
|
+
attr.name for attr in fields(type(artifact)) if not attr.eq
|
|
109
|
+
]
|
|
110
|
+
construct_params: list[str] = [
|
|
111
|
+
param_name
|
|
112
|
+
for param_name, param in inspect.signature(
|
|
113
|
+
artifact.construct
|
|
114
|
+
).parameters.items()
|
|
115
|
+
if param_name not in exclude_names or param.default == param.empty
|
|
116
|
+
]
|
|
117
|
+
artifact_attrs = {
|
|
118
|
+
key: value for key, value in artifact_attrs.items() if key in construct_params
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
curr_handler = type(artifact).construct(**artifact_attrs, dirty=False)
|
|
122
|
+
# Validate the handler
|
|
123
|
+
if curr_handler != artifact:
|
|
124
|
+
field_filters = filters.exclude(
|
|
125
|
+
*[attr for attr in fields(type(artifact)) if not attr.eq]
|
|
126
|
+
)
|
|
127
|
+
raise ArtifactLoadError(
|
|
128
|
+
"Runtime environments do not match. Artifact parameters:\n\n"
|
|
129
|
+
f"{json.dumps(asdict(artifact, filter=field_filters))}"
|
|
130
|
+
"\n\nCurrent parameters:\n\n"
|
|
131
|
+
f"{json.dumps(asdict(curr_handler, filter=field_filters))}"
|
|
132
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Import the handlers."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import entry_points
|
|
4
|
+
|
|
5
|
+
from lazyscribe.artifacts.base import Artifact
|
|
6
|
+
|
|
7
|
+
__all__: list[str] = ["_get_handler"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _get_handler(alias: str) -> type[Artifact]:
|
|
11
|
+
"""Retrieve a specific handler based on the alias.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
alias : str
|
|
16
|
+
The alias for the handler.
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
type[lazyscribe.artifacts.base.Artifact]
|
|
21
|
+
The artifact handler class.
|
|
22
|
+
"""
|
|
23
|
+
entry = entry_points(group="lazyscribe.artifact_type")
|
|
24
|
+
|
|
25
|
+
for full_artifact_class in entry: # search through entrypoints first
|
|
26
|
+
if full_artifact_class.name == alias:
|
|
27
|
+
try:
|
|
28
|
+
mod = full_artifact_class.load()
|
|
29
|
+
except ImportError as imp:
|
|
30
|
+
raise ImportError(
|
|
31
|
+
f"Unable to import handler for {alias} through entry points"
|
|
32
|
+
) from imp
|
|
33
|
+
|
|
34
|
+
if not isinstance(mod, type):
|
|
35
|
+
raise TypeError(f"{full_artifact_class} is not a class")
|
|
36
|
+
|
|
37
|
+
break
|
|
38
|
+
|
|
39
|
+
else:
|
|
40
|
+
for obj in Artifact.__subclasses__(): # search through experiment subclasses
|
|
41
|
+
if obj.alias == alias:
|
|
42
|
+
mod = obj
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
# no handler found in both entrypoints or subclass
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"No handler available with the name {alias} in `artifact_type` group."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return mod # type: ignore
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Base class for new artifact handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABCMeta, abstractmethod
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from io import IOBase
|
|
8
|
+
from typing import Any, ClassVar
|
|
9
|
+
|
|
10
|
+
from attrs import define, field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@define
|
|
14
|
+
class Artifact(metaclass=ABCMeta):
|
|
15
|
+
"""Generic artifact handler that defines the expected interface.
|
|
16
|
+
|
|
17
|
+
Artifact handlers are not meant to be initialized directly.
|
|
18
|
+
|
|
19
|
+
Attributes
|
|
20
|
+
----------
|
|
21
|
+
alias : str
|
|
22
|
+
The alias for the artifact handler. This value will be supplied to
|
|
23
|
+
:py:meth:`lazyscribe.experiment.Experiment.log_artifact`.
|
|
24
|
+
(A class attribute.)
|
|
25
|
+
suffix : str
|
|
26
|
+
The standard suffix for the files written and read by this handler.
|
|
27
|
+
(A class attribute.)
|
|
28
|
+
binary : bool
|
|
29
|
+
Whether or not the file format for the handler is binary in nature. This
|
|
30
|
+
affects whether or not the file handler uses ``w`` or ``wb``.
|
|
31
|
+
(A class attribute.)
|
|
32
|
+
output_only : bool
|
|
33
|
+
Whether or not the file output by the handler is meant to be read as the orginal project.
|
|
34
|
+
(A class attribute.)
|
|
35
|
+
name : str
|
|
36
|
+
The name of the artifact.
|
|
37
|
+
fname : str
|
|
38
|
+
The filename of the artifact.
|
|
39
|
+
value : Any
|
|
40
|
+
The value for the artifact.
|
|
41
|
+
writer_kwargs : dict
|
|
42
|
+
User provided keyword arguments for writing an artifact. Provided when
|
|
43
|
+
the artifact is logged to an experiment.
|
|
44
|
+
version : int
|
|
45
|
+
Version of the artifact.
|
|
46
|
+
dirty : bool
|
|
47
|
+
Whether or not this artifact should be saved when :py:meth:`lazyscribe.project.Project.save`
|
|
48
|
+
or :py:meth:`lazyscribe.repository.Repository.save` is called. This decision is based
|
|
49
|
+
on whether the artifact is new or has been updated.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
alias: ClassVar[str]
|
|
53
|
+
suffix: ClassVar[str]
|
|
54
|
+
binary: ClassVar[bool]
|
|
55
|
+
output_only: ClassVar[
|
|
56
|
+
bool
|
|
57
|
+
] # Describes if the artifact will reconstruct to a Python object on read
|
|
58
|
+
name: str = field(eq=False)
|
|
59
|
+
fname: str = field(eq=False)
|
|
60
|
+
value: Any = field(eq=False)
|
|
61
|
+
writer_kwargs: dict[str, Any] = field(eq=False)
|
|
62
|
+
created_at: datetime = field(eq=False)
|
|
63
|
+
expiry: datetime | None = field(eq=False)
|
|
64
|
+
version: int = field(eq=False)
|
|
65
|
+
dirty: bool = field(eq=False)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def construct(
|
|
70
|
+
cls,
|
|
71
|
+
name: str,
|
|
72
|
+
value: Any = None,
|
|
73
|
+
fname: str | None = None,
|
|
74
|
+
created_at: datetime | None = None,
|
|
75
|
+
expiry: datetime | None = None,
|
|
76
|
+
writer_kwargs: dict[str, Any] | None = None,
|
|
77
|
+
version: int = 0,
|
|
78
|
+
dirty: bool = True,
|
|
79
|
+
**kwargs: Any,
|
|
80
|
+
) -> Artifact:
|
|
81
|
+
"""Construct the artifact handler.
|
|
82
|
+
|
|
83
|
+
This method should use environment variables to capture information that
|
|
84
|
+
is relevant to compatibility between runtime environments.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
name : str
|
|
89
|
+
The name of the artifact.
|
|
90
|
+
value : Any, optional (default None)
|
|
91
|
+
The value for the artifact.
|
|
92
|
+
fname : str, optional (default None)
|
|
93
|
+
The filename for the artifact. If set to ``None`` or not provided, it will be derived from
|
|
94
|
+
the name of the artifact and the suffix for the class.
|
|
95
|
+
created_at : datetime.datetime, optional (default ``lazyscribe._utils.utcnow()``)
|
|
96
|
+
When the artifact was created.
|
|
97
|
+
expiry : datetime.datetime, optional (default None)
|
|
98
|
+
When the artifact expired.
|
|
99
|
+
writer_kwargs : dict, optional (default {})
|
|
100
|
+
Keyword arguments for writing an artifact to the filesystem. Provided when an artifact
|
|
101
|
+
is logged to an experiment.
|
|
102
|
+
version : int, optional (default 0)
|
|
103
|
+
Integer version to be used for versioning artifacts.
|
|
104
|
+
dirty : bool, optional (default True)
|
|
105
|
+
Whether or not this artifact should be saved when :py:meth:`lazyscribe.project.Project.save`
|
|
106
|
+
or :py:meth:`lazyscribe.repository.Repository.save` is called. This decision is based
|
|
107
|
+
on whether the artifact is new or has been updated.
|
|
108
|
+
**kwargs
|
|
109
|
+
Other keyword arguments.
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
Artifact
|
|
114
|
+
The artifact.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def read(cls, buf: IOBase, **kwargs: Any) -> Any:
|
|
120
|
+
"""Read in the artifact.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
buf : file-like object
|
|
125
|
+
The buffer from a ``fsspec`` filesystem.
|
|
126
|
+
**kwargs
|
|
127
|
+
Keyword arguments for the read method.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
Any
|
|
132
|
+
The artifact object.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
@abstractmethod
|
|
137
|
+
def write(cls, obj: Any, buf: IOBase, **kwargs: Any) -> None:
|
|
138
|
+
"""Write the artifact to the filesystem.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
obj : Any
|
|
143
|
+
The object to write to the buffer.
|
|
144
|
+
buf : file-like object
|
|
145
|
+
The buffer from a ``fsspec`` filesystem.
|
|
146
|
+
**kwargs
|
|
147
|
+
Keyword arguments for the write method.
|
|
148
|
+
"""
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Artifact handler for JSON-serializable objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from io import IOBase
|
|
7
|
+
from json import dump, load
|
|
8
|
+
from typing import Any, ClassVar
|
|
9
|
+
|
|
10
|
+
from attrs import define
|
|
11
|
+
from slugify import slugify
|
|
12
|
+
|
|
13
|
+
from lazyscribe._utils import utcnow
|
|
14
|
+
from lazyscribe.artifacts.base import Artifact
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@define(auto_attribs=True)
|
|
18
|
+
class JSONArtifact(Artifact):
|
|
19
|
+
"""Handler for JSON-serializable objects.
|
|
20
|
+
|
|
21
|
+
.. important::
|
|
22
|
+
|
|
23
|
+
This class is not meant to be initialized directly. Please use the ``construct``
|
|
24
|
+
method.
|
|
25
|
+
|
|
26
|
+
.. note::
|
|
27
|
+
|
|
28
|
+
For the attributes documentation, see also "Attributes" of :py:class:`lazyscribe.artifacts.base.Artifact`.
|
|
29
|
+
|
|
30
|
+
Attributes
|
|
31
|
+
----------
|
|
32
|
+
alias : str = "json"
|
|
33
|
+
suffix : str = "json"
|
|
34
|
+
binary : bool = False
|
|
35
|
+
output_only : bool = False
|
|
36
|
+
|
|
37
|
+
python_version : str
|
|
38
|
+
Minor Python version (e.g. ``"3.10"``).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
alias: ClassVar[str] = "json"
|
|
42
|
+
suffix: ClassVar[str] = "json"
|
|
43
|
+
binary: ClassVar[bool] = False
|
|
44
|
+
output_only: ClassVar[bool] = False
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def construct(
|
|
48
|
+
cls,
|
|
49
|
+
name: str,
|
|
50
|
+
value: Any = None,
|
|
51
|
+
fname: str | None = None,
|
|
52
|
+
created_at: datetime | None = None,
|
|
53
|
+
expiry: datetime | None = None,
|
|
54
|
+
writer_kwargs: dict[str, Any] | None = None,
|
|
55
|
+
version: int = 0,
|
|
56
|
+
dirty: bool = True,
|
|
57
|
+
**kwargs: Any,
|
|
58
|
+
) -> JSONArtifact:
|
|
59
|
+
"""Construct the handler class.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
name : str
|
|
64
|
+
The name of the artifact.
|
|
65
|
+
value : Any, optional (default None)
|
|
66
|
+
The value for the artifact. The default value of ``None`` is used when
|
|
67
|
+
an experiment is loaded from the project JSON.
|
|
68
|
+
fname : str, optional (default None)
|
|
69
|
+
The filename for the artifact. If set to ``None`` or not provided, it will be derived from
|
|
70
|
+
the name of the artifact and the suffix for the class.
|
|
71
|
+
created_at : datetime.datetime, optional (default ``lazyscribe._utils.utcnow()``)
|
|
72
|
+
When the artifact was created.
|
|
73
|
+
expiry : datetime.datetime, optional (default None)
|
|
74
|
+
When the artifact expired.
|
|
75
|
+
writer_kwargs : dict[str, Any], optional (default {})
|
|
76
|
+
Keyword arguments for writing an artifact to the filesystem. Provided when an artifact
|
|
77
|
+
is logged to an experiment.
|
|
78
|
+
version : int, optional (default 0)
|
|
79
|
+
Integer version to be used for versioning artifacts.
|
|
80
|
+
dirty : bool, optional (default True)
|
|
81
|
+
Whether or not this artifact should be saved when :py:meth:`lazyscribe.project.Project.save`
|
|
82
|
+
or :py:meth:`lazyscribe.repository.Repository.save` is called. This decision is based
|
|
83
|
+
on whether the artifact is new or has been updated.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
JSONArtifact
|
|
88
|
+
The artifact.
|
|
89
|
+
"""
|
|
90
|
+
created_at = created_at or utcnow()
|
|
91
|
+
return cls(
|
|
92
|
+
name=name,
|
|
93
|
+
value=value,
|
|
94
|
+
fname=fname
|
|
95
|
+
or f"{slugify(name)}-{slugify(created_at.strftime('%Y%m%d%H%M%S'))}.{cls.suffix}",
|
|
96
|
+
created_at=created_at,
|
|
97
|
+
expiry=expiry,
|
|
98
|
+
writer_kwargs=writer_kwargs or {},
|
|
99
|
+
version=version,
|
|
100
|
+
dirty=dirty,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def read(cls, buf: IOBase, **kwargs: Any) -> Any:
|
|
105
|
+
"""Read in the JSON file.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
buf : file-like object
|
|
110
|
+
The buffer from a ``fsspec`` filesystem.
|
|
111
|
+
**kwargs
|
|
112
|
+
Keyword arguments for :py:meth:`json.load`
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
Any
|
|
117
|
+
The deserialized JSON file.
|
|
118
|
+
"""
|
|
119
|
+
return load(buf, **kwargs)
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def write(cls, obj: Any, buf: IOBase, **kwargs: Any) -> None:
|
|
123
|
+
"""Write the content to a JSON file.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
obj : object
|
|
128
|
+
The JSON-serializable object.
|
|
129
|
+
buf : file-like object
|
|
130
|
+
The buffer from a ``fsspec`` filesystem.
|
|
131
|
+
**kwargs
|
|
132
|
+
Keyword arguments for :py:meth:`json.dump`.
|
|
133
|
+
"""
|
|
134
|
+
dump(obj, buf, **kwargs)
|