lazyscribe 2.2.2__tar.gz → 2.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lazyscribe
3
- Version: 2.2.2
3
+ Version: 2.3.0
4
4
  Summary: Lightweight and lazy experiment logging
5
5
  Keywords: experiment,machine learning,model
6
6
  Author: Akshay Gupta
@@ -25,7 +25,7 @@ Classifier: Topic :: Software Development :: Build Tools
25
25
  Classifier: Topic :: Software Development :: Documentation
26
26
  Requires-Dist: attrs>=21.2.0,<=26.1.0
27
27
  Requires-Dist: fsspec>=0.4.0,<=2026.9.0
28
- Requires-Dist: python-slugify>=5.0.0,<=9.1.2
28
+ Requires-Dist: python-slugify>=5.0.0,<=9.1.3
29
29
  Requires-Dist: tomli>=2.2.1,<=2.3.0 ; python_full_version < '3.11'
30
30
  Requires-Dist: commitizen ; extra == 'build'
31
31
  Requires-Dist: uv ; extra == 'build'
@@ -0,0 +1,3 @@
1
+ """Version."""
2
+
3
+ __version__ = "2.3.0"
@@ -29,6 +29,128 @@ else:
29
29
  LOG = logging.getLogger(__name__)
30
30
 
31
31
 
32
+ @define(auto_attribs=True)
33
+ class ReleaseDiff:
34
+ """Dataclass for holding the difference between two release objects.
35
+
36
+ In particular, this class will be used to generate human-readable reports on the differences
37
+ between two releases.
38
+
39
+ Parameters
40
+ ----------
41
+ tags : tuple[str, str]
42
+ Tags of the releases being compared.
43
+ identical : list[str]
44
+ The list of artifact-version combinations that are identical between the releases.
45
+ modified : list[tuple[str, int, int]]
46
+ A list of artifacts that exist in both releases, just with different versions.
47
+ The second entry in each tuple is the version of the artifact present in the release
48
+ corresponding to the first entry of ``tags``. The second entry in each tuple is the
49
+ version corresponding to the second entry.
50
+ unique : tuple[list[tuple[str, int]], list[tuple[str, int]]]
51
+ A tuple of artifact-versions that exist only in the respective releases.
52
+ """
53
+
54
+ tags: tuple[str, str]
55
+ identical: list[str]
56
+ modified: list[tuple[str, int, int]]
57
+ unique: tuple[list[tuple[str, int]], list[tuple[str, int]]]
58
+
59
+ def report(self, repository: Repository) -> str:
60
+ """Generate a human-readable report describing the differences between releases.
61
+
62
+ Parameters
63
+ ----------
64
+ repository : lazyscribe.repository.Repository
65
+ The repository that was used to generate the releases.
66
+
67
+ Returns
68
+ -------
69
+ str
70
+ A markdown-formatted string.
71
+ """
72
+ first_section_: str = f"# {self.tags[0]} vs. {self.tags[1]}\n\n"
73
+ if self.identical:
74
+ first_section_ += (
75
+ f"The following artifacts are identical: {', '.join(self.identical)}\n"
76
+ )
77
+ if self.unique[0]:
78
+ first_section_ += f"The following artifacts exist only in {self.tags[0]}: {', '.join(str(i) for i, _ in self.unique[0])}\n"
79
+ if self.unique[1]:
80
+ first_section_ += f"The following artifacts exist only in {self.tags[1]}: {', '.join(str(i) for i, _ in self.unique[1])}\n"
81
+ binary_arts_: list[tuple[str, int, int]] = []
82
+ art_diffs_: list[tuple[str, int, int, str]] = []
83
+ for art, ref, other in self.modified:
84
+ if repository._search_artifact_versions(art, ref).binary:
85
+ binary_arts_.append((art, ref, other))
86
+ continue
87
+ if repository._search_artifact_versions(art, other).binary:
88
+ binary_arts_.append((art, ref, other))
89
+ continue
90
+ art_diffs_.append(
91
+ (art, ref, other, repository.get_version_diff(art, (ref, other)))
92
+ )
93
+
94
+ second_section_: str = ""
95
+ if art_diffs_ or binary_arts_:
96
+ second_section_ += (
97
+ f"## Artifact comparison\n\nThis section contains artifacts that exist "
98
+ f"in both {self.tags[0]} and {self.tags[1]} with different versions.\n"
99
+ )
100
+ if binary_arts_:
101
+ # Find the maximum character length for each column
102
+ artifact_name_length = max(
103
+ [len("Artifact"), *(len(name) for name, _, _ in binary_arts_)]
104
+ )
105
+ ref_version_length = max(
106
+ [len(self.tags[0]), *(len(str(ver)) for _, ver, _ in binary_arts_)]
107
+ )
108
+ other_version_length = max(
109
+ [len(self.tags[1]), *(len(str(ver)) for _, _, ver in binary_arts_)]
110
+ )
111
+
112
+ def _tbl_line(x: str, y: str, z: str) -> str:
113
+ return (
114
+ "| "
115
+ + " | ".join(
116
+ [
117
+ x.ljust(artifact_name_length),
118
+ y.ljust(ref_version_length),
119
+ z.ljust(other_version_length),
120
+ ]
121
+ )
122
+ + " |\n"
123
+ )
124
+
125
+ tbl_ = (
126
+ _tbl_line("Artifact", self.tags[0], self.tags[1])
127
+ + "| "
128
+ + " | ".join(
129
+ [
130
+ "".join(["-"] * artifact_name_length),
131
+ "".join(["-"] * ref_version_length),
132
+ "".join(["-"] * other_version_length),
133
+ ]
134
+ )
135
+ + " |\n"
136
+ )
137
+ for art, ref, other in binary_arts_:
138
+ tbl_ += _tbl_line(art, str(ref), str(other))
139
+
140
+ second_section_ += f"The following artifacts are binary:\n\n{tbl_}\n\n"
141
+ if art_diffs_:
142
+ second_section_ += (
143
+ "The following artifacts can be compared using ``difflib``.\n\n"
144
+ )
145
+ for art, ref, other, diff in art_diffs_:
146
+ second_section_ += (
147
+ f"### {art}\n\nVersion {ref} in {self.tags[0]} and {other} in {self.tags[1]}.\n\n"
148
+ f"```diff\n{diff}\n```\n"
149
+ )
150
+
151
+ return first_section_ + "\n" + second_section_
152
+
153
+
32
154
  @define
