Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
221 lines (203 loc) · 9.19 KB
/
Copy pathpyproject.toml
File metadata and controls
221 lines (203 loc) · 9.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
[project]
# Distribution name = the product (Fathomline); the import package stays `fathom` (the engine
# codename — see the private docs/branding/ note). Both are intentional.
name = "fathomline"
version = "0.6.0"
description = "Fathomline — the storage-estate analyzer: scan every host, see where the bytes live, dedupe with confidence, reclaim safely"
readme = "README.md"
requires-python = ">=3.12"
license = { text = "AGPL-3.0-only" }
authors = [{ name = "Maurice Manning (Bionic Technologies)" }]
keywords = [
"disk-usage", "storage", "deduplication", "treemap", "zfs", "truenas",
"self-hosted", "homelab", "disk-space", "fastapi",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: GNU Affero General Public License v3",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3.12",
"Topic :: System :: Filesystems",
"Topic :: System :: Systems Administration",
]
# Runtime deps grow per build stage (docs/00-documentation-suite-plan.md "Build order").
# Stage 1: pydantic. Stage 2 (transport + catalogue + ingest): fastapi, sqlalchemy async,
# alembic, httpx, asyncpg. Stage 4 (full-bit dedup): blake3.
dependencies = [
"pydantic>=2.6",
"pydantic-settings>=2.2",
"fastapi>=0.110",
"uvicorn[standard]>=0.29",
"sqlalchemy[asyncio]>=2.0",
"alembic>=1.13",
"httpx>=0.27",
"asyncpg>=0.29",
"blake3>=0.4",
"pyyaml>=6.0",
# Auth + RBAC (ADD 13, ADR-009/010): Argon2 password hashing, TOTP, OIDC/JOSE.
"argon2-cffi>=23.1",
"pyotp>=2.9",
"pyjwt[crypto]>=2.8",
# Ed25519 signing for single-use remediation action jobs (remediation-enable, owner ruling
# design_question #1: asymmetric signing for non-repudiation). Already present transitively
# via pyjwt[crypto]; pinned explicitly because the remediation signer imports it directly.
"cryptography>=42.0",
# Persistent-session reconnect/backoff for the platform-adapter control plane (ADD 04,
# code-quality #8). Pure-Python, tiny — kept in base runtime; the WebSocket transport
# libs themselves stay in the optional 'truenas' group below.
"tenacity>=8.2",
# Remote storage-backend transports (ADR-004, storage-backends subsystem, owner ruling):
# SFTP via async-native asyncssh (no blocking I/O on the loop) and SMB via the pure-Python
# smbprotocol (blocking — wrapped in asyncio.to_thread). Both are lazy-imported inside the
# backend modules so a deploy that uses no remote target pays only the install cost.
"asyncssh>=2.14",
"smbprotocol>=1.12",
"pgvector>=0.3",
]
# NOTE(pre-publish): point these at the real public repo once the Bionic Technologies org +
# final repo name exist (PUBLISHING.md step 2).
[project.urls]
Homepage = "https://github.com/bionic-tech/fathomline"
Repository = "https://github.com/bionic-tech/fathomline"
Issues = "https://github.com/bionic-tech/fathomline/issues"
[project.optional-dependencies]
dev = [
"ruff>=0.5",
"mypy>=1.10",
"pytest>=8.0",
"pytest-asyncio>=0.23",
"pytest-cov>=5.0",
"aiosqlite>=0.20",
"asgi-lifespan>=2.1",
"types-PyYAML>=6.0",
]
# TrueNAS control-plane adapter transport (ADD 04). The JSON-RPC 2.0 WebSocket client libs
# stay optional/extensible: core ships TrueNAS + Generic adapters, but the WebSocket transport
# is only needed to actually talk to a live TrueNAS box (lazy-imported in adapters/_ws.py).
truenas = [
"websockets>=12.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/fathom"]
# ---------------------------------------------------------------------------
# Linting & formatting — ruff replaces black/isort/flake8 (standards/18 §3).
# ---------------------------------------------------------------------------
[tool.ruff]
target-version = "py312"
line-length = 100
src = ["src", "tests"]
[tool.ruff.lint]
select = [
"E", "F", "W", # pyflakes + pycodestyle
"I", # isort
"UP", # pyupgrade
"B", # bugbear
"ASYNC", # async pitfalls (no blocking I/O on the loop — standards/18 §7)
"S", # flake8-bandit (security)
"PTH", # prefer pathlib
"RUF",
]
# Prose comments and docstrings use real typography (x -> multiplication sign, minus sign, sigma)
# when quoting a measurement or a formula. They are never identifiers, so they cannot be the
# homoglyph attack RUF001-003 exists to catch.
allowed-confusables = ["×", "−", "Σ"]
[tool.ruff.lint.per-file-ignores]
# S101 assert / S108 tmp paths / S105-S106 test fixture credentials are non-secret test data.
"tests/**" = ["S101", "S108", "S105", "S106"]
# Local dev seeder (not shipped, not on any request path): a one-shot CLI that legitimately uses
# sync os.path / open() and reads local files — the async-loop-blocking (ASYNC240) and pathlib
# (PTH) / bandit (S) rules don't apply to a throwaway scanning tool.
"scripts/localdev/**" = ["ASYNC240", "PTH", "S"]
# Same category, same reasoning: the E2E harness and the Windows operator log-analysis scripts are
# throwaway tooling that never ships and never runs on a request path. Their /tmp defaults (S108)
# are documented CLI defaults the runner overrides, and the one blocking open() (ASYNC230) writes
# the report after every await has completed.
"scripts/e2e/**" = ["ASYNC230", "ASYNC240", "PTH", "S"]
"packaging/windows/*.py" = ["PTH"]
# ---------------------------------------------------------------------------
# Type checking — strict on security/write paths, pragmatic elsewhere (standards/18 §3).
# ---------------------------------------------------------------------------
[tool.mypy]
python_version = "3.12"
strict = true
warn_unused_ignores = true
warn_redundant_casts = true
disallow_any_explicit = false
plugins = ["pydantic.mypy"]
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
# smbprotocol / smbclient ship no type information (no py.typed). It is lazy-imported inside the
# SMB backend's live-transport adapter only; the rest of the subsystem stays fully typed. This is
# the standard ignore-missing-imports for an untyped third-party lib, not a weakening of strict on
# our own code (asyncssh is typed and needs no override).
[[tool.mypy.overrides]]
module = ["smbclient.*", "smbprotocol.*"]
ignore_missing_imports = true
# pgvector ships no py.typed marker (its SQLAlchemy ``Vector`` type is used for the optional
# concierge semantic-search column, ADR-035 Phase 2). Standard ignore for an untyped third-party lib.
[[tool.mypy.overrides]]
module = ["pgvector.*"]
ignore_missing_imports = true
# Pillow (PIL) and Pygments are the preview renderers' decode libraries. They are installed ONLY
# in the gVisor sandbox image (the dedicated preview-sandbox image) — never in the core/test image —
# and are lazy-imported inside the in-sandbox renderers (the decode of untrusted content runs only
# there, ADR-014 / file-mgmt §5.2). This is the standard ignore-missing-imports for an
# intentionally-uninstalled third-party lib, not a weakening of strict on our own code.
[[tool.mypy.overrides]]
module = ["PIL.*", "pygments.*"]
ignore_missing_imports = true
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
# Coverage is NOT in addopts: it slows local runs and is only enforced in CI (the `pytest`
# job passes --cov + --cov-fail-under). Run `uv run pytest --cov=src/fathom` locally to see it.
addopts = "-ra"
pythonpath = ["."]
# ---------------------------------------------------------------------------
# Coverage (pytest-cov) — line coverage of the engine package. CI enforces a floor
# (--cov-fail-under) so the README "coverage ≥ N%" badge claims only what is mechanically
# guaranteed and never goes stale; tighten the floor as coverage rises.
# ---------------------------------------------------------------------------
[tool.coverage.run]
source = ["src/fathom"]
omit = ["*/__pycache__/*", "*/web/*"]
[tool.coverage.report]
show_missing = true
skip_covered = true
exclude_lines = [
"pragma: no cover",
"if TYPE_CHECKING:",
"raise NotImplementedError",
"@(abc\\.)?abstractmethod",
]
[dependency-groups]
dev = [
"import-linter>=2.13",
]
# ---------------------------------------------------------------------------
# Import-layering contract (AR-0011 / AR-0030). "The read path has no write capability" was a
# convention; this makes it a MECHANISM checked in CI. The metadata/full-bit reader and the storage
# backends must NEVER import the actor (the delete/quarantine write path) — so a read-only component
# can't be turned into a mutation surface (confused-deputy, STRIDE E-5). Run: `uv run lint-imports`.
# ---------------------------------------------------------------------------
[tool.importlinter]
root_package = "fathom"
[[tool.importlinter.contracts]]
name = "The read path (reader + backends) never imports the actor (write) path"
type = "forbidden"
source_modules = [
"fathom.agent.reader",
"fathom.backends",
]
forbidden_modules = [
"fathom.agent.actor",
]