statcatlib 0.17.0__tar.gz → 0.18.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.
- {statcatlib-0.17.0 → statcatlib-0.18.0}/PKG-INFO +1 -1
- {statcatlib-0.17.0 → statcatlib-0.18.0}/pyproject.toml +1 -1
- {statcatlib-0.17.0 → statcatlib-0.18.0}/pyproject.toml.orig +1 -1
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/EGM.md +150 -9
- statcatlib-0.18.0/src/statcatlib/access.py +435 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/cli/EGM.md +30 -10
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/cli/__init__.py +151 -8
- statcatlib-0.18.0/src/statcatlib/release.py +1612 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/EGM.md +4 -2
- {statcatlib-0.17.0 → statcatlib-0.18.0}/LICENSE +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/README.md +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/__init__.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/admin.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/bundle.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/catalog.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/checks.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/diagnostics.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/embargo.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/expr.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/gc.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/jobs.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/EGM.md +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/__init__.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/agents.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/catalog.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/classification.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/dataset.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/distribution.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/licence.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/quality.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/skos.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/spatial.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/temporal.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/model/types.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/py.typed +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/query.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/sources/EGM.md +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/sources/__init__.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/sources/ckan.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/sources/sdmx.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/__init__.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/blob.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/cross_product.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/inventory.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/lance.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/row.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/schema.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/scopes.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/sentinel.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/store.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/working_copy.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/store/xhtml.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/testing.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/upload.py +0 -0
- {statcatlib-0.17.0 → statcatlib-0.18.0}/src/statcatlib/urn.py +0 -0
|
@@ -18,7 +18,8 @@ products (`catalog.py`), the operational changes to its Inventory
|
|
|
18
18
|
an sdmxlib edition manifest (`bundle.py`), the integrity and support
|
|
19
19
|
checks behind `statcat verify-catalog` and `statcat doctor`
|
|
20
20
|
(`diagnostics.py`), the copy of a sealed edition to object storage (`upload.py`), the
|
|
21
|
-
jobs configuration the release and GC jobs read (`jobs.py`), the GC job (`gc.py`),
|
|
21
|
+
jobs configuration the release and GC jobs read (`jobs.py`), the GC job (`gc.py`), the release job
|
|
22
|
+
(`release.py`) and its access precondition (`access.py`), composable query filters
|
|
22
23
|
(`query.py`), traversal expressions (`expr.py`), SDMX URN builders (`urn.py`)
|
|
23
24
|
and test catalogs (`testing.py`).
|
|
24
25
|
|
|
@@ -231,8 +232,9 @@ re-exported from `statcatlib`. Standard library and `attrs` only.
|
|
|
231
232
|
`namespace`, `deployments` or `port`, a port outside 1 to 65535, or a failed
|
|
232
233
|
inequality.
|
|
233
234
|
- `JobsConfig(retention, access, expiry, sha256, format_version=1)`: `access` is
|
|
234
|
-
`None` when the file has no `[access]
|
|
235
|
-
when it has no `[expiry]`; `sha256` is the file's,
|
|
235
|
+
`None` when the file has no `[access]`, which `ReleaseJob` refuses; `expiry` is
|
|
236
|
+
`ExpiryPolicy()` (disabled) when it has no `[expiry]`; `sha256` is the file's,
|
|
237
|
+
which each run logs.
|
|
236
238
|
- `RetentionPolicy(backup, gc_window, tombstone_window, inventory_window,
|
|
237
239
|
retire_after, pod_staleness, keep_roots=DEFAULT_KEEP_ROOTS (10),
|
|
238
240
|
upload_grace=DEFAULT_UPLOAD_GRACE (24 h))`, `durations()`, and `check()`,
|
|
@@ -307,6 +309,116 @@ The GC job (Phase 5 D10), imported from `statcatlib.gc`; nothing is re-exported
|
|
|
307
309
|
alerts (`event`, `severity`, `at`, `catalog`, and `product`, `edition`, `reason`,
|
|
308
310
|
`detail` when they apply).
|
|
309
311
|
|
|
312
|
+
### `release.py`
|
|
313
|
+
|
|
314
|
+
The release job (Phase 5 Part 3), imported from `statcatlib.release`; nothing is
|
|
315
|
+
re-exported from `statcatlib`. `statcat release` runs it.
|
|
316
|
+
|
|
317
|
+
- `ReleaseJob(location, config, *, rest, listing, storage=None)`: *config* is a
|
|
318
|
+
`JobsConfig` whose `access` is set and names the container of `location.uri`
|
|
319
|
+
(`az://<container>/...`) and, when the store's config names `account_name`, that
|
|
320
|
+
account (`JobsConfigError` otherwise); *rest* has `head(path) -> RemoteFile | None`
|
|
321
|
+
(`AzureBlobRest`); *listing* is an `access.ReaderListing`; *storage* defaults to
|
|
322
|
+
`BlobInventoryStorage(location)`. It holds one `InventoryWriter` over the storage.
|
|
323
|
+
- `run(*, dry_run=False) -> ReleaseReport`: step 0 reads `catalog.json`
|
|
324
|
+
(`FileNotFoundError` when absent), refuses an opted-in catalog with
|
|
325
|
+
`InventoryWriteRefusedError` naming Part 6, and reads `storage.now()`. Then, each
|
|
326
|
+
through `_step`, which catches `_STEP_ERRORS` (`BlobStoreError`, `ManifestError`,
|
|
327
|
+
`InventoryError`, `InventoryConflictError`, `InventoryContentionError`,
|
|
328
|
+
`InventoryRuleError`, `IncompatibleCatalogError`, `OSError`), logs `step.failed`
|
|
329
|
+
and goes on: `_advance` (`InventoryWriter.advance_current`, `current.behind`; left
|
|
330
|
+
out of a dry run); `_access`, outside `_step` (`listing.readers()` and
|
|
331
|
+
`access.check_access`; an `AccessListingError` becomes a failed `AccessResult`;
|
|
332
|
+
any other error stops the run); `_fold`; `_release`; `_retire`. Each of the last
|
|
333
|
+
three reads `storage.now()` once and commits through `InventoryWriter.commit` with
|
|
334
|
+
a *change* that plans on the head it is given and returns `None` when nothing
|
|
335
|
+
changes; a dry run calls the plan on `writer.full()` and commits nothing.
|
|
336
|
+
- The fold (`_plan_fold`, kind `seal`, reason `fold of pending/ registrations`):
|
|
337
|
+
`full.leaves()`, one LIST of `pending/` a run, then for each
|
|
338
|
+
`pending/<p>/<e>.json` the head neither records in a leaf nor tombstones, in path
|
|
339
|
+
order, `_judge`: `_read` (kept by path for the run: the document through
|
|
340
|
+
`jobs.decode_registration`, `too-old` against `tombstone_window`, the manifest
|
|
341
|
+
through `decode_manifest` and `blob._refuse_unsafe_files`, `canonical-form`,
|
|
342
|
+
`files` through `verify_remote` over `upload._ALL_TIERS` and `upload._lookup`),
|
|
343
|
+
then `fold_lifecycle`, `_kind`, `chain` and `_refuse_changed_final`. A recorded
|
|
344
|
+
registration is skipped by its path and never read; an object of another shape
|
|
345
|
+
under `pending/` is ignored. A `BlobStoreError` is the registration's `store`
|
|
346
|
+
refusal. `_check_structures` runs `_reference_check` for each candidate with no
|
|
347
|
+
binding (`pull_edition` into the run's temporary directory, each home through
|
|
348
|
+
`structure_entries`, `check_edition(at="receive", remote=, lookup=, structures=,
|
|
349
|
+
extensions={"statcat": ...})`), the fold's structure editions together until a
|
|
350
|
+
pass refuses none, and keeps each answer by edition and homes given.
|
|
351
|
+
`_refuse_forks` refuses passing registrations of one product, `previous` and
|
|
352
|
+
schema (`pending-fork`). `_record_stable` records per product in id order
|
|
353
|
+
(`_record`: `fold_lifecycle` on the evolving leaf, sdmxlib's `record(embargo=True)`
|
|
354
|
+
with `recorded_routing(routing_fields(manifest))`, `supersede_pending` for each
|
|
355
|
+
embargoed entry with its `previous` and schema), runs `drop_until_stable`, refuses
|
|
356
|
+
every registration of a dropped product, and refuses with `references` a product
|
|
357
|
+
whose check was given a fold edition that is refused, until a pass refuses none.
|
|
358
|
+
`_release_at` records `None` for a `release_at` before the registration's
|
|
359
|
+
last-modified time (`fold.release_at_past`) and, on a restore epoch above 0, for a
|
|
360
|
+
registration last modified before the epoch's first root (`fold.restored`), which
|
|
361
|
+
`_find_epoch` takes from the head's record or finds by reading roots back from the
|
|
362
|
+
head while they carry the epoch.
|
|
363
|
+
- The release step (`_plan_release`, kind `release`, reason `release_due at <time>`):
|
|
364
|
+
`full.leaves()`, then, when the head's `embargo` is off and the access result
|
|
365
|
+
passed, sdmxlib's `release_due` and `drop_until_stable`; the record from
|
|
366
|
+
`refusal_record`; a `Change` when a leaf or the record differs. It logs
|
|
367
|
+
`release.refused`, `release.held` (embargo on), `release.unprepared` (released and
|
|
368
|
+
folded by this run) and `release.overdue` (every entry still embargoed with a
|
|
369
|
+
`release_at` at or before the step's time). A failed fold makes the record keep
|
|
370
|
+
the head's fold refusals.
|
|
371
|
+
- Retirement (`_retire`, kind `retire`): `inventory.retire_change(full, now -
|
|
372
|
+
retire_after, now=, artefacts=writer.artefacts)`.
|
|
373
|
+
- `ReleaseReport(folded, released, retired, refused, skipped, failed, versions,
|
|
374
|
+
access, dry_run)` and `ReleaseRefusal(product_id, edition_id, step, rule, reason,
|
|
375
|
+
since)`, frozen; `Step` is `fold` or `release`.
|
|
376
|
+
- `refusal_record(full, refusals, now, *, epoch=(0, None))`: the next root's
|
|
377
|
+
`extensions`, with `statcat.release` set to `{refused, omitted, epoch_started_at,
|
|
378
|
+
restore_epoch}` and `format_version` to `bundle.ROOT_SECTION_FORMAT`; `since`
|
|
379
|
+
carried from the head's record by `(product_id, edition_id, step)`; at most
|
|
380
|
+
`MAX_REFUSALS` (500) kept, the oldest `since` first; the head's sections unchanged
|
|
381
|
+
when there is no refusal, no epoch time and no record. `current_record(full)` is
|
|
382
|
+
the head's `extensions`, and `current_refusals(full)` the refusals its record
|
|
383
|
+
holds, leaving out an entry it cannot read. `RELEASE_KEY` is `"release"`.
|
|
384
|
+
- `drop_until_stable(writer, full, leaves) -> (kept, dropped)`: reads each incoming
|
|
385
|
+
entry's manifest through `writer.artefacts` (`ManifestError` drops the product with
|
|
386
|
+
`manifest`, `BlobStoreError` with `store`), then `writer.refusals`, dropping each
|
|
387
|
+
refused product's whole change until a pass refuses none. `Dropped(edition_id,
|
|
388
|
+
rule, reason)`. A manifest of another product's unfilled entry that cannot be
|
|
389
|
+
read propagates.
|
|
390
|
+
- `structure_entries(full, *, product_id, batch) -> list[StructureEntry]`:
|
|
391
|
+
`working_copy.structure_editions`'s choice as `StructureEntry(product_id,
|
|
392
|
+
edition_id, manifest_sha256)` values.
|
|
393
|
+
- Events go through `jobs._emit` on the `statcatlib.release` logger: `event`,
|
|
394
|
+
`severity`, `at` (the step's storage time), `catalog`, and `product`, `edition`,
|
|
395
|
+
`rule`, `reason`, `detail` where they apply.
|
|
396
|
+
|
|
397
|
+
### `access.py`
|
|
398
|
+
|
|
399
|
+
The release job's access precondition (Phase 5 D8), imported from
|
|
400
|
+
`statcatlib.access`. `httpx`, the standard library and `attrs` only.
|
|
401
|
+
|
|
402
|
+
- `ReaderListing` (a `Protocol`): `readers() -> ReaderReport`, raising
|
|
403
|
+
`AccessListingError` (`RuntimeError`) when the listing cannot be made.
|
|
404
|
+
- `ReaderReport(assignment_ids, grants)` and `ReaderGrant(assignment_id,
|
|
405
|
+
principal_id, principal_type, role, scope)`, frozen.
|
|
406
|
+
- `check_access(report, allowlist, sentinels) -> AccessResult(passed, outsiders,
|
|
407
|
+
unused, missing_sentinels, errors)`: passed when every grant's principal is on
|
|
408
|
+
the allowlist and every sentinel id is listed; ids compare without regard to case.
|
|
409
|
+
- `AzureReaderListing(storage_account, container, *, arm_token, scopes=())`:
|
|
410
|
+
`readers()` sends, with the bearer token *arm_token* returns for `ARM_SCOPE`,
|
|
411
|
+
`GET https://management.azure.com{scope}/providers/Microsoft.Authorization/roleAssignments?$filter=atScope()&api-version=2022-04-01`
|
|
412
|
+
at `{storage_account}/blobServices/default/containers/{container}` and at each of
|
|
413
|
+
*scopes*, following each `nextLink` on that host and refusing one on another;
|
|
414
|
+
then one `GET {storage_account}/providers/Microsoft.Authorization/roleDefinitions/{guid}`
|
|
415
|
+
per distinct role. A 429 or 5xx is tried again after each of `_BACKOFF` (1, 2 and
|
|
416
|
+
4 s); any other failure raises `AccessListingError`. A resource id that does not
|
|
417
|
+
start with `/`, or a container that is empty or holds `/`, raises `ValueError`.
|
|
418
|
+
- `reads_blobs(permissions) -> bool`: whether one permission block's `dataActions`
|
|
419
|
+
match `BLOB_READ` and none of its `notDataActions` does, `*` matching any run of
|
|
420
|
+
characters, case ignored.
|
|
421
|
+
|
|
310
422
|
### `checks.py`
|
|
311
423
|
|
|
312
424
|
- `SealCheck`: the type `Callable[[EditionView], Iterable[Finding]]`, passed
|
|
@@ -369,8 +481,9 @@ The module keeps its 0.11 name and describes editions. Imported from
|
|
|
369
481
|
refuses a store other than `AzureStore` (`TypeError`, naming W3) and a store with
|
|
370
482
|
no `TokenIdentity` (`ValueError`), then runs `_upload` with an `AzureBlobRest`.
|
|
371
483
|
- `_upload(edition_dir, location, *, release_at=None, rest)`: the steps, over any
|
|
372
|
-
store and any digest client with `head(path) -> RemoteFile | None`. Tests
|
|
373
|
-
`
|
|
484
|
+
store and any digest client with `head(path) -> RemoteFile | None`. Tests call it,
|
|
485
|
+
through `tests/stores/_stores.py`'s `upload` and `record_uploaded`. A naive
|
|
486
|
+
`release_at` raises `ValueError`.
|
|
374
487
|
- `UploadReport(edition_id, product_id, sent, kept, registered)`: `sent` and `kept`
|
|
375
488
|
hold store paths, and `registered` is advisory, since a retried create can report
|
|
376
489
|
false against its own earlier success.
|
|
@@ -533,8 +646,9 @@ read.
|
|
|
533
646
|
- A library call never takes a credential from the environment: `Catalog.open` over a
|
|
534
647
|
store takes a `BlobLocation` the caller built. `catalog.py` and `store/blob.py` read
|
|
535
648
|
no environment variable.
|
|
536
|
-
- Only `CatalogAdmin`
|
|
537
|
-
|
|
649
|
+
- Only `CatalogAdmin` and, on a catalog in object storage, the release job
|
|
650
|
+
(`release.ReleaseJob`) change the Inventory, apart from a seal's record and
|
|
651
|
+
the journal repair. `CatalogAdmin` runs over a catalog directory or a `BlobLocation`,
|
|
538
652
|
`prune` on a directory only. No attribute of `Catalog`, `Product` or `EditionView`
|
|
539
653
|
returns a `CatalogAdmin`, an `EmbargoReader` (spec §6.4) or an embargoed,
|
|
540
654
|
superseded or withdrawn edition. `Catalog`'s reads (`products`, `Product.view`,
|
|
@@ -565,6 +679,18 @@ read.
|
|
|
565
679
|
none. A refusal in one product never stops another's. A crash during the
|
|
566
680
|
batch leaves each sealed, unrecorded edition's journal entry, so its
|
|
567
681
|
product's next `working_copy()` records it, in a version of its own.
|
|
682
|
+
- `ReleaseJob.run` writes nothing under `products/` or `pending/` and deletes
|
|
683
|
+
nothing in the store. It releases no edition while the access result failed or the
|
|
684
|
+
head's `embargo` is on, and each retry of the release commit checks both again on
|
|
685
|
+
the new head. Every entry the fold records is `embargoed`, carries its manifest's
|
|
686
|
+
SHA-256 as read and its artefact records, and passed every check of its own; no
|
|
687
|
+
entry goes live whose manifest differs from the one its entry records. A refusal
|
|
688
|
+
in one registration or product never stops another's, and a failure of one step
|
|
689
|
+
never stops the next. A run against an unchanged catalog writes nothing. A dry
|
|
690
|
+
run writes nothing, `CURRENT` included.
|
|
691
|
+
- `check_access` fails closed: a listing error, a missing sentinel and a principal
|
|
692
|
+
off the allowlist each fail it, and `AzureReaderListing` sends its token to
|
|
693
|
+
`management.azure.com` alone.
|
|
568
694
|
- `collect_garbage` deletes nothing when it cannot read the Inventory back through
|
|
569
695
|
the window (`GcStoppedError`), nothing under the `data/` of a product whose
|
|
570
696
|
manifest or registration it cannot read or verify, no file a kept or registered
|
|
@@ -604,9 +730,17 @@ read.
|
|
|
604
730
|
- `diagnostics.verify_catalog` opens each edition in a DuckDB engine of
|
|
605
731
|
`check_edition`'s own; neither it nor `diagnostics.doctor` writes a file.
|
|
606
732
|
- These modules make no network calls, except `upload.py`, `admin.py`, `gc.py`,
|
|
607
|
-
`catalog.py` and `embargo.py`, which reach a store only through the
|
|
608
|
-
they are given
|
|
733
|
+
`release.py`, `catalog.py` and `embargo.py`, which reach a store only through the
|
|
734
|
+
`BlobLocation` they are given, and `access.py`, whose `AzureReaderListing` sends
|
|
735
|
+
GET requests to Azure Resource Manager. `jobs.py` reads
|
|
609
736
|
the one file it is given.
|
|
737
|
+
- `ReleaseJob.run` writes at most three Inventory versions (kinds `seal`, `release`
|
|
738
|
+
and `retire`) and `CURRENT`, creates and deletes one temporary directory holding
|
|
739
|
+
the pulled files of the editions the fold's reference check reads, and logs each
|
|
740
|
+
event on the `statcatlib.release` logger at WARNING, or ERROR for `step.failed`,
|
|
741
|
+
`access.mismatch` and `access.unchecked`. It sends one anonymous `HEAD` for the
|
|
742
|
+
storage clock at its start and at each of its three committing steps, unless its
|
|
743
|
+
storage has a `clock=`.
|
|
610
744
|
- `gc.collect_garbage` writes nothing. It deletes under `products/` and `pending/`
|
|
611
745
|
only, unless `dry_run`, and logs each event on the `statcatlib.gc` logger at INFO
|
|
612
746
|
(`gc.planned`, `gc.deleted`), WARNING (`gc.failed_closed`, `gc.unregistered`,
|
|
@@ -619,6 +753,13 @@ read.
|
|
|
619
753
|
- `EmbargoReader`: `tests/test_embargo_reader.py` (a local catalog), the
|
|
620
754
|
`TestNoOperationalReach` and blob-extra tests in `tests/test_public_api.py`, and,
|
|
621
755
|
against Azurite, `tests/stores/test_embargo_reader.py`.
|
|
756
|
+
- The release job: `tests/test_release_fold.py`, `tests/test_release_step.py` and
|
|
757
|
+
`tests/test_release_job.py` (every step over a `MemoryStore`, with the catalog of
|
|
758
|
+
`tests/_release.py` and the fakes of `tests/_fakes.py`),
|
|
759
|
+
`tests/test_access_listing.py` (`AzureReaderListing` through `pytest-httpx`, and
|
|
760
|
+
`check_access`), `tests/test_cli_release.py` and, against Azurite,
|
|
761
|
+
`tests/stores/test_release_job.py` (the 200-table release, the reference check, a
|
|
762
|
+
crash, a fork and its discard, a restore).
|
|
622
763
|
- GC: `tests/test_gc.py` (every rule over a `MemoryStore`, with the helpers in
|
|
623
764
|
`tests/_gc.py`), `tests/test_cli_gc.py` and, against Azurite,
|
|
624
765
|
`tests/stores/test_gc.py`; `decode_registration` in `tests/test_jobs_config.py`.
|