makeprov 0.4.6__tar.gz → 0.7.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.
- makeprov-0.7.0/LICENSE +21 -0
- makeprov-0.7.0/PKG-INFO +454 -0
- makeprov-0.7.0/README.md +417 -0
- makeprov-0.7.0/pyproject.toml +55 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov/__init__.py +5 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov/config.py +59 -2
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov/context.jsonld +7 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov/core.py +100 -8
- makeprov-0.7.0/src/makeprov/forges.py +106 -0
- makeprov-0.7.0/src/makeprov/forges.toml +35 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov/paths.py +75 -3
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov/prov.py +338 -87
- makeprov-0.7.0/src/makeprov/refs.py +133 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov/snakemake.py +148 -14
- makeprov-0.7.0/src/makeprov.egg-info/PKG-INFO +454 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov.egg-info/SOURCES.txt +7 -0
- makeprov-0.7.0/src/makeprov.egg-info/entry_points.txt +2 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov.egg-info/requires.txt +8 -6
- makeprov-0.7.0/tests/test_forges.py +109 -0
- makeprov-0.7.0/tests/test_jsonld_refs.py +33 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/tests/test_makeprov.py +117 -0
- makeprov-0.7.0/tests/test_model_0_7.py +413 -0
- makeprov-0.7.0/tests/test_prov_context.py +118 -0
- makeprov-0.7.0/tests/test_snakemake.py +336 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/tests/test_span_and_cached_download.py +54 -1
- makeprov-0.4.6/PKG-INFO +0 -250
- makeprov-0.4.6/README.md +0 -220
- makeprov-0.4.6/pyproject.toml +0 -51
- makeprov-0.4.6/src/makeprov.egg-info/PKG-INFO +0 -250
- makeprov-0.4.6/tests/test_jsonld_refs.py +0 -31
- makeprov-0.4.6/tests/test_prov_context.py +0 -39
- makeprov-0.4.6/tests/test_snakemake.py +0 -121
- {makeprov-0.4.6 → makeprov-0.7.0}/setup.cfg +0 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov/rdfmixin.py +0 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov/span.py +0 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov.egg-info/dependency_links.txt +0 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/src/makeprov.egg-info/top_level.txt +0 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/tests/test_env_identity.py +0 -0
- {makeprov-0.4.6 → makeprov-0.7.0}/tests/test_prov_shacl.py +0 -0
makeprov-0.7.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Benno Kruit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
makeprov-0.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: makeprov
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: A PROV/JSON-LD provenance tracking library for Python scripts, with an optional Snakemake bridge
|
|
5
|
+
Author-email: Benno Kruit <b.b.kruit@amsterdamumc.nl>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bennokr/makeprov
|
|
8
|
+
Project-URL: Documentation, https://bennokr.github.io/makeprov
|
|
9
|
+
Project-URL: Issue Tracker, https://github.com/bennokr/makeprov/issues
|
|
10
|
+
Keywords: provenance,prov,rdf,json-ld,snakemake
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: parse>=1.20
|
|
20
|
+
Provides-Extra: rdf
|
|
21
|
+
Requires-Dist: rdflib>=6.0; extra == "rdf"
|
|
22
|
+
Requires-Dist: pyshacl>=0.20; extra == "rdf"
|
|
23
|
+
Provides-Extra: snakemake
|
|
24
|
+
Requires-Dist: snakemake; extra == "snakemake"
|
|
25
|
+
Provides-Extra: cli
|
|
26
|
+
Requires-Dist: defopt>=6; extra == "cli"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest; extra == "dev"
|
|
29
|
+
Requires-Dist: makeprov[cli,rdf]; extra == "dev"
|
|
30
|
+
Provides-Extra: docs
|
|
31
|
+
Requires-Dist: sphinx>=7; extra == "docs"
|
|
32
|
+
Requires-Dist: myst-parser[linkify]; extra == "docs"
|
|
33
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
34
|
+
Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
|
|
35
|
+
Requires-Dist: makeprov[cli]; extra == "docs"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# makeprov: Pythonic Provenance Tracking
|
|
39
|
+
|
|
40
|
+
`makeprov` is a small library for recording W3C PROV/JSON-LD provenance
|
|
41
|
+
around Python functions that read and write files: which inputs produced
|
|
42
|
+
which outputs, when, with what code and environment. A decorator wraps a
|
|
43
|
+
function, tracks the files it declares as inputs/outputs, and writes a
|
|
44
|
+
provenance record after each call. A minimal `make`-style dependency
|
|
45
|
+
resolver and an optional Snakemake bridge are included, but the core
|
|
46
|
+
contract of the library is the provenance record — not workflow
|
|
47
|
+
orchestration, which tools like Snakemake already do well.
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- Decorator-based rules that infer dependencies from `InPath`/`OutPath`
|
|
52
|
+
parameters and write a PROV/JSON-LD record after every call.
|
|
53
|
+
- A clean `Plan → Run → Artifact` model: the script at a commit is a
|
|
54
|
+
`prov:Plan`, the runtime and the user are agents, and the two are tied
|
|
55
|
+
together by `prov:qualifiedAssociation`/`prov:hadPlan`.
|
|
56
|
+
- `ArtifactRef` lets a run cite external entities — a dataset IRI, an
|
|
57
|
+
object-store key, a model checkpoint — without makeprov copying their metadata.
|
|
58
|
+
- Provenance write failures are fatal by default (`ProvenanceConfig(strict=True)`),
|
|
59
|
+
so a rule can't silently "succeed" with no record of what it did.
|
|
60
|
+
- Resolve templated targets (``results/{sample}.txt``) via ``parse``-style patterns,
|
|
61
|
+
and a small dependency resolver (`build`/`build_all`) for chaining rules.
|
|
62
|
+
- Serialize provenance as JSON-LD, or as RDF/TriG when `rdflib` is installed
|
|
63
|
+
(`pip install "makeprov[rdf]"`).
|
|
64
|
+
- Optional Snakemake bridge that turns `--d3dag` and `--detailed-summary`
|
|
65
|
+
output into PROV JSON-LD artifacts ready for inclusion in Snakemake HTML reports.
|
|
66
|
+
|
|
67
|
+
## The provenance model
|
|
68
|
+
|
|
69
|
+
makeprov keeps PROV's distinction between the *plan* (the recipe) and the
|
|
70
|
+
*agent* (whoever carried it out):
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
run.py @ git SHA a prov:Plan, schema:SoftwareSourceCode
|
|
74
|
+
CPython 3.11 a prov:Agent, prov:SoftwareAgent
|
|
75
|
+
you (opt-in) a prov:Agent, schema:Person
|
|
76
|
+
|
|
77
|
+
train-20260910T…-c2f6dc7b a prov:Activity
|
|
78
|
+
prov:used dataset-X, the Python environment
|
|
79
|
+
prov:wasAssociatedWith runtime, person
|
|
80
|
+
prov:qualifiedAssociation [ prov:agent person ; prov:hadPlan run.py ]
|
|
81
|
+
|
|
82
|
+
results/model.txt a prov:Entity
|
|
83
|
+
prov:wasGeneratedBy train-20260910T…-c2f6dc7b
|
|
84
|
+
dct:identifier sha256:…
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Keeping the plan and the agent apart is what makes the graph mappable onto
|
|
88
|
+
[Workflow Run RO-Crate](https://www.researchobject.org/workflow-run-crate/),
|
|
89
|
+
whose `instrument` (the software that was run) and `agent` (a Person or
|
|
90
|
+
Organization) are separate slots:
|
|
91
|
+
|
|
92
|
+
| makeprov / PROV-O | Process Run Crate |
|
|
93
|
+
| ------------------------ | ----------------- |
|
|
94
|
+
| `prov:Plan` | `instrument` |
|
|
95
|
+
| `prov:Activity` | `CreateAction` |
|
|
96
|
+
| `prov:used` | `object` |
|
|
97
|
+
| `prov:wasGeneratedBy` | `result` |
|
|
98
|
+
| `schema:Person` agent | `agent` |
|
|
99
|
+
| `startedAtTime`/`endedAtTime` | `startTime`/`endTime` |
|
|
100
|
+
|
|
101
|
+
The `schema:Person` agent is **off by default**: provenance documents are
|
|
102
|
+
routinely committed and published, and a name and email address are personal
|
|
103
|
+
data you should choose to publish rather than emit by accident. Turn it on with
|
|
104
|
+
`ProvenanceConfig(record_user=True)`, or `--record-user` on the Snakemake
|
|
105
|
+
bridge. Without it, the qualified association names the runtime as the
|
|
106
|
+
responsible agent.
|
|
107
|
+
|
|
108
|
+
Note that WRROC is Schema.org-native and defines no normative PROV-O mapping;
|
|
109
|
+
the table above is a practical alignment, not an OWL equivalence. makeprov's
|
|
110
|
+
own vocabulary stays `prov:`/`schema:` — RO-Crate and OpenLineage are intended
|
|
111
|
+
as adapters over this model rather than changes to it.
|
|
112
|
+
|
|
113
|
+
### Planned structure vs. observed execution
|
|
114
|
+
|
|
115
|
+
By default a document is purely *retrospective*: it records the activities that
|
|
116
|
+
ran. A rule that was already up to date contributes nothing, because asserting
|
|
117
|
+
an execution that did not happen would be worse than saying nothing.
|
|
118
|
+
|
|
119
|
+
Set `emit_plan_graph = true` (or pass `--plan-graph`) to additionally emit the
|
|
120
|
+
*prospective* structure — each rule as a `prov:Plan` in its own right, linked to
|
|
121
|
+
the plans it depends on by `dct:requires`:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
run.py#rule-transform a prov:Plan
|
|
125
|
+
dct:requires run.py#rule-extract
|
|
126
|
+
dct:source run.py
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The activity's `prov:hadPlan` then points at the specific rule rather than the
|
|
130
|
+
whole script. The Snakemake bridge does the same, collapsing the job DAG's
|
|
131
|
+
edges to rule-level `dct:requires` edges.
|
|
132
|
+
|
|
133
|
+
### Forge profiles
|
|
134
|
+
|
|
135
|
+
When no `base_iri` is set, makeprov derives one from the git remote. The
|
|
136
|
+
supported hosts are declared in [`forges.toml`](src/makeprov/forges.toml) —
|
|
137
|
+
GitHub, GitLab, Bitbucket, Forgejo/Gitea/Codeberg and SourceHut — each giving
|
|
138
|
+
the permalink layout for that host:
|
|
139
|
+
|
|
140
|
+
```toml
|
|
141
|
+
[[forge]]
|
|
142
|
+
name = "gitlab"
|
|
143
|
+
hosts = ["gitlab.com"]
|
|
144
|
+
blob = "{repo}/-/blob/{revision}/"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Point `forge_profiles` at your own TOML file to add self-hosted instances;
|
|
148
|
+
entries there are matched first, so they can also override a built-in host.
|
|
149
|
+
SSH and scp-style remotes (`git@host:owner/repo.git`) are understood, and any
|
|
150
|
+
credentials embedded in a remote URL are stripped before it reaches a document.
|
|
151
|
+
|
|
152
|
+
## Referencing things that aren't local files
|
|
153
|
+
|
|
154
|
+
`ArtifactRef` describes an entity a run consumed or produced. It is either
|
|
155
|
+
*local* (makeprov stats and hashes it) or *external* (makeprov records the IRI
|
|
156
|
+
and never touches the filesystem):
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from makeprov import ArtifactRef, OutPath, rule
|
|
160
|
+
|
|
161
|
+
@rule()
|
|
162
|
+
def train(
|
|
163
|
+
dataset: ArtifactRef = ArtifactRef.external(
|
|
164
|
+
"https://example.org/datasets/train-v17",
|
|
165
|
+
types=("prov:Entity", "schema:Dataset"),
|
|
166
|
+
digest="sha256:...",
|
|
167
|
+
),
|
|
168
|
+
model: OutPath = OutPath("models/m.pkl"),
|
|
169
|
+
):
|
|
170
|
+
...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The external object keeps its own detailed metadata; makeprov only records that
|
|
174
|
+
this run used its stable IRI. External refs take no part in staleness checks,
|
|
175
|
+
since they have no local mtime to compare.
|
|
176
|
+
|
|
177
|
+
## Installation
|
|
178
|
+
|
|
179
|
+
You can install the module directly from PyPI:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
pip install makeprov
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Optional extras add RDF/TriG export, CLI subcommand support, or the Snakemake bridge:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
pip install "makeprov[rdf]" # rdflib + pyshacl for RDF/TriG export
|
|
189
|
+
pip install "makeprov[cli]" # defopt, needed for makeprov.main()
|
|
190
|
+
pip install "makeprov[snakemake]" # the makeprov-snakemake bridge
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Usage
|
|
194
|
+
|
|
195
|
+
Here’s an example of how to use this package in your Python scripts:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from makeprov import rule, InPath, OutPath, build
|
|
199
|
+
|
|
200
|
+
@rule()
|
|
201
|
+
def process_data(
|
|
202
|
+
sample: int | None = None,
|
|
203
|
+
input_file: InPath = InPath('data/{sample:d}.txt'),
|
|
204
|
+
output_file: OutPath = OutPath('results/{sample:d}.txt')
|
|
205
|
+
):
|
|
206
|
+
with input_file.open('r') as infile, output_file.open('w') as outfile:
|
|
207
|
+
data = infile.read()
|
|
208
|
+
outfile.write(data.upper())
|
|
209
|
+
|
|
210
|
+
if __name__ == '__main__':
|
|
211
|
+
# Build a specific templated target and its prerequisites
|
|
212
|
+
from makeprov import build
|
|
213
|
+
build('results/1.txt')
|
|
214
|
+
|
|
215
|
+
# Or expose rules via a command line interface
|
|
216
|
+
import defopt
|
|
217
|
+
defopt.run(process_data)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
You can execute `examples/example.py` via the CLI like so:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
python examples/example.py build-all
|
|
224
|
+
|
|
225
|
+
# Or set configuration through the CLI
|
|
226
|
+
python examples/example.py build-all --conf='{"base_iri": "http://mybaseiri.org/", "prov_dir": "my_prov_directory"}' --force --input_file input.txt --output_file final_output.txt
|
|
227
|
+
|
|
228
|
+
# Or set configuration through a TOML file
|
|
229
|
+
python examples/example.py build-all -c @my_config.toml
|
|
230
|
+
|
|
231
|
+
# Inspect dependency resolution without executing rules
|
|
232
|
+
python examples/example.py --explain results/1.txt
|
|
233
|
+
python examples/example.py --to-dot results/1.txt
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Complex CSV-to-RDF Workflow
|
|
237
|
+
|
|
238
|
+
For a more involved scenario, see [`examples/complex_example.py`](examples/complex_example.py). It creates multiple CSV files, aggregates their contents, and emits an RDF graph that is both serialized to disk and embedded into the provenance dataset because the function returns an `rdflib.Graph`.
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
@rule()
|
|
242
|
+
def export_totals_graph(
|
|
243
|
+
totals_csv: InPath = InPath("data/region_totals.csv"),
|
|
244
|
+
graph_ttl: OutPath = OutPath("data/region_totals.ttl"),
|
|
245
|
+
) -> Graph:
|
|
246
|
+
graph = Graph()
|
|
247
|
+
graph.bind("sales", SALES)
|
|
248
|
+
|
|
249
|
+
with totals_csv.open("r", newline="") as handle:
|
|
250
|
+
for row in csv.DictReader(handle):
|
|
251
|
+
region_key = row["region"].lower().replace(" ", "-")
|
|
252
|
+
subject = SALES[f"region/{region_key}"]
|
|
253
|
+
|
|
254
|
+
graph.add((subject, RDF.type, SALES.RegionTotal))
|
|
255
|
+
graph.add((subject, SALES.regionName, Literal(row["region"])))
|
|
256
|
+
graph.add((subject, SALES.totalUnits, Literal(row["total_units"], datatype=XSD.integer)))
|
|
257
|
+
graph.add((subject, SALES.totalRevenue, Literal(row["total_revenue"], datatype=XSD.decimal)))
|
|
258
|
+
|
|
259
|
+
with graph_ttl.open("w") as handle:
|
|
260
|
+
handle.write(graph.serialize(format="turtle"))
|
|
261
|
+
|
|
262
|
+
return graph
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Run the entire workflow, including CSV generation and RDF export, with:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
python examples/complex_example.py build-sales-report
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Bundling nested provenance and directory outputs
|
|
272
|
+
|
|
273
|
+
Rules can merge the provenance from any rules they invoke by passing
|
|
274
|
+
``merge=True`` to `makeprov.rule`. Pair this with
|
|
275
|
+
`makeprov.OutDir` to declare a directory and then materialize multiple
|
|
276
|
+
outputs beneath it while keeping them linked to a single provenance record. Use
|
|
277
|
+
`makeprov.InDir` for the same tracked-directory semantics on inputs. For nested
|
|
278
|
+
structures, call `subdir()` on an `OutDir`/`InDir` to auto-wrap subfolders
|
|
279
|
+
without manually constructing new instances.
|
|
280
|
+
See [`examples/merge_outdir_example.py`](examples/merge_outdir_example.py) for an example.
|
|
281
|
+
|
|
282
|
+
Merging is enabled by default: top-level runs start a provenance buffer and
|
|
283
|
+
flush it once the CLI finishes, so downstream rules end up in one document
|
|
284
|
+
unless you explicitly turn buffering off with `merge=False` on a rule or in the
|
|
285
|
+
global config. Nested merges append to their parent buffer rather than writing
|
|
286
|
+
multiple files.
|
|
287
|
+
|
|
288
|
+
### Configured context and isolated sessions
|
|
289
|
+
|
|
290
|
+
`examples/context_demo_example.py` demonstrates pinning a base IRI, writing
|
|
291
|
+
provenance to a dedicated directory, and running rules inside an isolated
|
|
292
|
+
session so registries and buffers do not leak across runs:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
python examples/context_demo_example.py build-all
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Snakemake workflows
|
|
299
|
+
|
|
300
|
+
Install the `snakemake` extra (`pip install "makeprov[snakemake]"`) to get the
|
|
301
|
+
`makeprov-snakemake` command, which shells out to Snakemake and converts the
|
|
302
|
+
job DAG together with ``--detailed-summary`` metadata into a PROV document.
|
|
303
|
+
It mirrors the familiar configuration flags from `makeprov.config` and writes
|
|
304
|
+
JSON-LD by default. Note this is a best-effort bridge: it parses Snakemake's
|
|
305
|
+
human-oriented text output, so treat it as a convenience for reports rather
|
|
306
|
+
than an authoritative source of truth — it will raise rather than guess when
|
|
307
|
+
it can't unambiguously parse a filename (e.g. one containing whitespace).
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
makeprov-snakemake --prov-path prov/snakemake -- --snakefile Snakefile --nolock
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Wire the resulting file into a report by marking it with Snakemake’s
|
|
314
|
+
`report()` helper:
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
rule provenance:
|
|
318
|
+
input:
|
|
319
|
+
"results/word_count.txt"
|
|
320
|
+
output:
|
|
321
|
+
"prov/snakemake.json"
|
|
322
|
+
shell:
|
|
323
|
+
(
|
|
324
|
+
"makeprov-snakemake "
|
|
325
|
+
"--prov-path prov/snakemake "
|
|
326
|
+
"--out-fmt json --context --frame provenance "
|
|
327
|
+
"-- "
|
|
328
|
+
"--snakefile {workflow.snakefile} --nolock {input}"
|
|
329
|
+
)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Using the optional `--forceall-dag` flag ensures that the job-level dependency
|
|
333
|
+
edges in the provenance graph remain complete even when Snakemake skips nodes
|
|
334
|
+
that are already up to date.
|
|
335
|
+
|
|
336
|
+
### Configuration
|
|
337
|
+
|
|
338
|
+
You can customize the provenance tracking with the following options:
|
|
339
|
+
|
|
340
|
+
- `base_iri` (str): Base IRI for new resources
|
|
341
|
+
- `prov_dir` (str): Directory for writing PROV `.json-ld` or `.trig` files
|
|
342
|
+
- `force` (bool): Force running of dependencies
|
|
343
|
+
- `dry_run` (bool): Only check workflow, don't run anything
|
|
344
|
+
- `strict` (bool, default `True`): Raise `makeprov.ProvenanceWriteError` if a
|
|
345
|
+
rule's provenance record fails to write, instead of only logging a
|
|
346
|
+
warning. A rule that produces a result but no provenance record is
|
|
347
|
+
treated as a failure by default; set `strict=False` to opt out per-rule
|
|
348
|
+
or globally.
|
|
349
|
+
- `run_id` (str | None): Adopt an externally supplied run identity, such as a
|
|
350
|
+
CI job id. When unset, each run gets a fresh unique id.
|
|
351
|
+
- `record_user` (bool, default `False`): Record the invoking user, taken from
|
|
352
|
+
`git config user.name`/`user.email`, as a `schema:Person` agent. Off by
|
|
353
|
+
default so personal data isn't published by accident.
|
|
354
|
+
CLI: `--record-user`.
|
|
355
|
+
- `emit_plan_graph` (bool, default `False`): Also emit prospective structure —
|
|
356
|
+
the rule dependency graph as `prov:Plan` nodes linked by `dct:requires`. Off
|
|
357
|
+
by default, so a document describes only what actually ran. CLI:
|
|
358
|
+
`--plan-graph`.
|
|
359
|
+
- `forge_profiles` (str | None): TOML file of extra forge profiles, for
|
|
360
|
+
self-hosted git hosts. CLI: `--forge-profiles`.
|
|
361
|
+
|
|
362
|
+
### Upgrading to 0.7
|
|
363
|
+
|
|
364
|
+
0.7 changes the provenance model. The decorator API is unchanged — existing
|
|
365
|
+
`@rule` functions using `InPath`/`OutPath` keep working — but the emitted
|
|
366
|
+
graph differs:
|
|
367
|
+
|
|
368
|
+
- The script is no longer a `prov:SoftwareAgent`. It is a `prov:Plan`, reached
|
|
369
|
+
from the activity via `prov:qualifiedAssociation`/`prov:hadPlan`. Consumers
|
|
370
|
+
that looked for `prov:wasAssociatedWith` to find the script should follow
|
|
371
|
+
`prov:hadPlan` instead.
|
|
372
|
+
- Outputs now carry a `sha256` content digest. Previously the digest was
|
|
373
|
+
computed and then discarded for outputs.
|
|
374
|
+
- Entity IRIs derived from a GitHub remote are pinned to the **commit** rather
|
|
375
|
+
than the branch, so an IRI no longer denotes different bytes after each push.
|
|
376
|
+
- Run identifiers include seconds and a random suffix. Minute-resolution ids
|
|
377
|
+
meant two runs of the same rule in one minute shared an activity IRI.
|
|
378
|
+
- A declared output that is missing after a successful run now raises
|
|
379
|
+
`UnresolvedArtifactError` instead of being dropped from the graph. Missing
|
|
380
|
+
*inputs* are recorded without content metadata and logged, rather than
|
|
381
|
+
disappearing.
|
|
382
|
+
- `Prov.create()` takes `list[ArtifactRef]` instead of `list[Path]`.
|
|
383
|
+
- The Snakemake bridge follows the same model: each rule is now a `prov:Plan`
|
|
384
|
+
at `<base>rule/<name>`, and each job activity carries a
|
|
385
|
+
`prov:qualifiedAssociation`. Its agent node gained `schema:SoftwareApplication`.
|
|
386
|
+
- The bridge no longer defaults to a `urn:snakemake:` namespace. That NID was
|
|
387
|
+
never IANA-registered, so it named no real namespace and collided across
|
|
388
|
+
unrelated workflows sharing rule names. It now shares the decorator API's
|
|
389
|
+
identifier policy (`makeprov.prov.resolve_iris`): an explicit `base_iri`,
|
|
390
|
+
else a commit-pinned base derived from a GitHub remote, else relative IRIs.
|
|
391
|
+
- `blob:` identifiers are only minted for files inside the repository. An
|
|
392
|
+
absolute path within the checkout is rewritten to its repo-relative form, and
|
|
393
|
+
a path outside it gets a `file:` URI instead of a `blob:` IRI that would
|
|
394
|
+
expand to a nonexistent location.
|
|
395
|
+
- Entities carry `schema:sha256` (bare hex) alongside the algorithm-qualified
|
|
396
|
+
`dct:identifier`, so consumers no longer have to parse a prefix.
|
|
397
|
+
- Activities state `prov:generated` as well as each entity's inverse
|
|
398
|
+
`prov:wasGeneratedBy`, mirroring `prov:used` and mapping onto RO-Crate's
|
|
399
|
+
`result`.
|
|
400
|
+
- A dirty working tree is recorded as `<sha>-dirty` (the `git describe --dirty`
|
|
401
|
+
convention) with a warning, instead of asserting a clean revision that does
|
|
402
|
+
not describe what ran.
|
|
403
|
+
- `prov:wasDerivedFrom` and `rdfs:seeAlso` on cached downloads are emitted as
|
|
404
|
+
node references rather than string literals, so the links are traversable.
|
|
405
|
+
`CachedDownload(..., sha256=...)` pins and verifies the cached copy.
|
|
406
|
+
- The base heuristic covers all hosts in `forges.toml`, not just GitHub, and
|
|
407
|
+
understands SSH remotes. Credentials embedded in a remote URL are stripped —
|
|
408
|
+
previously a remote like `https://user:token@github.com/o/r.git` would have
|
|
409
|
+
put the token into `@base` in every document.
|
|
410
|
+
|
|
411
|
+
### Scoped spans and cached downloads
|
|
412
|
+
|
|
413
|
+
Use `makeprov.span(label, prov_path=None, frame=None, context=None)` as a
|
|
414
|
+
context manager or decorator to bracket a chunk of work in its own provenance
|
|
415
|
+
buffer. A span returns the merged `Prov` via `span.prov`, so nested spans can
|
|
416
|
+
emit labeled artifacts without manual slicing/merging:
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
from makeprov import span
|
|
420
|
+
|
|
421
|
+
with span("model-run", prov_path="prov/models/model1"):
|
|
422
|
+
run_model()
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
For remote resources that are cached locally, wrap the path with
|
|
426
|
+
`CachedDownload`. It will lazily fetch on first access and record the source
|
|
427
|
+
URL (and optional headers) in the provenance:
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
from makeprov import CachedDownload, rule
|
|
431
|
+
|
|
432
|
+
@rule()
|
|
433
|
+
def fetch_data(meta_json=CachedDownload("https://example.org/meta.json", "cache/meta.json")):
|
|
434
|
+
with meta_json.open() as handle:
|
|
435
|
+
return handle.read()
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Documentation
|
|
439
|
+
|
|
440
|
+
Build the Sphinx docs (including autosummary API stubs) with the docs extra so
|
|
441
|
+
that the CLI dependencies needed for imports are available:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
pip install -e ".[docs]"
|
|
445
|
+
python docs/build.py
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## Contributing
|
|
449
|
+
|
|
450
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
451
|
+
|
|
452
|
+
## License
|
|
453
|
+
|
|
454
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|