fileroute 0.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.
Files changed (58) hide show
  1. fileroute-0.1.2/PKG-INFO +493 -0
  2. fileroute-0.1.2/README.md +472 -0
  3. fileroute-0.1.2/fileroute/__init__.py +13 -0
  4. fileroute-0.1.2/fileroute/auth/__init__.py +17 -0
  5. fileroute-0.1.2/fileroute/auth/google.py +178 -0
  6. fileroute-0.1.2/fileroute/auth/microsoft.py +126 -0
  7. fileroute-0.1.2/fileroute/auth/settings.py +220 -0
  8. fileroute-0.1.2/fileroute/auth/token_store.py +24 -0
  9. fileroute-0.1.2/fileroute/cli.py +40 -0
  10. fileroute-0.1.2/fileroute/clients/__init__.py +24 -0
  11. fileroute-0.1.2/fileroute/clients/base.py +59 -0
  12. fileroute-0.1.2/fileroute/clients/googledrive.py +963 -0
  13. fileroute-0.1.2/fileroute/clients/s3.py +301 -0
  14. fileroute-0.1.2/fileroute/clients/sharepoint.py +792 -0
  15. fileroute-0.1.2/fileroute/commands/__init__.py +9 -0
  16. fileroute-0.1.2/fileroute/commands/auth.py +156 -0
  17. fileroute-0.1.2/fileroute/commands/config.py +38 -0
  18. fileroute-0.1.2/fileroute/commands/descriptor.py +445 -0
  19. fileroute-0.1.2/fileroute/commands/diagram.py +63 -0
  20. fileroute-0.1.2/fileroute/commands/toolkit.py +134 -0
  21. fileroute-0.1.2/fileroute/descriptor.py +286 -0
  22. fileroute-0.1.2/fileroute/diagram.py +335 -0
  23. fileroute-0.1.2/fileroute/diagram_reports.py +258 -0
  24. fileroute-0.1.2/fileroute/exceptions.py +65 -0
  25. fileroute-0.1.2/fileroute/item.py +332 -0
  26. fileroute-0.1.2/fileroute/migration.py +157 -0
  27. fileroute-0.1.2/fileroute/models.py +179 -0
  28. fileroute-0.1.2/fileroute/transfer.py +262 -0
  29. fileroute-0.1.2/fileroute.egg-info/PKG-INFO +493 -0
  30. fileroute-0.1.2/fileroute.egg-info/SOURCES.txt +56 -0
  31. fileroute-0.1.2/fileroute.egg-info/dependency_links.txt +1 -0
  32. fileroute-0.1.2/fileroute.egg-info/entry_points.txt +2 -0
  33. fileroute-0.1.2/fileroute.egg-info/requires.txt +15 -0
  34. fileroute-0.1.2/fileroute.egg-info/top_level.txt +1 -0
  35. fileroute-0.1.2/pyproject.toml +154 -0
  36. fileroute-0.1.2/setup.cfg +4 -0
  37. fileroute-0.1.2/tests/test_activate.py +27 -0
  38. fileroute-0.1.2/tests/test_add.py +141 -0
  39. fileroute-0.1.2/tests/test_clients.py +44 -0
  40. fileroute-0.1.2/tests/test_clone.py +163 -0
  41. fileroute-0.1.2/tests/test_descriptor.py +120 -0
  42. fileroute-0.1.2/tests/test_diagram.py +102 -0
  43. fileroute-0.1.2/tests/test_diagram_reports.py +195 -0
  44. fileroute-0.1.2/tests/test_google_auth.py +173 -0
  45. fileroute-0.1.2/tests/test_google_auth_settings.py +102 -0
  46. fileroute-0.1.2/tests/test_googledrive_client.py +374 -0
  47. fileroute-0.1.2/tests/test_item.py +387 -0
  48. fileroute-0.1.2/tests/test_migration.py +107 -0
  49. fileroute-0.1.2/tests/test_models.py +33 -0
  50. fileroute-0.1.2/tests/test_release.py +177 -0
  51. fileroute-0.1.2/tests/test_resolution.py +145 -0
  52. fileroute-0.1.2/tests/test_s3_client.py +260 -0
  53. fileroute-0.1.2/tests/test_sharepoint_auth.py +80 -0
  54. fileroute-0.1.2/tests/test_sharepoint_auth_settings.py +114 -0
  55. fileroute-0.1.2/tests/test_sharepoint_client.py +465 -0
  56. fileroute-0.1.2/tests/test_transfers.py +345 -0
  57. fileroute-0.1.2/tests/test_update.py +268 -0
  58. fileroute-0.1.2/tests/test_yaml_roundtrip.py +72 -0
