data-annotations 2.1.2__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.
- data_annotations-2.1.2/LICENSE +28 -0
- data_annotations-2.1.2/PKG-INFO +616 -0
- data_annotations-2.1.2/README.md +587 -0
- data_annotations-2.1.2/pyproject.toml +49 -0
- data_annotations-2.1.2/src/data_annotations/__init__.py +2 -0
- data_annotations-2.1.2/src/data_annotations/_decorators.py +140 -0
- data_annotations-2.1.2/src/data_annotations/annotations/__init__.py +30 -0
- data_annotations-2.1.2/src/data_annotations/annotations/decorators.py +147 -0
- data_annotations-2.1.2/src/data_annotations/annotations/models.py +45 -0
- data_annotations-2.1.2/src/data_annotations/annotations/writers.py +368 -0
- data_annotations-2.1.2/src/data_annotations/cli.py +37 -0
- data_annotations-2.1.2/src/data_annotations/cli_app/__init__.py +1 -0
- data_annotations-2.1.2/src/data_annotations/cli_app/annotate.py +307 -0
- data_annotations-2.1.2/src/data_annotations/cli_app/common.py +276 -0
- data_annotations-2.1.2/src/data_annotations/cli_app/prompts.py +534 -0
- data_annotations-2.1.2/src/data_annotations/cli_app/provenance_commands.py +107 -0
- data_annotations-2.1.2/src/data_annotations/description/__init__.py +37 -0
- data_annotations-2.1.2/src/data_annotations/description/decorators.py +145 -0
- data_annotations-2.1.2/src/data_annotations/description/models.py +63 -0
- data_annotations-2.1.2/src/data_annotations/description/writers.py +321 -0
- data_annotations-2.1.2/src/data_annotations/provenance/__init__.py +37 -0
- data_annotations-2.1.2/src/data_annotations/provenance/decorators.py +111 -0
- data_annotations-2.1.2/src/data_annotations/provenance/git.py +121 -0
- data_annotations-2.1.2/src/data_annotations/provenance/models.py +50 -0
- data_annotations-2.1.2/src/data_annotations/provenance/recovery.py +473 -0
- data_annotations-2.1.2/src/data_annotations/provenance/runtime.py +248 -0
- data_annotations-2.1.2/src/data_annotations/provenance/writers.py +206 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, CeDA, University of Basel
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: data-annotations
|
|
3
|
+
Version: 2.1.2
|
|
4
|
+
Summary: Annotate generated data artifacts
|
|
5
|
+
Keywords: annotations,data,metadata,provenance,reproducibility
|
|
6
|
+
Author: Rodrigo C. G. Pena
|
|
7
|
+
Author-email: Rodrigo C. G. Pena <rodrigo.cerqueiragonzalezpena@unibas.ch>
|
|
8
|
+
License-Expression: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Dist: pydantic>=2.13.1
|
|
21
|
+
Requires-Dist: questionary>=2.1.1 ; extra == 'cli'
|
|
22
|
+
Requires-Dist: typer>=0.16.0 ; extra == 'cli'
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Project-URL: Source, https://gitlab.com/ceda-unibas/tools/data-annotations
|
|
25
|
+
Project-URL: Changelog, https://gitlab.com/ceda-unibas/tools/data-annotations/-/blob/main/CHANGELOG.md
|
|
26
|
+
Project-URL: Issues, https://gitlab.com/ceda-unibas/tools/data-annotations/-/issues
|
|
27
|
+
Provides-Extra: cli
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# data-annotations
|
|
31
|
+
|
|
32
|
+
A small Python package for attaching provenance and structured descriptions to the
|
|
33
|
+
files and directories your workflows produce.
|
|
34
|
+
|
|
35
|
+
It is designed for lightweight research and reproducibility pipelines where you want
|
|
36
|
+
generated datasets, tables, plots, or reports to carry enough context to explain
|
|
37
|
+
where they came from and what they contain.
|
|
38
|
+
|
|
39
|
+
The package captures common provenance automatically and writes plain JSON and
|
|
40
|
+
Markdown artifacts that are easy to inspect or archive. The canonical on-disk format
|
|
41
|
+
is now a single annotation document:
|
|
42
|
+
|
|
43
|
+
- Files use `artifact.ext.meta.json`
|
|
44
|
+
- Directories use `manifest.json`
|
|
45
|
+
|
|
46
|
+
Each annotation document stores four top-level sections:
|
|
47
|
+
|
|
48
|
+
- `annotation_version`
|
|
49
|
+
- `subject`
|
|
50
|
+
- `provenance`
|
|
51
|
+
- `description`
|
|
52
|
+
|
|
53
|
+
See the [changelog](CHANGELOG.md) for release history and upgrade-oriented notes.
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
Install the core library from PyPI with `pip`:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install data-annotations
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or add it to a project with [uv](https://astral.sh/uv/):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uv add data-annotations
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The command-line interface uses optional dependencies. Install the package with
|
|
70
|
+
CLI support when you want to run `data-annotations` commands:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install "data-annotations[cli]"
|
|
74
|
+
uv add "data-annotations[cli]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
For development or unreleased source installs, install directly from GitLab:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv add "data-annotations @ git+https://gitlab.com/ceda-unibas/tools/data-annotations.git"
|
|
81
|
+
pip install "data-annotations @ git+https://gitlab.com/ceda-unibas/tools/data-annotations.git"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Pin a source install to a particular release tag `x.y.z` with:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv add "data-annotations @ git+https://gitlab.com/ceda-unibas/tools/data-annotations.git@x.y.z"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## What gets captured automatically
|
|
91
|
+
|
|
92
|
+
Every annotation document includes provenance with:
|
|
93
|
+
|
|
94
|
+
- A UTC creation timestamp
|
|
95
|
+
- Hostname and username
|
|
96
|
+
- The script path and command-line arguments
|
|
97
|
+
- The script path relative to the Git repo root when it can be determined
|
|
98
|
+
- Git commit, branch, dirty state, and canonical repository remote when available
|
|
99
|
+
- The current `SLURM_JOB_ID` when available
|
|
100
|
+
|
|
101
|
+
You can also attach your own parameters, input file paths, and function names.
|
|
102
|
+
Local filesystem paths in provenance are stored as absolute paths. URI-style inputs
|
|
103
|
+
such as `s3://...` or `https://...` are preserved as provided.
|
|
104
|
+
|
|
105
|
+
## Quick Start
|
|
106
|
+
|
|
107
|
+
The recommended way to annotate your data artifacts is to decorate pipeline
|
|
108
|
+
functions that consume some inputs and parameters, then write those artifacts.
|
|
109
|
+
This keeps the artifact-writing logic explicit while letting `data-annotations` capture
|
|
110
|
+
provenance and emit sidecars automatically.
|
|
111
|
+
|
|
112
|
+
For example, here is a complete file-level annotation workflow using the
|
|
113
|
+
`record_file_annotation(...)` decorator. Once `write_participants` is called, it
|
|
114
|
+
automatically generates sidecars `participants.csv.meta.json` and `participants.csv.README.md`.
|
|
115
|
+
The JSON sidecar will contain provenance and description metadata, and the Markdown sidecar
|
|
116
|
+
will have a human-friendly rendering of the description provided in the decorator.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from pathlib import Path
|
|
120
|
+
|
|
121
|
+
from data_annotations.annotations import record_file_annotation
|
|
122
|
+
from data_annotations.description import AllowedValue, FieldDefinition
|
|
123
|
+
|
|
124
|
+
@record_file_annotation(
|
|
125
|
+
title="Participant Cohort",
|
|
126
|
+
summary="Participant-level cohort assignments for the validation split.",
|
|
127
|
+
fields=[
|
|
128
|
+
FieldDefinition(
|
|
129
|
+
name="participant_id",
|
|
130
|
+
data_type="string",
|
|
131
|
+
summary="Stable participant identifier.",
|
|
132
|
+
required=True,
|
|
133
|
+
nullable=False,
|
|
134
|
+
),
|
|
135
|
+
FieldDefinition(
|
|
136
|
+
name="group",
|
|
137
|
+
data_type="string",
|
|
138
|
+
summary="Assigned study group.",
|
|
139
|
+
allowed_values=[
|
|
140
|
+
AllowedValue(value="control"),
|
|
141
|
+
AllowedValue(value="treatment"),
|
|
142
|
+
],
|
|
143
|
+
),
|
|
144
|
+
],
|
|
145
|
+
primary_key=["participant_id"],
|
|
146
|
+
artifact_kind="dataset",
|
|
147
|
+
acquisition_context={"source": "Study A registry export"},
|
|
148
|
+
generation_context={"pipeline": "baseline-v1"},
|
|
149
|
+
)
|
|
150
|
+
def write_participants(
|
|
151
|
+
artifact_path: Path,
|
|
152
|
+
input_path: Path,
|
|
153
|
+
split: str,
|
|
154
|
+
) -> Path:
|
|
155
|
+
participant_ids = [
|
|
156
|
+
line.strip()
|
|
157
|
+
for line in input_path.read_text(encoding="utf-8").splitlines()[1:]
|
|
158
|
+
if line.strip()
|
|
159
|
+
]
|
|
160
|
+
artifact_path.parent.mkdir(parents=True, exist_ok=True)
|
|
161
|
+
artifact_path.write_text(
|
|
162
|
+
"\n".join(
|
|
163
|
+
[
|
|
164
|
+
"participant_id,group,split",
|
|
165
|
+
*[
|
|
166
|
+
f"{participant_id},control,{split}"
|
|
167
|
+
for participant_id in participant_ids
|
|
168
|
+
],
|
|
169
|
+
]
|
|
170
|
+
)
|
|
171
|
+
+ "\n",
|
|
172
|
+
encoding="utf-8",
|
|
173
|
+
)
|
|
174
|
+
return artifact_path
|
|
175
|
+
|
|
176
|
+
# Annotation sidecars are written automatically
|
|
177
|
+
# when the decorated function is called:
|
|
178
|
+
artifact_path = Path("outputs") / "participants.csv"
|
|
179
|
+
write_participants(
|
|
180
|
+
artifact_path=artifact_path,
|
|
181
|
+
input_path=Path("data/raw/participants.csv"),
|
|
182
|
+
split="validation",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
print(f"{artifact_path}.meta.json")
|
|
186
|
+
print(f"{artifact_path}.README.md")
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Decorator Contract
|
|
190
|
+
|
|
191
|
+
You write a normal Python function and the decorator returns that function's
|
|
192
|
+
original return value unchanged.
|
|
193
|
+
|
|
194
|
+
For provenance-bearing decorators, recorded inputs are inferred from named
|
|
195
|
+
function arguments such as `input_path` and `input_paths`. Those arguments
|
|
196
|
+
should correspond to real data dependencies used inside the wrapped function.
|
|
197
|
+
|
|
198
|
+
For file decorators:
|
|
199
|
+
|
|
200
|
+
- `record_file_manifest(...)`
|
|
201
|
+
- `record_file_annotation(...)`
|
|
202
|
+
- `record_file_description(...)`
|
|
203
|
+
|
|
204
|
+
Your function should:
|
|
205
|
+
|
|
206
|
+
- accept one argument pointing at the output file path. By default this argument
|
|
207
|
+
is named `artifact_path`, but you can change the expected name with
|
|
208
|
+
`artifact_path_arg=...`.
|
|
209
|
+
- use any other normal Python arguments you need for the pipeline step.
|
|
210
|
+
- for provenance-bearing decorators, use argument names listed in `input_args`
|
|
211
|
+
for real upstream dependencies you want recorded as provenance inputs. By
|
|
212
|
+
default those names are `("input_path", "input_paths")`.
|
|
213
|
+
|
|
214
|
+
Your function may return any value. File decorators do not inspect that return
|
|
215
|
+
value. Returning the generated `artifact_path` is recommended because it is
|
|
216
|
+
convenient for callers, but it is not required.
|
|
217
|
+
|
|
218
|
+
For directory decorators:
|
|
219
|
+
|
|
220
|
+
- `record_directory_manifest(...)`
|
|
221
|
+
- `record_directory_annotation(...)`
|
|
222
|
+
- `record_directory_description(...)`
|
|
223
|
+
|
|
224
|
+
Your function should:
|
|
225
|
+
|
|
226
|
+
- accept one argument pointing at the output directory. By default this argument
|
|
227
|
+
is named `output_dir`, but you can change the expected name with
|
|
228
|
+
`output_dir_arg=...`.
|
|
229
|
+
- return a materialized iterable, usually a `list`, describing the files that
|
|
230
|
+
were produced in that directory.
|
|
231
|
+
- prefer returning a `list` or `tuple` rather than a generator, since the
|
|
232
|
+
decorator needs to iterate over the outputs to write sidecars.
|
|
233
|
+
|
|
234
|
+
Accepted directory return items are:
|
|
235
|
+
|
|
236
|
+
- `DocumentedArtifact` when you want per-artifact title, summary, fields,
|
|
237
|
+
keys, or missing-value metadata.
|
|
238
|
+
- `ProducedFile` when you only need path, kind, and optional precomputed hash.
|
|
239
|
+
- `(path, kind)` tuples when path and artifact kind are enough.
|
|
240
|
+
- plain path-like values when the artifact kind can default to `"other"`.
|
|
241
|
+
|
|
242
|
+
For provenance-bearing directory decorators, `input_args` works the same way as
|
|
243
|
+
for file decorators: matching argument names are recorded as inputs, and the
|
|
244
|
+
remaining bound arguments become provenance params.
|
|
245
|
+
|
|
246
|
+
Here is another decorator pattern example with `record_directory_annotation(...)`:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from pathlib import Path
|
|
250
|
+
|
|
251
|
+
from data_annotations.annotations import record_directory_annotation
|
|
252
|
+
from data_annotations.description import DocumentedArtifact, FieldDefinition
|
|
253
|
+
from data_annotations.provenance import ProducedFile
|
|
254
|
+
|
|
255
|
+
@record_directory_annotation(
|
|
256
|
+
title="Validation Outputs",
|
|
257
|
+
summary="Directory-level documentation for the validation run outputs.",
|
|
258
|
+
acquisition_context={"source": "Study A registry export"},
|
|
259
|
+
generation_context={"pipeline": "baseline-v1"},
|
|
260
|
+
)
|
|
261
|
+
def build_outputs(
|
|
262
|
+
output_dir: Path,
|
|
263
|
+
input_path: Path,
|
|
264
|
+
split: str,
|
|
265
|
+
):
|
|
266
|
+
participant_ids = [
|
|
267
|
+
line.strip()
|
|
268
|
+
for line in input_path.read_text(encoding="utf-8").splitlines()[1:]
|
|
269
|
+
if line.strip()
|
|
270
|
+
]
|
|
271
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
272
|
+
|
|
273
|
+
table_path = output_dir / "scores.csv"
|
|
274
|
+
table_path.write_text(
|
|
275
|
+
"\n".join(
|
|
276
|
+
[
|
|
277
|
+
"participant_id,score,split",
|
|
278
|
+
*[
|
|
279
|
+
f"{participant_id},0.94,{split}"
|
|
280
|
+
for participant_id in participant_ids
|
|
281
|
+
],
|
|
282
|
+
]
|
|
283
|
+
)
|
|
284
|
+
+ "\n",
|
|
285
|
+
encoding="utf-8",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
report_path = output_dir / "summary.txt"
|
|
289
|
+
report_path.write_text(
|
|
290
|
+
(
|
|
291
|
+
f"Validated {len(participant_ids)} participants from "
|
|
292
|
+
f"{input_path.name} for the {split} split.\n"
|
|
293
|
+
),
|
|
294
|
+
encoding="utf-8",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
plot_path = output_dir / "roc.png"
|
|
298
|
+
plot_path.write_bytes(
|
|
299
|
+
(
|
|
300
|
+
f"plot placeholder derived from {input_path.name} "
|
|
301
|
+
f"({len(participant_ids)} participants)\n"
|
|
302
|
+
).encode("utf-8")
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return [
|
|
306
|
+
DocumentedArtifact(
|
|
307
|
+
path=str(table_path),
|
|
308
|
+
kind="dataset",
|
|
309
|
+
title="Metrics Table",
|
|
310
|
+
fields=[
|
|
311
|
+
FieldDefinition(
|
|
312
|
+
name="metric",
|
|
313
|
+
data_type="string",
|
|
314
|
+
summary="Metric name.",
|
|
315
|
+
),
|
|
316
|
+
FieldDefinition(
|
|
317
|
+
name="value",
|
|
318
|
+
data_type="float",
|
|
319
|
+
summary="Metric value.",
|
|
320
|
+
),
|
|
321
|
+
],
|
|
322
|
+
),
|
|
323
|
+
ProducedFile(path=str(report_path), kind="report"),
|
|
324
|
+
(plot_path, "plot"),
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
output_dir = Path("outputs") / "run-001"
|
|
329
|
+
build_outputs(
|
|
330
|
+
output_dir=output_dir,
|
|
331
|
+
input_path=Path("data/raw/participants.csv"),
|
|
332
|
+
split="validation",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
print(output_dir / "manifest.json")
|
|
336
|
+
print(output_dir / "README.md")
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
The decorator and direct APIs write the same canonical document shape. If you need
|
|
340
|
+
metadata to vary per call instead of staying fixed at decoration time, use
|
|
341
|
+
`annotate_file(...)`, `annotate_directory(...)`, `write_file_annotation(...)`, or
|
|
342
|
+
`write_directory_annotation(...)` directly instead. See the example gallery in
|
|
343
|
+
`examples/` for runnable examples of all approaches.
|
|
344
|
+
|
|
345
|
+
### When To Use Decorators Vs Direct Functions
|
|
346
|
+
|
|
347
|
+
If a function is only a final serializer for already-prepared data, prefer the
|
|
348
|
+
direct annotation and writer APIs. They let you attach `inputs=[...]` explicitly.
|
|
349
|
+
|
|
350
|
+
## Canonical Document Shape
|
|
351
|
+
|
|
352
|
+
File annotations store:
|
|
353
|
+
|
|
354
|
+
- `subject.path`
|
|
355
|
+
- `subject.kind`
|
|
356
|
+
- `subject.sha256`
|
|
357
|
+
- `provenance.*`
|
|
358
|
+
- `description.title`
|
|
359
|
+
- `description.summary`
|
|
360
|
+
- `description.fields`
|
|
361
|
+
- `description.primary_key`
|
|
362
|
+
- `description.missing_value_codes`
|
|
363
|
+
- `description.acquisition_context`
|
|
364
|
+
- `description.generation_context`
|
|
365
|
+
- `description.description_updated_at`
|
|
366
|
+
|
|
367
|
+
Directory annotations store:
|
|
368
|
+
|
|
369
|
+
- `subject.path`
|
|
370
|
+
- `subject.produced_files[]`
|
|
371
|
+
- `provenance.*`
|
|
372
|
+
- `description.title`
|
|
373
|
+
- `description.summary`
|
|
374
|
+
- `description.artifacts[]`
|
|
375
|
+
- `description.acquisition_context`
|
|
376
|
+
- `description.generation_context`
|
|
377
|
+
- `description.description_updated_at`
|
|
378
|
+
|
|
379
|
+
The `description` section intentionally excludes provenance linkage fields and
|
|
380
|
+
file kinds for directory artifacts. Kinds live in `subject.produced_files`.
|
|
381
|
+
|
|
382
|
+
## Provenance Decorators And Writers
|
|
383
|
+
|
|
384
|
+
The `data_annotations.provenance` namespace provides provenance-only entry points.
|
|
385
|
+
Prefer the decorators when you already have a small function that writes artifacts:
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from pathlib import Path
|
|
389
|
+
|
|
390
|
+
from data_annotations.provenance import record_file_manifest
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@record_file_manifest(artifact_kind="report")
|
|
394
|
+
def write_report(
|
|
395
|
+
artifact_path: Path,
|
|
396
|
+
input_path: Path,
|
|
397
|
+
threshold: float = 0.5,
|
|
398
|
+
):
|
|
399
|
+
artifact_path.parent.mkdir(parents=True, exist_ok=True)
|
|
400
|
+
artifact_path.write_text(
|
|
401
|
+
f"threshold applied: {threshold}\nsource={input_path.name}\n",
|
|
402
|
+
encoding="utf-8",
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
write_report(
|
|
407
|
+
artifact_path=Path("outputs/summary.txt"),
|
|
408
|
+
input_path=Path("data/raw/participants.csv"),
|
|
409
|
+
threshold=0.75,
|
|
410
|
+
)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Use `record_directory_manifest(...)` for directory outputs. Directory decorators
|
|
414
|
+
accept `DocumentedArtifact`, `ProducedFile`, `(path, kind)`, and plain path-like
|
|
415
|
+
return values.
|
|
416
|
+
|
|
417
|
+
If you want the direct writer approach instead, use `write_file_manifest(...)` and
|
|
418
|
+
`write_directory_manifest(...)` (see `examples/`).
|
|
419
|
+
|
|
420
|
+
## Description Layer
|
|
421
|
+
|
|
422
|
+
The `data_annotations.description` sub-package provides the structured description
|
|
423
|
+
models used by annotation writers and the Markdown sidecar renderers.
|
|
424
|
+
Within those models, the primary human-written narrative field is named `summary`.
|
|
425
|
+
|
|
426
|
+
Key public description models:
|
|
427
|
+
|
|
428
|
+
- `AllowedValue`
|
|
429
|
+
- `FieldDefinition`
|
|
430
|
+
- `DocumentedArtifact`
|
|
431
|
+
- `ArtifactDescription`
|
|
432
|
+
- `FileDescription`
|
|
433
|
+
- `DirectoryDescription`
|
|
434
|
+
|
|
435
|
+
Description decorators and helpers:
|
|
436
|
+
|
|
437
|
+
- `record_file_description(...)`
|
|
438
|
+
- `record_directory_description(...)`
|
|
439
|
+
- `write_file_description(...)`
|
|
440
|
+
- `write_directory_description(...)`
|
|
441
|
+
- `render_file_readme(...)`
|
|
442
|
+
- `render_directory_readme(...)`
|
|
443
|
+
|
|
444
|
+
Alias helpers `write_file_readme(...)` and `write_directory_readme(...)` are supported.
|
|
445
|
+
|
|
446
|
+
Use the decorator forms when the description metadata is stable
|
|
447
|
+
for a function, and use the direct helpers when you want to assemble descriptions
|
|
448
|
+
per call.
|
|
449
|
+
|
|
450
|
+
## Recovery Helpers
|
|
451
|
+
|
|
452
|
+
Use `artifact_matches_manifest(...)` to verify whether a detached artifact still
|
|
453
|
+
matches an annotation document, and `checkout_manifest_source(...)` to recover the
|
|
454
|
+
recorded code state from Git metadata.
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
from pathlib import Path
|
|
458
|
+
|
|
459
|
+
from data_annotations.provenance import (
|
|
460
|
+
artifact_matches_manifest,
|
|
461
|
+
checkout_manifest_source,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
annotation_path = Path("outputs/participants.csv.meta.json")
|
|
465
|
+
artifact_path = Path("downloads/participants.csv")
|
|
466
|
+
|
|
467
|
+
if artifact_matches_manifest(artifact_path, annotation_path):
|
|
468
|
+
recovered = checkout_manifest_source(annotation_path)
|
|
469
|
+
print(recovered.checkout_path)
|
|
470
|
+
print(recovered.script_path)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Post-Hoc Annotation
|
|
474
|
+
|
|
475
|
+
The strongest workflow is to create provenance and description at the same time
|
|
476
|
+
as the artifact itself. When annotations are written during generation, the
|
|
477
|
+
package can capture runtime context directly and the resulting records are
|
|
478
|
+
typically more complete, precise, and trustworthy.
|
|
479
|
+
|
|
480
|
+
For existing artifacts, the CLI provides a post-hoc annotation path so you can
|
|
481
|
+
still attach provenance and description after the fact.
|
|
482
|
+
|
|
483
|
+
Post-hoc descriptions can still be very useful, but the quality of post-hoc
|
|
484
|
+
provenance depends on how exact the supplied answers are. In particular, fields
|
|
485
|
+
such as the generating script, command, function, Git commit, repository path,
|
|
486
|
+
inputs, and parameters are only as reliable as the information entered during
|
|
487
|
+
annotation.
|
|
488
|
+
|
|
489
|
+
## CLI Workflow
|
|
490
|
+
|
|
491
|
+
This package provides a command-line interface (CLI) for retrospective annotation
|
|
492
|
+
and provenance inspection.
|
|
493
|
+
|
|
494
|
+
For post-hoc annotation:
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
data-annotations annotate file path/to/participants.csv
|
|
498
|
+
data-annotations annotate directory path/to/run-001
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
These commands prompt for missing details, write `*.meta.json` or `manifest.json`,
|
|
502
|
+
and optionally derive README sidecars. Post-hoc records are marked with
|
|
503
|
+
`capture_mode="post_hoc"`.
|
|
504
|
+
|
|
505
|
+
For provenance inspection and source recovery:
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
data-annotations provenance match path/to/artifact
|
|
509
|
+
data-annotations provenance checkout path/to/artifact
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Command `match` auto-discovers `*.meta.json` for files and `manifest.json` for
|
|
513
|
+
directories, prints a verification summary, and suggests the exact `checkout`
|
|
514
|
+
command to run next when Git recovery metadata is available.
|
|
515
|
+
|
|
516
|
+
### Run With `uvx`
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
uvx --from "data-annotations[cli]" data-annotations provenance match path/to/participants.csv
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Install And Use With `uv tool`
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
uv tool install "data-annotations[cli]"
|
|
526
|
+
data-annotations provenance match path/to/participants.csv
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Run From Repository Root
|
|
530
|
+
|
|
531
|
+
From the repository root while developing locally, run `task install` first.
|
|
532
|
+
That task uses `uv sync --extra cli`, so the CLI commands are available in
|
|
533
|
+
the project environment. You can then run:
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
uv run data-annotations annotate file path/to/participants.csv
|
|
537
|
+
uv run data-annotations annotate directory path/to/run-001
|
|
538
|
+
uv run data-annotations provenance match path/to/participants.csv
|
|
539
|
+
uv run data-annotations provenance checkout path/to/participants.csv
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
## API Overview
|
|
543
|
+
|
|
544
|
+
### Annotation Models
|
|
545
|
+
|
|
546
|
+
- `FileArtifactSubject`
|
|
547
|
+
- `DirectoryArtifactSubject`
|
|
548
|
+
- `FileAnnotationDocument`
|
|
549
|
+
- `DirectoryAnnotationDocument`
|
|
550
|
+
- `FileAnnotationResult`
|
|
551
|
+
- `DirectoryAnnotationResult`
|
|
552
|
+
|
|
553
|
+
### Annotation Decorators
|
|
554
|
+
|
|
555
|
+
- `record_file_annotation(...)`
|
|
556
|
+
- `record_directory_annotation(...)`
|
|
557
|
+
|
|
558
|
+
### Annotation Functions
|
|
559
|
+
|
|
560
|
+
- `write_file_annotation(...)`
|
|
561
|
+
- `write_directory_annotation(...)`
|
|
562
|
+
- `annotate_file(...)`
|
|
563
|
+
- `annotate_directory(...)`
|
|
564
|
+
|
|
565
|
+
### Description Functions
|
|
566
|
+
|
|
567
|
+
- `record_file_description(...)`
|
|
568
|
+
- `record_directory_description(...)`
|
|
569
|
+
- `write_file_description(...)`
|
|
570
|
+
- `write_directory_description(...)`
|
|
571
|
+
- `write_file_readme(...)`
|
|
572
|
+
- `write_directory_readme(...)`
|
|
573
|
+
- `render_file_readme(...)`
|
|
574
|
+
- `render_directory_readme(...)`
|
|
575
|
+
|
|
576
|
+
### Provenance Models
|
|
577
|
+
|
|
578
|
+
- `ProducedFile`
|
|
579
|
+
- `BaseProvenance`
|
|
580
|
+
- `FileManifest`
|
|
581
|
+
- `DirectoryManifest`
|
|
582
|
+
- `RecoveredSource`
|
|
583
|
+
|
|
584
|
+
### Provenance Functions
|
|
585
|
+
|
|
586
|
+
- `record_file_manifest(...)`
|
|
587
|
+
- `record_directory_manifest(...)`
|
|
588
|
+
- `write_file_manifest(...)`
|
|
589
|
+
- `write_directory_manifest(...)`
|
|
590
|
+
- `artifact_matches_manifest(...)`
|
|
591
|
+
- `checkout_manifest_source(...)`
|
|
592
|
+
|
|
593
|
+
## Examples
|
|
594
|
+
|
|
595
|
+
Runnable examples live in `examples/` and mirror the README workflows.
|
|
596
|
+
Run them from the repository root with:
|
|
597
|
+
|
|
598
|
+
```bash
|
|
599
|
+
uv run python examples/record_file_annotation.py
|
|
600
|
+
uv run python examples/record_directory_annotation.py
|
|
601
|
+
uv run python examples/record_file_manifest.py
|
|
602
|
+
uv run python examples/record_directory_manifest.py
|
|
603
|
+
uv run python examples/record_file_description.py
|
|
604
|
+
uv run python examples/record_directory_description.py
|
|
605
|
+
uv run python examples/annotate_file.py
|
|
606
|
+
uv run python examples/annotate_directory.py
|
|
607
|
+
uv run python examples/write_file_manifest.py
|
|
608
|
+
uv run python examples/write_directory_manifest.py
|
|
609
|
+
uv run python examples/write_file_description.py
|
|
610
|
+
uv run python examples/write_directory_description.py
|
|
611
|
+
uv run python examples/recover_provenance.py
|
|
612
|
+
uv run python examples/recover_provenance_cli.py
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
Each example writes its outputs to a fresh temporary directory and prints the
|
|
616
|
+
location so you can inspect the generated annotation documents and README sidecars.
|