artifactum 0.2.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.
- artifactum-0.2.0/.gitignore +11 -0
- artifactum-0.2.0/CHANGELOG.md +32 -0
- artifactum-0.2.0/CONTRIBUTING.md +51 -0
- artifactum-0.2.0/LICENSE +21 -0
- artifactum-0.2.0/PKG-INFO +155 -0
- artifactum-0.2.0/README.md +116 -0
- artifactum-0.2.0/pyproject.toml +110 -0
- artifactum-0.2.0/src/artifactum/__init__.py +221 -0
- artifactum-0.2.0/src/artifactum/cache.py +43 -0
- artifactum-0.2.0/src/artifactum/cli/__init__.py +0 -0
- artifactum-0.2.0/src/artifactum/cli/init_cmd.py +95 -0
- artifactum-0.2.0/src/artifactum/cli/main.py +340 -0
- artifactum-0.2.0/src/artifactum/cli/shell_cmd.py +187 -0
- artifactum-0.2.0/src/artifactum/cli/tui.py +308 -0
- artifactum-0.2.0/src/artifactum/client.py +224 -0
- artifactum-0.2.0/src/artifactum/config.py +86 -0
- artifactum-0.2.0/src/artifactum/exceptions.py +28 -0
- artifactum-0.2.0/src/artifactum/hashing.py +13 -0
- artifactum-0.2.0/src/artifactum/provenance.py +180 -0
- artifactum-0.2.0/src/artifactum/storage/__init__.py +59 -0
- artifactum-0.2.0/src/artifactum/storage/base.py +53 -0
- artifactum-0.2.0/src/artifactum/storage/gcs.py +48 -0
- artifactum-0.2.0/src/artifactum/storage/local.py +61 -0
- artifactum-0.2.0/src/artifactum/storage/move.py +53 -0
- artifactum-0.2.0/src/artifactum/tracking.py +175 -0
- artifactum-0.2.0/tests/__init__.py +0 -0
- artifactum-0.2.0/tests/conftest.py +96 -0
- artifactum-0.2.0/tests/fakes.py +222 -0
- artifactum-0.2.0/tests/test_browse.py +120 -0
- artifactum-0.2.0/tests/test_cache.py +67 -0
- artifactum-0.2.0/tests/test_config.py +63 -0
- artifactum-0.2.0/tests/test_hashing.py +27 -0
- artifactum-0.2.0/tests/test_plan_limits.py +38 -0
- artifactum-0.2.0/tests/test_setup.py +119 -0
- artifactum-0.2.0/tests/test_shell_cmd.py +192 -0
- artifactum-0.2.0/tests/test_storage.py +176 -0
- artifactum-0.2.0/tests/test_tracking.py +180 -0
- artifactum-0.2.0/tests/test_user_journeys.py +189 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0](https://github.com/atfm-io/artifactum/compare/v0.1.0...v0.2.0) (2026-11-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* **storage:** google-cloud-storage is no longer installed by default. Install `artifactum[gcs]` to keep storing files in a bucket.
|
|
9
|
+
* **cli:** the `artifactum` console script no longer exists. Run `atfm` instead.
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **cli:** add --json output to get, publish, describe, log and run status ([16f97aa](https://github.com/atfm-io/artifactum/commit/16f97aafbcdca2c34815cc49d9502ff3e3abe431))
|
|
14
|
+
* **cli:** atfm --version ([5f16856](https://github.com/atfm-io/artifactum/commit/5f1685657061c83e92da738cc2e650a3f191d932))
|
|
15
|
+
* **cli:** atfm init --token --storage, and atfm storage move ([6aecb95](https://github.com/atfm-io/artifactum/commit/6aecb95afca1c751ee88f04a0ed960358af57f98))
|
|
16
|
+
* **cli:** label the terminal browser as a preview ([b6a9940](https://github.com/atfm-io/artifactum/commit/b6a99403ec090edb2dbf34d98653b6a0a4e32e13))
|
|
17
|
+
* **cli:** the command is atfm only ([0777167](https://github.com/atfm-io/artifactum/commit/0777167210378998a1f46601a87ba8f7fd31f982))
|
|
18
|
+
* **storage:** store files in a shared directory, or a GCS bucket ([3db014f](https://github.com/atfm-io/artifactum/commit/3db014fc0d89c4ba53ca7fa1480b9c6517eeff32))
|
|
19
|
+
* support Python 3.10 ([070f6c4](https://github.com/atfm-io/artifactum/commit/070f6c4e69ec3756777a5debcb1d1f1223684e12))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* accept artifact IDs in lowercase ([be79ec2](https://github.com/atfm-io/artifactum/commit/be79ec2a4a103d5b43c7ba0416abb0073dfb2595))
|
|
25
|
+
* **cli:** clear errors instead of tracebacks, and other fixes found by new tests ([fd3fe10](https://github.com/atfm-io/artifactum/commit/fd3fe1085535fb724740d6ba312c91d92a4ff1b6))
|
|
26
|
+
* **cli:** no hint about parents on the first publish ([451b737](https://github.com/atfm-io/artifactum/commit/451b737cd02f12b36aea031e25345de404d09e3d))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Documentation
|
|
30
|
+
|
|
31
|
+
* MIT license ([7ad2ce5](https://github.com/atfm-io/artifactum/commit/7ad2ce50729fba95843b57b8546e01542d3316c4))
|
|
32
|
+
* README no longer mentions an atfm search command ([2d9b86e](https://github.com/atfm-io/artifactum/commit/2d9b86e2aa1a97ee47ef54a4c9872265d51fd946))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Development
|
|
4
|
+
|
|
5
|
+
The development environment is defined in `pyproject.toml` (the `[tool.pixi.*]` tables):
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pixi run test # runs the tests and prints a coverage report
|
|
9
|
+
pixi run fmt # formats the code and fixes what ruff can fix
|
|
10
|
+
pixi run lint # what CI checks: formatting and lint, without changing anything
|
|
11
|
+
pixi run install-hooks # once per clone: ruff then runs on every commit
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Without pixi: `pip install -e ".[dev]"`, then `pytest`, `ruff format` and `ruff check`.
|
|
15
|
+
Style settings are in `[tool.ruff]` in `pyproject.toml`.
|
|
16
|
+
|
|
17
|
+
## Commit messages
|
|
18
|
+
|
|
19
|
+
Every commit on `main` follows [Conventional Commits](https://www.conventionalcommits.org/).
|
|
20
|
+
Releases are cut from these messages by [release-please](https://github.com/googleapis/release-please),
|
|
21
|
+
so a commit that doesn't follow the format is left out of the changelog.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
<type>(<optional scope>): <summary in the imperative>
|
|
25
|
+
|
|
26
|
+
<optional body>
|
|
27
|
+
|
|
28
|
+
<optional footers, e.g. BREAKING CHANGE: ...>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Type | Use for | In the changelog | Version bump |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| `feat` | a new feature | Features | minor |
|
|
34
|
+
| `fix` | a bug fix | Bug Fixes | patch |
|
|
35
|
+
| `perf` | a performance improvement | Performance | patch |
|
|
36
|
+
| `docs` | documentation only | Documentation | patch |
|
|
37
|
+
| `deps` | dependency updates | Dependencies | patch |
|
|
38
|
+
| `refactor`, `test`, `build`, `ci`, `chore`, `style` | everything else | hidden | none |
|
|
39
|
+
|
|
40
|
+
A `!` after the type (`feat!: ...`) or a `BREAKING CHANGE:` footer marks a breaking change.
|
|
41
|
+
While the version is below 1.0, breaking changes bump the minor version.
|
|
42
|
+
|
|
43
|
+
Pull requests are squash-merged, so the PR title becomes the commit message and must
|
|
44
|
+
follow the same format.
|
|
45
|
+
|
|
46
|
+
## Releases
|
|
47
|
+
|
|
48
|
+
release-please keeps a release PR open that collects unreleased changes, bumps the
|
|
49
|
+
version in `pyproject.toml` and updates `CHANGELOG.md`. Merging that PR tags the release
|
|
50
|
+
(`vX.Y.Z`), publishes a GitHub release with the built files attached, and uploads the
|
|
51
|
+
package to PyPI.
|
artifactum-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chris Cole
|
|
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.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: artifactum
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A registry and lineage tracker for the files data teams produce
|
|
5
|
+
Project-URL: Homepage, https://atfm.io
|
|
6
|
+
Project-URL: Repository, https://github.com/atfm-io/artifactum
|
|
7
|
+
Project-URL: Issues, https://github.com/atfm-io/artifactum/issues
|
|
8
|
+
Author-email: Chris Cole <chris.c.1221@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: artifacts,data lineage,data versioning,provenance,reproducibility
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: jsonargparse>=4.35
|
|
26
|
+
Requires-Dist: pyperclip>=1.8
|
|
27
|
+
Requires-Dist: rich>=13.0
|
|
28
|
+
Requires-Dist: textual>=0.70
|
|
29
|
+
Requires-Dist: tomli-w>=1.0
|
|
30
|
+
Requires-Dist: tomli>=1.1; python_version < '3.11'
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pre-commit>=3.5; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
36
|
+
Provides-Extra: gcs
|
|
37
|
+
Requires-Dist: google-cloud-storage>=2.0; extra == 'gcs'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# Artifactum · `atfm`
|
|
41
|
+
|
|
42
|
+
[atfm.io](https://atfm.io) · `pip install artifactum` · `import artifactum as atfm` · the `atfm` command
|
|
43
|
+
|
|
44
|
+
A registry and lineage tracker for the files data teams produce: datasets, models, results and figures.
|
|
45
|
+
|
|
46
|
+
Every file you publish gets a permanent ID, and Artifactum records where it came from: the artifacts your
|
|
47
|
+
code read, the git commit and code that ran, and who published it. Files stay in storage your team
|
|
48
|
+
controls, a shared directory or a Google Cloud Storage bucket; Artifactum stores only metadata.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install artifactum
|
|
54
|
+
atfm init
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`init` asks for your API token (from Settings at [atfm.io](https://atfm.io)), your server URL (`https://atfm.io`
|
|
58
|
+
unless your team runs its own) and where to store files. Or give them and skip the questions:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
atfm init --token atfm_... --storage ~/atfm-files
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Storage
|
|
65
|
+
|
|
66
|
+
Artifactum never holds your files. Each one is copied to your team's storage when it's published and
|
|
67
|
+
fetched from there when someone reads it, then cached locally.
|
|
68
|
+
|
|
69
|
+
| Storage | Location to give `init` | Install |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| A shared directory (lab server, cluster, network drive) | `/groups/lab/atfm` | `pip install artifactum` |
|
|
72
|
+
| Google Cloud Storage | `gs://my-team-bucket/atfm` | `pip install 'artifactum[gcs]'` |
|
|
73
|
+
|
|
74
|
+
A shared directory needs no cloud account: everyone on the team just needs to reach it **at the same
|
|
75
|
+
path**, since the registry records where each file is. Files are stored by content, under
|
|
76
|
+
`<location>/<hash[:2]>/<hash>/<filename>`, and never overwritten.
|
|
77
|
+
|
|
78
|
+
For Google Cloud Storage, each person signs in once with `gcloud auth application-default login` and needs
|
|
79
|
+
read and write access to the bucket.
|
|
80
|
+
|
|
81
|
+
To try Artifactum on your own, a folder like `~/atfm-files` is enough. When you bring in your team, move
|
|
82
|
+
what you've published to shared storage; the files are copied, checked against their hashes and their
|
|
83
|
+
records updated, and the old copies are left in place:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
atfm storage move /groups/lab/atfm # or gs://my-team-bucket/atfm
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Use
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import artifactum as atfm
|
|
93
|
+
import pandas as pd
|
|
94
|
+
|
|
95
|
+
events = pd.read_parquet(atfm.artifact("C4LN9E2A")) # fetched and cached locally
|
|
96
|
+
features = build_features(events)
|
|
97
|
+
features.to_parquet("features.parquet")
|
|
98
|
+
|
|
99
|
+
atfm.publish(
|
|
100
|
+
"features.parquet",
|
|
101
|
+
description="Feature matrix, nulls dropped",
|
|
102
|
+
tags=["features"],
|
|
103
|
+
)
|
|
104
|
+
# C4LN9E2A is recorded as the parent automatically, along with the git commit and code.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
From the shell (bash, R, Nextflow, Snakemake, ...):
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
eval "$(atfm run start 'align reads')" # lineage is recorded across the commands below
|
|
111
|
+
bwa mem "$(atfm get GRCH38RF)" "$(atfm get FQ7R1A2B)" > aligned.sam
|
|
112
|
+
atfm publish aligned.bam -d "Reads aligned with BWA-MEM" --code align.sh
|
|
113
|
+
eval "$(atfm run end)"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`atfm get` prints a local path and `atfm publish` prints the new ID, so both compose with other
|
|
117
|
+
commands. Use `--parent ID` to set parents explicitly.
|
|
118
|
+
|
|
119
|
+
Add `--json` to `get`, `publish`, `describe`, `log` or `run status` to get one JSON object on stdout
|
|
120
|
+
instead, for tools that parse rather than split strings. Messages and errors stay on stderr, and a
|
|
121
|
+
failure exits non-zero.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
$ atfm get C4LN9E2A --json
|
|
125
|
+
{"id": "C4LN9E2A", "path": "/home/me/.artifactum/cache/3f9a..."}
|
|
126
|
+
$ atfm publish model.rds -p C4LN9E2A --json
|
|
127
|
+
{"id": "M0DL3V3X", "file": "/home/me/project/model.rds", "parents": ["C4LN9E2A"], "run_id": null}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
From R, call the command and read its JSON with `jsonlite`:
|
|
131
|
+
|
|
132
|
+
```r
|
|
133
|
+
atfm <- function(...) {
|
|
134
|
+
out <- system2("atfm", c(..., "--json"), stdout = TRUE)
|
|
135
|
+
if (!is.null(attr(out, "status"))) stop("atfm failed")
|
|
136
|
+
jsonlite::fromJSON(paste(out, collapse = ""))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
counts <- read.csv(atfm("get", "C4LN9E2A")$path)
|
|
140
|
+
saveRDS(fit, "model.rds")
|
|
141
|
+
atfm("publish", "model.rds", "-p", "C4LN9E2A", "-d", shQuote("GLM on cleaned counts"))$id
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
There's no R package yet. If you'd like one, email hello@atfm.io.
|
|
145
|
+
|
|
146
|
+
Browsing from the terminal:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
atfm log K9PWQM7T # upstream lineage
|
|
150
|
+
atfm browse # interactive browser (preview: recent artifacts, no search yet)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT. See [LICENSE](https://github.com/atfm-io/artifactum/blob/main/LICENSE).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Artifactum · `atfm`
|
|
2
|
+
|
|
3
|
+
[atfm.io](https://atfm.io) · `pip install artifactum` · `import artifactum as atfm` · the `atfm` command
|
|
4
|
+
|
|
5
|
+
A registry and lineage tracker for the files data teams produce: datasets, models, results and figures.
|
|
6
|
+
|
|
7
|
+
Every file you publish gets a permanent ID, and Artifactum records where it came from: the artifacts your
|
|
8
|
+
code read, the git commit and code that ran, and who published it. Files stay in storage your team
|
|
9
|
+
controls, a shared directory or a Google Cloud Storage bucket; Artifactum stores only metadata.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install artifactum
|
|
15
|
+
atfm init
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`init` asks for your API token (from Settings at [atfm.io](https://atfm.io)), your server URL (`https://atfm.io`
|
|
19
|
+
unless your team runs its own) and where to store files. Or give them and skip the questions:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
atfm init --token atfm_... --storage ~/atfm-files
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Storage
|
|
26
|
+
|
|
27
|
+
Artifactum never holds your files. Each one is copied to your team's storage when it's published and
|
|
28
|
+
fetched from there when someone reads it, then cached locally.
|
|
29
|
+
|
|
30
|
+
| Storage | Location to give `init` | Install |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| A shared directory (lab server, cluster, network drive) | `/groups/lab/atfm` | `pip install artifactum` |
|
|
33
|
+
| Google Cloud Storage | `gs://my-team-bucket/atfm` | `pip install 'artifactum[gcs]'` |
|
|
34
|
+
|
|
35
|
+
A shared directory needs no cloud account: everyone on the team just needs to reach it **at the same
|
|
36
|
+
path**, since the registry records where each file is. Files are stored by content, under
|
|
37
|
+
`<location>/<hash[:2]>/<hash>/<filename>`, and never overwritten.
|
|
38
|
+
|
|
39
|
+
For Google Cloud Storage, each person signs in once with `gcloud auth application-default login` and needs
|
|
40
|
+
read and write access to the bucket.
|
|
41
|
+
|
|
42
|
+
To try Artifactum on your own, a folder like `~/atfm-files` is enough. When you bring in your team, move
|
|
43
|
+
what you've published to shared storage; the files are copied, checked against their hashes and their
|
|
44
|
+
records updated, and the old copies are left in place:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
atfm storage move /groups/lab/atfm # or gs://my-team-bucket/atfm
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Use
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import artifactum as atfm
|
|
54
|
+
import pandas as pd
|
|
55
|
+
|
|
56
|
+
events = pd.read_parquet(atfm.artifact("C4LN9E2A")) # fetched and cached locally
|
|
57
|
+
features = build_features(events)
|
|
58
|
+
features.to_parquet("features.parquet")
|
|
59
|
+
|
|
60
|
+
atfm.publish(
|
|
61
|
+
"features.parquet",
|
|
62
|
+
description="Feature matrix, nulls dropped",
|
|
63
|
+
tags=["features"],
|
|
64
|
+
)
|
|
65
|
+
# C4LN9E2A is recorded as the parent automatically, along with the git commit and code.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
From the shell (bash, R, Nextflow, Snakemake, ...):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
eval "$(atfm run start 'align reads')" # lineage is recorded across the commands below
|
|
72
|
+
bwa mem "$(atfm get GRCH38RF)" "$(atfm get FQ7R1A2B)" > aligned.sam
|
|
73
|
+
atfm publish aligned.bam -d "Reads aligned with BWA-MEM" --code align.sh
|
|
74
|
+
eval "$(atfm run end)"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`atfm get` prints a local path and `atfm publish` prints the new ID, so both compose with other
|
|
78
|
+
commands. Use `--parent ID` to set parents explicitly.
|
|
79
|
+
|
|
80
|
+
Add `--json` to `get`, `publish`, `describe`, `log` or `run status` to get one JSON object on stdout
|
|
81
|
+
instead, for tools that parse rather than split strings. Messages and errors stay on stderr, and a
|
|
82
|
+
failure exits non-zero.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
$ atfm get C4LN9E2A --json
|
|
86
|
+
{"id": "C4LN9E2A", "path": "/home/me/.artifactum/cache/3f9a..."}
|
|
87
|
+
$ atfm publish model.rds -p C4LN9E2A --json
|
|
88
|
+
{"id": "M0DL3V3X", "file": "/home/me/project/model.rds", "parents": ["C4LN9E2A"], "run_id": null}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
From R, call the command and read its JSON with `jsonlite`:
|
|
92
|
+
|
|
93
|
+
```r
|
|
94
|
+
atfm <- function(...) {
|
|
95
|
+
out <- system2("atfm", c(..., "--json"), stdout = TRUE)
|
|
96
|
+
if (!is.null(attr(out, "status"))) stop("atfm failed")
|
|
97
|
+
jsonlite::fromJSON(paste(out, collapse = ""))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
counts <- read.csv(atfm("get", "C4LN9E2A")$path)
|
|
101
|
+
saveRDS(fit, "model.rds")
|
|
102
|
+
atfm("publish", "model.rds", "-p", "C4LN9E2A", "-d", shQuote("GLM on cleaned counts"))$id
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
There's no R package yet. If you'd like one, email hello@atfm.io.
|
|
106
|
+
|
|
107
|
+
Browsing from the terminal:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
atfm log K9PWQM7T # upstream lineage
|
|
111
|
+
atfm browse # interactive browser (preview: recent artifacts, no search yet)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT. See [LICENSE](https://github.com/atfm-io/artifactum/blob/main/LICENSE).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "artifactum"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "A registry and lineage tracker for the files data teams produce"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{name = "Chris Cole", email = "chris.c.1221@gmail.com"}]
|
|
9
|
+
requires-python = ">= 3.10"
|
|
10
|
+
keywords = ["data lineage", "provenance", "artifacts", "reproducibility", "data versioning"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Intended Audience :: Science/Research",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Topic :: Scientific/Engineering",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"httpx>=0.27",
|
|
26
|
+
"jsonargparse>=4.35",
|
|
27
|
+
"rich>=13.0",
|
|
28
|
+
"textual>=0.70",
|
|
29
|
+
"tomli-w>=1.0",
|
|
30
|
+
"pyperclip>=1.8",
|
|
31
|
+
"tomli>=1.1; python_version < '3.11'",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
# Storing files in a Google Cloud Storage bucket (a shared directory needs nothing extra)
|
|
36
|
+
gcs = ["google-cloud-storage>=2.0"]
|
|
37
|
+
dev = ["pytest>=8.0", "pytest-cov>=5.0", "ruff>=0.8", "pre-commit>=3.5"]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://atfm.io"
|
|
41
|
+
Repository = "https://github.com/atfm-io/artifactum"
|
|
42
|
+
Issues = "https://github.com/atfm-io/artifactum/issues"
|
|
43
|
+
|
|
44
|
+
[project.scripts]
|
|
45
|
+
atfm = "artifactum.cli.main:main"
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
build-backend = "hatchling.build"
|
|
49
|
+
requires = ["hatchling"]
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["src/artifactum"]
|
|
53
|
+
|
|
54
|
+
# The source archive is the package, its tests and docs; not the dev tooling
|
|
55
|
+
[tool.hatch.build.targets.sdist]
|
|
56
|
+
include = ["src/artifactum", "tests", "LICENSE", "README.md", "CHANGELOG.md", "CONTRIBUTING.md"]
|
|
57
|
+
|
|
58
|
+
# ── Development environment (pixi) ───────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
[tool.pixi.workspace]
|
|
61
|
+
channels = ["conda-forge"]
|
|
62
|
+
platforms = ["linux-64", "osx-arm64", "osx-64"]
|
|
63
|
+
|
|
64
|
+
[tool.pixi.dependencies]
|
|
65
|
+
python = "3.12.*"
|
|
66
|
+
|
|
67
|
+
[tool.pixi.pypi-dependencies]
|
|
68
|
+
artifactum = { path = ".", editable = true, extras = ["dev"] }
|
|
69
|
+
|
|
70
|
+
[tool.pixi.tasks]
|
|
71
|
+
test = "pytest"
|
|
72
|
+
# Format and fix what can be fixed automatically, then report the rest
|
|
73
|
+
fmt = "ruff format && ruff check --fix"
|
|
74
|
+
# What CI runs: fails on unformatted code or lint errors, changes nothing
|
|
75
|
+
lint = "ruff format --check && ruff check"
|
|
76
|
+
# Run ruff automatically on every commit (once per clone)
|
|
77
|
+
install-hooks = "pre-commit install"
|
|
78
|
+
|
|
79
|
+
# ── Code style ───────────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
[tool.ruff]
|
|
82
|
+
line-length = 120
|
|
83
|
+
target-version = "py310"
|
|
84
|
+
|
|
85
|
+
[tool.ruff.lint]
|
|
86
|
+
select = [
|
|
87
|
+
"E", "W", # pycodestyle
|
|
88
|
+
"F", # pyflakes: unused imports and variables, undefined names
|
|
89
|
+
"I", # import order
|
|
90
|
+
"UP", # modern syntax for the supported Python versions
|
|
91
|
+
"B", # likely bugs (bugbear)
|
|
92
|
+
"SIM", # simpler equivalents
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
# ── Tests and coverage ───────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
[tool.pytest.ini_options]
|
|
98
|
+
testpaths = ["tests"]
|
|
99
|
+
addopts = "--cov --cov-report=term-missing"
|
|
100
|
+
|
|
101
|
+
[tool.coverage.run]
|
|
102
|
+
source = ["artifactum"]
|
|
103
|
+
branch = true
|
|
104
|
+
|
|
105
|
+
[tool.coverage.report]
|
|
106
|
+
# CI fails if coverage drops below this (91% when it was set)
|
|
107
|
+
fail_under = 85
|
|
108
|
+
skip_covered = true
|
|
109
|
+
show_missing = true
|
|
110
|
+
exclude_also = ["if __name__ == .__main__.:", "if TYPE_CHECKING:"]
|