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.
@@ -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://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![PyPI](https://img.shields.io/pypi/v/lazyscribe)](https://pypi.org/project/lazyscribe/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/lazyscribe)](https://pypi.org/project/lazyscribe/) [![Documentation Status](https://github.com/lazyscribe/lazyscribe/actions/workflows/docs.yml/badge.svg)](https://lazyscribe.github.io/lazyscribe/) [![codecov](https://codecov.io/github/lazyscribe/lazyscribe/branch/main/graph/badge.svg?token=M5BHYS2SSU)](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://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![PyPI](https://img.shields.io/pypi/v/lazyscribe)](https://pypi.org/project/lazyscribe/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/lazyscribe)](https://pypi.org/project/lazyscribe/) [![Documentation Status](https://github.com/lazyscribe/lazyscribe/actions/workflows/docs.yml/badge.svg)](https://lazyscribe.github.io/lazyscribe/) [![codecov](https://codecov.io/github/lazyscribe/lazyscribe/branch/main/graph/badge.svg?token=M5BHYS2SSU)](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,3 @@
1
+ """Version."""
2
+
3
+ __version__ = "2.0.0"
@@ -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)