33
155
  class Release:
34
156
  """Create a release associated with a Repository instance.
@@ -82,6 +204,55 @@ class Release:
82
204
  created_at=datetime.fromisoformat(info["created_at"]),
83
205
  )
84
206
 
207
+ def __sub__(self, other: Release) -> ReleaseDiff:
208
+ """Compare two releases.
209
+
210
+ The output dataclass can be used to generate reporting.
211
+
212
+ Parameters
213
+ ----------
214
+ other : lazyscribe.release.Release
215
+ The other release object.
216
+
217
+ Returns
218
+ -------
219
+ lazyscribe.release.ReleaseDiff
220
+ A lightweight dataclass for reporting the differences between
221
+ :py:class:`lazyscribe.release.Release` objects.
222
+ """
223
+ identical_: set[str] = set()
224
+ modified_: set[tuple[str, int, int]] = set()
225
+ unique_: set[tuple[str, int]] = set()
226
+
227
+ other_names_: list[str] = [art[0] for art in other.artifacts]
228
+ for art, version in self.artifacts:
229
+ try:
230
+ other_art_: tuple[str, int] = other.artifacts[other_names_.index(art)]
231
+ if other_art_[1] == version:
232
+ identical_.add(art)
233
+ else:
234
+ modified_.add((art, version, other_art_[1]))
235
+ except ValueError:
236
+ unique_.add((art, version))
237
+
238
+ other_unique_: set[tuple[str, int]] = set()
239
+ for art, version in other.artifacts:
240
+ if art in identical_:
241
+ continue
242
+ if art in {i[0] for i in modified_}:
243
+ continue
244
+ other_unique_.add((art, version))
245
+
246
+ return ReleaseDiff(
247
+ tags=(self.tag, other.tag),
248
+ identical=sorted(identical_),
249
+ modified=sorted(modified_, key=lambda x: x[0]),
250
+ unique=(
251
+ sorted(unique_, key=lambda x: x[0]),
252
+ sorted(other_unique_, key=lambda x: x[0]),
253
+ ),
254
+ )
255
+
85
256
 
86
257
  def create_release(repository: Repository, tag: str) -> Release:
87
258
  """Create a release.