@@ -0,0 +1,493 @@
1
+ Metadata-Version: 2.4
2
+ Name: fileroute
3
+ Version: 0.1.2
4
+ Summary: Convenient connectors to make interoperating and collaborating across multiple content management systems easier
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pydantic>=2.10.6
8
+ Requires-Dist: pydantic-settings>=2.7.0
9
+ Requires-Dist: ruamel.yaml<0.20,>=0.19.1
10
+ Requires-Dist: requests>=2.32.3
11
+ Requires-Dist: python-dotenv>=1.0.1
12
+ Requires-Dist: typer>=0.24.1
13
+ Requires-Dist: boto3>=1.42.55
14
+ Requires-Dist: google-auth-oauthlib>=1.2.1
15
+ Requires-Dist: google-auth>=2.0
16
+ Requires-Dist: rich>=13.0
17
+ Requires-Dist: msal>=1.33.0
18
+ Provides-Extra: docs
19
+ Requires-Dist: mkdocs>=1.6.1; extra == "docs"
20
+ Requires-Dist: mkdocs-material>=9.7.1; extra == "docs"
21
+
22
+ # fileroute
23
+
24
+ Fileroute describes where project artifacts come from, where they live locally,
25
+ and where they should be published. A small YAML or JSON catalog connects local
26
+ files with SharePoint, Google Drive, and S3 URLs, so a document or data export
27
+ can keep its provenance and multiple destinations in one place. The CLI can
28
+ preview changes without credentials, visualize descriptor workflows as SVG, HTML,
29
+ or Markdown, pull remote inputs, and push supported outputs; the same descriptor, graph, and
30
+ transfer APIs are available from Python. Use it inside a project for repeatable,
31
+ versioned workflows, or run it as a standalone tool to inspect and retrieve
32
+ individual files. Python 3.11 or newer is required.
33
+
34
+ ## Choose how to run it
35
+
36
+ **Within a project:** add Fileroute to that project's dependencies, commit its
37
+ `uv.lock` and descriptor, and run commands from the project root. `uv run` uses
38
+ the project's environment and resolves its declared dependencies before running
39
+ the command. This is the better fit for automation and Python API imports.
40
+
41
+ ```bash
42
+ uv add git+https://github.com/mbkranz/fileroute.git@dev
43
+ uv run fileroute diagram config/fileroute.yaml
44
+ uv run fileroute pull config/fileroute.yaml --dry-run
45
+ uv run fileroute push config/fileroute.yaml --dry-run
46
+ ```
47
+
48
+ Use a commit SHA instead of `dev` in the Git dependency for a fixed revision,
49
+ or `uv add ./path/to/fileroute` when developing against a local checkout. See
50
+ uv's [project command guide](https://docs.astral.sh/uv/concepts/projects/run/)
51
+ and [dependency guide](https://docs.astral.sh/uv/concepts/projects/dependencies/).
52
+
53
+ **As a standalone tool:** `uvx` (an alias for `uv tool run`) runs Fileroute in
54
+ its own cached, disposable environment, separate from any project environment.
55
+ It reads descriptor paths and files from your current working directory, but it
56
+ does not add Fileroute to the project's dependencies or make it importable by
57
+ that project's Python code. Use this for ad hoc CLI operations:
58
+
59
+ ```bash
60
+ uvx --from git+https://github.com/mbkranz/fileroute.git@dev fileroute list config/fileroute.yaml
61
+ uvx --from git+https://github.com/mbkranz/fileroute.git@dev fileroute diagram config/fileroute.yaml
62
+ uvx --from git+https://github.com/mbkranz/fileroute.git@dev fileroute pull config/fileroute.yaml --dry-run
63
+ ```
64
+
65
+ Once the intended Fileroute distribution is available from your package index,
66
+ the shorter forms include `uvx fileroute diagram config/fileroute.yaml` and
67
+ `uvx fileroute list config/fileroute.yaml` (or `uvx fileroute --help`). For a
68
+ fixed tool version, pin the package version or Git commit. See uv's
69
+ [tool guide](https://docs.astral.sh/uv/concepts/tools/).
70
+
71
+ To develop this repository itself:
72
+
73
+ ```bash
74
+ uv sync
75
+ uv run fileroute --help
76
+ ```
77
+
78
+ Configure credentials using `.env-sample`. SharePoint uses the `AZURE_*` and
79
+ `SHAREPOINT_*` settings; Google supports ADC, service accounts, and user OAuth;
80
+ S3 uses the standard AWS credential chain. Descriptor loading, diagramming, and
81
+ dry runs do not authenticate.
82
+
83
+ ```bash
84
+ fileroute auth login gdrive
85
+ fileroute auth login microsoft --auth-mode delegated
86
+ fileroute auth login sharepoint --auth-mode delegated
87
+ ```
88
+
89
+ See [descriptor diagrams](docs/diagram.md),
90
+ [Google authentication](docs/google-auth.md), and the generated
91
+ [CLI](docs/cli.md) and [Python API](docs/api.md) references.
92
+
93
+ ## Descriptor model
94
+
95
+ Four models define the public descriptor API: `Catalog`, `Resource`, `Location`,
96
+ and `CatalogReference`. A catalog groups resources and nested catalogs. A
97
+ resource describes one artifact. Sources and targets are lists of locations.
98
+
99
+ | Field | Meaning |
100
+ | --- | --- |
101
+ | `path` | Artifact location; a local file/directory for transfers |
102
+ | `sources` | Upstream inputs or provenance, including local authoring files |
103
+ | `targets` | Downstream publication destinations |
104
+ | `serviceType` | Optional location provider: `GoogleDrive`, `SharePoint`, or `S3` |
105
+ | `serviceId` | Optional provider-native identifier on a location |
106
+ | `$ref` | Another local catalog document, resolved beside its containing document |
107
+
108
+ For example, a rendered Word document can identify its Quarto source without
109
+ making that source a publication destination:
110
+
111
+ ```yaml
112
+ $schema: fileroute-catalog
113
+ catalogs:
114
+ - name: documentation
115
+ path: docs/_output
116
+ targets:
117
+ - path: https://contoso.sharepoint.com/sites/dev/Shared%20Documents/Docs
118
+ serviceType: SharePoint
119
+ resources:
120
+ - name: guide
121
+ path: docs/_output/guide.docx
122
+ sources:
123
+ - path: docs/guide.qmd
124
+ - name: internal
125
+ path: docs/_output/internal.docx
126
+ targets: []
127
+ ```
128
+
129
+ A retrieval descriptor explicitly identifies its remote source:
130
+
131
+ ```yaml
132
+ resources:
133
+ - name: source-export
134
+ path: downloads/source.csv
135
+ sources:
136
+ - path: s3://my-bucket/exports/source.csv
137
+ ```
138
+
139
+ All artifact paths are relative to the **working directory**, even when the
140
+ descriptor is in `config/` or references another descriptor. The Python planners
141
+ also accept an explicit `root`. Paths are preserved when loading and saving.
142
+ Transfers reject paths outside that root and symbolic links.
143
+
144
+ Reference paths are the exception: `$ref` is relative to the containing
145
+ **descriptor's directory**. References stay references on normal load/save. Transfer planning explicitly
146
+ expands them once; they cannot escape the containing directory. Cycles raise an error.
147
+
148
+ ```yaml
149
+ catalogs:
150
+ - name: research
151
+ $ref: catalogs/research.yaml
152
+ ```
153
+
154
+ Unrecognized metadata fields survive model round trips. Existing YAML descriptors
155
+ are round-trip edited where practical, retaining comments and authored styles.
156
+ This is a Fileroute format inspired by Data Package
157
+ and DCAT, not a full implementation of either standard. `$schema` is an optional
158
+ profile label; loading does not fetch a schema from the network.
159
+
160
+ ## Common use cases
161
+
162
+ Each resource has one local artifact `path`. Its `sources` record where the
163
+ artifact came from; its `targets` list every intended publication destination.
164
+ One resource can have multiple targets. `pull` downloads **one remote source**
165
+ to `path`, and `push` uploads the file at `path` to **all targets**. Run them as
166
+ separate steps; Fileroute does not stream directly between cloud providers or
167
+ convert source formats. These examples can be saved as `config/fileroute.yaml`.
168
+
169
+ ### One SharePoint source and two SharePoint targets
170
+
171
+ Retrieve a report to the project, then publish copies to two SharePoint sites:
172
+
173
+ ```yaml
174
+ resources:
175
+ - name: monthly-report
176
+ path: artifacts/monthly-report.csv
177
+ sources:
178
+ - path: https://contoso.sharepoint.com/sites/data/Shared%20Documents/monthly-report.csv
179
+ targets:
180
+ - path: https://contoso.sharepoint.com/sites/reports/Shared%20Documents/monthly-report.csv
181
+ - path: https://contoso.sharepoint.com/sites/archive/Shared%20Documents/monthly-report.csv
182
+ ```
183
+
184
+ For this supported combination, run `fileroute pull config/fileroute.yaml`
185
+ and then `fileroute push config/fileroute.yaml`, with `--dry-run` on either
186
+ command to inspect its plan first. Each SharePoint site needs working access.
187
+
188
+ ### SharePoint source and Google Drive target
189
+
190
+ Describe retrieval from SharePoint and intended publication to Google Drive:
191
+
192
+ ```yaml
193
+ resources:
194
+ - name: partner-report
195
+ path: artifacts/partner-report.csv
196
+ sources:
197
+ - path: https://contoso.sharepoint.com/sites/data/Shared%20Documents/partner-report.csv
198
+ targets:
199
+ - path: https://drive.google.com/file/d/GOOGLE_FILE_ID/view
200
+ ```
201
+
202
+ ### SharePoint source, SharePoint and Google Drive targets
203
+
204
+ Use the same local copy for a supported SharePoint publication and a planned
205
+ Google Drive publication:
206
+
207
+ ```yaml
208
+ resources:
209
+ - name: partner-report
210
+ path: artifacts/partner-report.csv
211
+ sources:
212
+ - path: https://contoso.sharepoint.com/sites/data/Shared%20Documents/partner-report.csv
213
+ targets:
214
+ - path: https://contoso.sharepoint.com/sites/reports/Shared%20Documents/partner-report.csv
215
+ - path: https://drive.google.com/file/d/GOOGLE_FILE_ID/view
216
+ ```
217
+
218
+ ### Local authoring source with SharePoint, Google Drive, and S3 targets
219
+
220
+ Keep the input document as provenance while publishing its rendered output:
221
+
222
+ ```yaml
223
+ resources:
224
+ - name: guide
225
+ path: docs/_output/guide.docx
226
+ sources:
227
+ - path: docs/guide.qmd
228
+ targets:
229
+ - path: https://contoso.sharepoint.com/sites/docs/Shared%20Documents/guide.docx
230
+ - path: https://drive.google.com/file/d/GOOGLE_FILE_ID/view
231
+ - path: s3://example-docs/guide.docx
232
+ ```
233
+
234
+ Render `docs/guide.qmd` to `docs/_output/guide.docx` with Quarto before
235
+ publishing. A local source is provenance; `pull` does not render or copy it.
236
+
237
+ **Current transfer support:** SharePoint, Google Drive, and S3 remote sources
238
+ can be pulled; only SharePoint targets can be pushed. A descriptor with a Google
239
+ Drive or S3 target is valid metadata, but `push` **and `push --dry-run` fail at
240
+ planning** until upload support is implemented. If you need the SharePoint
241
+ destination now, put it in a separate descriptor (or remove the unsupported
242
+ targets) for that run. `resolve` and `diagram` can still resolve those providers
243
+ and inspect the descriptor without authentication. Source and target URLs above
244
+ are examples; replace them with your own accessible files and sites.
245
+
246
+ ## Visualize file workflows
247
+
248
+ `diagram` renders descriptor intent rather than executing a transfer. It expands
249
+ local `$ref` catalogs, resolves known provider URLs in memory, applies catalog
250
+ target inheritance, and draws sources → artifacts → targets as a standalone SVG.
251
+ It does not require Graphviz or another rendering dependency.
252
+
253
+ ```bash
254
+ fileroute diagram config/fileroute.yaml
255
+ fileroute diagram config/fileroute.yaml --output docs/fileroute-workflow.svg
256
+ fileroute diagram config/fileroute.yaml --output docs/fileroute-workflow.html
257
+ fileroute diagram config/fileroute.yaml --output docs/fileroute-workflow.md --detail full
258
+ ```
259
+
260
+ HTML provides a searchable offline file dictionary linked to diagram nodes; Markdown
261
+ writes an anchored dictionary and companion SVG. `--detail summary` exports curated
262
+ metadata; `--detail full` includes custom fields and service IDs. Inherited targets
263
+ identify their declaring catalog. Reports link to HTTP(S) locations but do not
264
+ preview file contents or authenticate. See the diagram guide for anchor stability
265
+ and duplicate-identity behavior.
266
+
267
+ The descriptor argument follows the same saved/default selection behavior as
268
+ `pull`, `push`, and `resolve`, so after `fileroute activate` you can simply run
269
+ `fileroute diagram`. The default output is `fileroute-diagram.svg` in the
270
+ current working directory. See [Descriptor diagrams](docs/diagram.md) for the
271
+ Python `DescriptorGraph` API and repository-integration guidance.
272
+
273
+ ## Commands
274
+
275
+ ```bash
276
+ fileroute add export --path downloads/source.csv --source s3://my-bucket/source.csv
277
+ fileroute add documentation --catalog --path docs/_output --target https://contoso.sharepoint.com/sites/dev/Docs
278
+ fileroute list config/fileroute.yaml --format json
279
+ fileroute activate config/fileroute.yaml
280
+ fileroute update --name documentation --title "Published documentation"
281
+ fileroute resolve config/fileroute.yaml
282
+ fileroute resolve config/fileroute.yaml --write
283
+ fileroute diagram config/fileroute.yaml
284
+ fileroute migrate old.yaml new.yaml --direction push
285
+ fileroute pull config/fileroute.yaml --dry-run
286
+ fileroute push config/fileroute.yaml --dry-run
287
+ fileroute push config/fileroute.yaml
288
+ ```
289
+
290
+ `activate` saves the active descriptor in `.fileroute/descriptor`.
291
+ The optional descriptor argument also accepts an explicit override. `update`
292
+ selects an exact name or dot-path and reports ambiguous names; `clone descriptor`
293
+ copies a single authored document. For a resource with one source,
294
+ `update --name export --service-type S3` edits that source's provider.
295
+ Edit `sources`/`targets` in YAML for more involved changes, or supply a JSON array
296
+ using `--sources`/`--targets`.
297
+
298
+ ### Resolve URLs
299
+
300
+ `resolve` previews canonical JSON without authenticating or contacting a remote
301
+ service. `resolve --write` saves the result to the selected YAML/JSON descriptor.
302
+ It infers `serviceType` from `s3://` URLs, `drive.google.com` / `docs.google.com`,
303
+ and SharePoint hosts, while preserving the original URL exactly as a clickable
304
+ link. For example:
305
+
306
+ ```yaml
307
+ path: https://contoso.sharepoint.com/sites/dev/Docs/guide.docx
308
+ serviceType: SharePoint
309
+ ```
310
+
311
+ A conflicting explicit provider is an error. Unrecognized target URLs require
312
+ an explicit `serviceType`; local files and general web citations remain valid
313
+ provider-less sources. Pull requires a supported remote source. Resolution
314
+ does not follow redirects, fetch remote IDs, or check remote permissions.
315
+
316
+ Write-back updates only the selected document and preserves `$ref` entries;
317
+ resolve referenced descriptors separately to persist their inferred metadata.
318
+ Repeated resolution is idempotent. Existing YAML comments and styles are retained
319
+ where practical; byte-for-byte whitespace preservation is not guaranteed.
320
+ Transfers expand references and resolve the relevant sources or targets in
321
+ memory, so write-back is optional. `push --dry-run` and `pull --dry-run` provide
322
+ concrete transfer plans; there is no separate `plan` command or stored lockfile.
323
+
324
+ ### Push
325
+
326
+ `push` publishes `path` to `targets`.
327
+ Sources are never treated as targets. Only SharePoint uploads are currently
328
+ implemented. Other providers fail during planning, before authentication.
329
+
330
+ - Catalog targets denote existing remote folders. Children inherit those
331
+ targets using paths relative to the declaring catalog's `path` (or the working
332
+ root when no catalog path is given).
333
+ - A child's explicit `targets` replace inherited targets. `targets: []` disables
334
+ publication for that child and its descendants unless a descendant declares
335
+ its own targets.
336
+ - A catalog with children publishes only its declared children. A leaf catalog
337
+ with a path publishes all files in that directory tree.
338
+ - Explicit resource targets are exact file URLs, allowing renaming. Set a
339
+ target's `entityType: Directory` to append the local filename to a folder URL.
340
+ - Multiple targets publish multiple copies. Conflicting files aimed at the same
341
+ destination fail before any transfer.
342
+
343
+ The planner checks all inputs before authentication. SharePoint transfers
344
+ create missing child folders and create/replace files; they never delete remote
345
+ files. Files above Microsoft Graph's 250 MB single-request limit fail planning.
346
+ Dry runs show the proposed destinations without contacting the remote service;
347
+ they cannot verify remote permissions or folder existence.
348
+
349
+ ### Pull
350
+
351
+ `pull` materializes one remote `sources` entry into each artifact's `path`.
352
+ A leaf catalog can retrieve a whole remote directory. A catalog with children
353
+ retrieves only those children. Targets never affect retrieval.
354
+
355
+ Multiple sources may represent a transformation, so pull refuses to choose one.
356
+ Local provenance such as `.qmd` inputs is not a download operation: build those
357
+ artifacts with their authoring tool, then push them. Use separate catalogs for
358
+ retrieval and publication when their source semantics differ. Dry runs plan
359
+ remote directories as units; execution enumerates and checks remote file paths
360
+ before writing any files. Pull replaces existing local files at planned paths.
361
+
362
+ ## Python API and architecture
363
+
364
+ ```python
365
+ from pathlib import Path
366
+ from fileroute import Catalog, Resource, Location
367
+ from fileroute.descriptor import save
368
+ from fileroute.diagram import load_graph, render_svg
369
+ from fileroute.transfer import plan_push, push
370
+
371
+ catalog = Catalog(resources=[Resource(
372
+ name="guide", path="docs/guide.docx",
373
+ sources=[Location(path="docs/guide.qmd")],
374
+ targets=[Location(path="https://contoso.sharepoint.com/sites/dev/Docs/guide.docx")],
375
+ )])
376
+ save(catalog, "config/fileroute.yaml")
377
+ graph = load_graph("config/fileroute.yaml")
378
+ render_svg(graph, "fileroute-diagram.svg")
379
+ plan = plan_push(Path("config/fileroute.yaml"), root=Path.cwd())
380
+ # Inspect plan before transfer.
381
+ push(plan)
382
+ ```
383
+
384
+ The core has one module per responsibility:
385
+
386
+ - `models.py`: declarative `Catalog`, `Resource`, `Location`, `CatalogReference`
387
+ and `ServiceType` validation.
388
+ - `descriptor.py`: `load`, `save`, `walk`, `find`, and offline `resolve`.
389
+ - `diagram.py`: semantic descriptor graphs and dependency-free SVG rendering.
390
+ - `diagram_reports.py`: offline HTML inspector and Markdown file dictionary.
391
+ - `migration.py`: explicit one-way conversion of legacy descriptors.
392
+ - `transfer.py`: `plan_pull` / `plan_push`, then `pull` / `push` execution.
393
+ - `item.py`: runtime `ServiceItem` hierarchy.
394
+ - `clients/sharepoint.py`, `clients/googledrive.py`, `clients/s3.py`: provider APIs.
395
+ - `auth/` and `commands/`: credential handling and CLI workflows.
396
+
397
+ After resolution, `location.service_type` is the authoritative `ServiceType`
398
+ enum used directly for provider dispatch. Pydantic accepts aliases such as
399
+ `gdrive` or `google_drive`; saved values are `GoogleDrive`, `SharePoint`, or `S3`.
400
+ Python attributes use `service_type`, `service_id`, and `entity_type`; descriptors
401
+ use `serviceType`, `serviceId`, and `entityType`. No provider-string conversion
402
+ layer or dynamic registry is needed.
403
+
404
+ `ServiceItem` and provider clients retain their runtime roles. Clients own
405
+ provider-specific authentication and HTTP behavior; runtime items expose
406
+ `refresh()`, `children`, `get_path()`, `iter_items()`, `iter_files()`, and
407
+ `download()`. `item.to_catalog()` emits canonical artifact paths and sources.
408
+ Runtime paths remain relative to the provider container. `get_path()` is
409
+ relative to the current item. Parent relationships and recursive traversal use
410
+ a snapshot until refresh or mutation invalidates it.
411
+
412
+ ## Breaking API changes
413
+
414
+ Use the four models above and the functions in `descriptor`, `diagram`, and
415
+ `transfer`. Model I/O and traversal methods, `upload.py`, `download.py`,
416
+ `helpers.py`, and the provider registry have been removed. The CLI has no
417
+ `upload` alias or `set` command. Run `activate` again to select a descriptor
418
+ using the new single-path selection file; obsolete saved workflow defaults are
419
+ no longer read.
420
+
421
+ Legacy `Drive*` classes, packages, `_cache`, and artifact-level provider fields
422
+ are unsupported. Update authored descriptors to `path`, `sources`, `targets`,
423
+ `resources`, and `catalogs`; provider metadata belongs on a location. Normal loading has no compatibility shims or automatic legacy conversion. Use
424
+ `fileroute migrate OLD NEW --direction pull|push` for an explicit, one-way
425
+ conversion that preserves the input file.
426
+
427
+ ## Development
428
+
429
+ ```bash
430
+ poe check
431
+ poe docs-check
432
+ poe docs-update
433
+ poe docs-build
434
+ poe docs-serve
435
+ ```
436
+
437
+ Install the task runner with `uv tool install poethepoet==0.48.0`, or use
438
+ `uvx --from poethepoet==0.48.0 poe <task>` for a one-off run. `poe docs-build`
439
+ installs the documentation extra through `uv run`.
440
+
441
+ Provider tests use mocks; they do not prove live tenant permissions or transfers.
442
+
443
+ Field naming references: [Data Resource](https://datapackage.org/standard/data-resource/),
444
+ [DCAT](https://www.w3.org/TR/vocab-dcat-3/), and
445
+ [OpenMetadata Drive Service](https://docs.open-metadata.org/latest/main-concepts/metadata-standard/schemas/entity/services/driveservice).
446
+
447
+ ## Package releases
448
+
449
+ Install the repository task runner with `uv tool install poethepoet==0.48.0`,
450
+ then run `poe release-check` for validation and `poe build` for a local wheel
451
+ and source distribution. One-off usage is
452
+ `uvx --from poethepoet==0.48.0 poe release-check`.
453
+
454
+ The `publish-to-pypi.yaml` workflow has three jobs: **prepare**, **publish**, and
455
+ **release**. Pushes to `dev` start or increment a patch development version
456
+ (`1.0.0 → 1.0.1.dev1 → 1.0.1.dev2`). Pushes to `main` promote a prerelease to
457
+ stable, or increment the patch when the source already has a stable version.
458
+ After a stable tag exists, `dev` starts the next patch series. Normal releases
459
+ should not edit the version manually. Merge updated `main` back into `dev`
460
+ when needed to keep the branches' version baselines aligned.
461
+
462
+ `poe release --branch dev --source <full-commit-sha>` runs `scripts/release.py`
463
+ in a clean checkout of that source. It uses `uv version --no-sync` to update
464
+ `pyproject.toml` and `uv.lock`, invokes `poe build`, and creates a local release
465
+ commit and annotated tag. This is the CI preparation command: it changes the
466
+ local checkout but does not push or publish. The workflow first saves the built
467
+ distributions, then atomically pushes the release commit and tag. Both branches
468
+ share one release concurrency group; a stale run fails rather than overwriting
469
+ newer work. Rapid pushes may supersede pending runs; the latest source should
470
+ be released. Workflow pushes use `GITHUB_TOKEN` and do not recursively trigger
471
+ another push workflow.
472
+
473
+ PyPI publication uses Trusted Publishing bound to this repository and
474
+ **`publish-to-pypi.yaml`**. No long-lived PyPI token is needed, and the publish
475
+ job does not require a GitHub environment unless the PyPI publisher is configured
476
+ to expect one. Only stable `main` versions get a GitHub Release, after PyPI succeeds.
477
+ The branch rules must permit the workflow's version commit; rejected pushes
478
+ leave both remote refs unchanged.
479
+
480
+ For a failed publish, use **Re-run failed jobs** on the original Actions run.
481
+ The publish job downloads the saved wheel and source distribution without
482
+ rebuilding. Identical PyPI uploads can be retried, including a partially
483
+ completed upload. A full rerun recognizes the tagged source/branch before
484
+ calculating a version and finds its unexpired artifact in the original workflow
485
+ run by source commit. Existing GitHub Releases
486
+ are left intact. If preparation failed before the atomic push, a fresh attempt
487
+ can rebuild and replace that run's unpublished artifact. If the original
488
+ artifact has expired or been deleted after the push, stop and recover those
489
+ exact files; the workflow deliberately does not rebuild a published version.
490
+
491
+ `poe release-test` exercises the release helper with temporary local Git remotes
492
+ and real uv version changes; distribution builds are mocked. No test publishes
493
+ packages or contacts cloud providers.