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.
Files changed (38) hide show
  1. artifactum-0.2.0/.gitignore +11 -0
  2. artifactum-0.2.0/CHANGELOG.md +32 -0
  3. artifactum-0.2.0/CONTRIBUTING.md +51 -0
  4. artifactum-0.2.0/LICENSE +21 -0
  5. artifactum-0.2.0/PKG-INFO +155 -0
  6. artifactum-0.2.0/README.md +116 -0
  7. artifactum-0.2.0/pyproject.toml +110 -0
  8. artifactum-0.2.0/src/artifactum/__init__.py +221 -0
  9. artifactum-0.2.0/src/artifactum/cache.py +43 -0
  10. artifactum-0.2.0/src/artifactum/cli/__init__.py +0 -0
  11. artifactum-0.2.0/src/artifactum/cli/init_cmd.py +95 -0
  12. artifactum-0.2.0/src/artifactum/cli/main.py +340 -0
  13. artifactum-0.2.0/src/artifactum/cli/shell_cmd.py +187 -0
  14. artifactum-0.2.0/src/artifactum/cli/tui.py +308 -0
  15. artifactum-0.2.0/src/artifactum/client.py +224 -0
  16. artifactum-0.2.0/src/artifactum/config.py +86 -0
  17. artifactum-0.2.0/src/artifactum/exceptions.py +28 -0
  18. artifactum-0.2.0/src/artifactum/hashing.py +13 -0
  19. artifactum-0.2.0/src/artifactum/provenance.py +180 -0
  20. artifactum-0.2.0/src/artifactum/storage/__init__.py +59 -0
  21. artifactum-0.2.0/src/artifactum/storage/base.py +53 -0
  22. artifactum-0.2.0/src/artifactum/storage/gcs.py +48 -0
  23. artifactum-0.2.0/src/artifactum/storage/local.py +61 -0
  24. artifactum-0.2.0/src/artifactum/storage/move.py +53 -0
  25. artifactum-0.2.0/src/artifactum/tracking.py +175 -0
  26. artifactum-0.2.0/tests/__init__.py +0 -0
  27. artifactum-0.2.0/tests/conftest.py +96 -0
  28. artifactum-0.2.0/tests/fakes.py +222 -0
  29. artifactum-0.2.0/tests/test_browse.py +120 -0
  30. artifactum-0.2.0/tests/test_cache.py +67 -0
  31. artifactum-0.2.0/tests/test_config.py +63 -0
  32. artifactum-0.2.0/tests/test_hashing.py +27 -0
  33. artifactum-0.2.0/tests/test_plan_limits.py +38 -0
  34. artifactum-0.2.0/tests/test_setup.py +119 -0
  35. artifactum-0.2.0/tests/test_shell_cmd.py +192 -0
  36. artifactum-0.2.0/tests/test_storage.py +176 -0
  37. artifactum-0.2.0/tests/test_tracking.py +180 -0
  38. artifactum-0.2.0/tests/test_user_journeys.py +189 -0
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ dist/
4
+ build/
5
+ .venv/
6
+ .pixi/*
7
+ !.pixi/config.toml
8
+ .pytest_cache/
9
+ .coverage
10
+ coverage.xml
11
+ htmlcov/
@@ -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.
@@ -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:"]