@@ -113,8 +284,12 @@ def create_release(repository: Repository, tag: str) -> Release:
113
284
  all_artifacts_ = sorted({art.name for art in repository.artifacts})
114
285
  latest: list[tuple[str, int]] = []
115
286
  for name in all_artifacts_:
116
- art = repository._search_artifact_versions(name)
117
- latest.append((name, art.version))
287
+ try:
288
+ art = repository._search_artifact_versions(name)
289
+ latest.append((name, art.version))
290
+ except VersionNotFoundError:
291
+ LOG.info("Artifact '%s' has no valid versions available", name)
292
+ continue
118
293
 
119
294
  return Release(tag=tag, artifacts=latest)
120
295
 
@@ -112,8 +112,12 @@ class Repository:
112
112
  for artifact in data:
113
113
  handler_cls = _get_handler(artifact.pop("handler"))
114
114
  created_at = datetime.fromisoformat(artifact.pop("created_at"))
115
+ if (expiry := artifact.pop("expiry", None)) is not None:
116
+ expiry = datetime.fromisoformat(expiry)
115
117
  artifacts.append(
116
- handler_cls.construct(**artifact, created_at=created_at, dirty=False)
118
+ handler_cls.construct(
119
+ **artifact, created_at=created_at, expiry=expiry, dirty=False
120
+ )
117
121
  )
118
122
  self.artifacts = artifacts
119
123
 
@@ -4,7 +4,7 @@ requires = [ "uv-build>=0.8.0,<=0.9.5" ]
4
4
 
5
5
  [project]
6
6
  name = "lazyscribe"
7
- version = "2.2.2"
7
+ version = "2.3.0"
8
8
  description = "Lightweight and lazy experiment logging"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  keywords = [ "experiment", "machine learning", "model" ]
@@ -35,7 +35,7 @@ classifiers = [
35
35
  dependencies = [
36
36
  "attrs<=26.1.0,>=21.2.0",
37
37
  "fsspec<=2026.9.0,>=0.4.0",
38
- "python-slugify<=9.1.2,>=5.0.0",
38
+ "python-slugify<=9.1.3,>=5.0.0",
39
39
  "tomli<=2.3.0,>=2.2.1; python_version<='3.10'",
40
40
  ]
41
41
  optional-dependencies.build = [ "commitizen", "uv" ]
@@ -119,34 +119,34 @@ lint.pydocstyle.convention = "numpy"
119
119
  lint.preview = true
120
120
 
121
121
  [tool.pyproject-fmt]
122
- column_width = 88
122
+ column_width = 99
123
123
  indent = 4
124
124
  keep_full_version = true
125
125
  max_supported_python = "3.14"
126
126
 
127
- [tool.coverage]
128
- run.relative_files = true
129
-
130
127
  [tool.mypy]
131
- python_version = "3.10"
132
128
  exclude = [
133
129
  "^docs\\/",
134
130
  "^tests\\/",
135
131
  ]
136
- strict = true
132
+ ignore_missing_imports = true
133
+ python_version = "3.10"
137
134
  warn_return_any = true
135
+ strict = true
138
136
  warn_unused_configs = true
139
- ignore_missing_imports = true
137
+
138
+ [tool.coverage]
139
+ run.relative_files = true
140
140
 
141
141
  [tool.commitizen]
142
142
  version_provider = "uv"
143
- tag_format = "v$version"
144
- bump_message = "chore: version $current_version -> $new_version"
145
- update_changelog_on_bump = true
146
143
  version_files = [
147
- "lazyscribe/_meta.py:__version__",
148
144
  "docs/conf.py:version",
145
+ "lazyscribe/_meta.py:__version__",
149
146
  ]
147
+ bump_message = "chore: version $current_version -> $new_version"
148
+ tag_format = "v$version"
149
+ update_changelog_on_bump = true
150
150
 
151
151
  [tool.edgetest]
152
152
  exclude_newer = "3 days"
@@ -1,3 +0,0 @@
1
- """Version."""
2
-
3
- __version__ = "2.2.2"
File without changes