statcatlib 0.14.0__tar.gz → 0.16.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.14.0 → statcatlib-0.16.0}/PKG-INFO +9 -3
- {statcatlib-0.14.0 → statcatlib-0.16.0}/README.md +2 -1
- {statcatlib-0.14.0 → statcatlib-0.16.0}/pyproject.toml +11 -2
- {statcatlib-0.14.0 → statcatlib-0.16.0}/pyproject.toml.orig +7 -3
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/EGM.md +58 -8
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/__init__.py +7 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/admin.py +2 -2
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/bundle.py +41 -3
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/catalog.py +176 -43
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/cli/EGM.md +18 -5
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/cli/__init__.py +147 -9
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/EGM.md +113 -7
- statcatlib-0.16.0/src/statcatlib/store/blob.py +850 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/inventory.py +213 -64
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/working_copy.py +1 -1
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/testing.py +127 -0
- statcatlib-0.16.0/src/statcatlib/upload.py +353 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/LICENSE +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/checks.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/diagnostics.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/expr.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/EGM.md +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/__init__.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/agents.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/catalog.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/classification.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/dataset.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/distribution.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/licence.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/quality.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/skos.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/spatial.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/temporal.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/model/types.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/py.typed +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/query.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/sources/EGM.md +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/sources/__init__.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/sources/ckan.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/sources/sdmx.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/__init__.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/cross_product.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/lance.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/row.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/schema.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/scopes.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/sentinel.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/store.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/store/xhtml.py +0 -0
- {statcatlib-0.14.0 → statcatlib-0.16.0}/src/statcatlib/urn.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: statcatlib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0
|
|
4
4
|
Summary: Queryable open data catalog engine for DCAT-AP, StatDCAT-AP, CKAN, and SDMX
|
|
5
5
|
Keywords: sdmx,dcat,open-data,catalog,duckdb,statistical-data,ckan
|
|
6
6
|
Author: gabrielgellner
|
|
@@ -18,7 +18,11 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
|
18
18
|
Requires-Dist: attrs>=26.1.0
|
|
19
19
|
Requires-Dist: duckdb>=2.0.0
|
|
20
20
|
Requires-Dist: httpx>=0.28
|
|
21
|
-
Requires-Dist: sdmxlib>=0.
|
|
21
|
+
Requires-Dist: sdmxlib>=0.87,<0.88
|
|
22
|
+
Requires-Dist: obstore>=0.12,<0.13 ; extra == 'blob'
|
|
23
|
+
Requires-Dist: azure-identity>=1 ; extra == 'blob'
|
|
24
|
+
Requires-Dist: typer>=0.15 ; extra == 'blob'
|
|
25
|
+
Requires-Dist: rich>=15.0 ; extra == 'blob'
|
|
22
26
|
Requires-Dist: lancedb>=0.39 ; extra == 'search'
|
|
23
27
|
Requires-Dist: sentence-transformers>=3.0 ; extra == 'search'
|
|
24
28
|
Requires-Dist: typer>=0.15 ; extra == 'search'
|
|
@@ -29,6 +33,7 @@ Project-URL: Repository, https://github.com/StatCat-Suite/statcatlib
|
|
|
29
33
|
Project-URL: Documentation, https://statcat-suite.github.io/statcatlib
|
|
30
34
|
Project-URL: Issue Tracker, https://github.com/StatCat-Suite/statcatlib/issues
|
|
31
35
|
Project-URL: Changelog, https://github.com/StatCat-Suite/statcatlib/blob/main/CHANGELOG.md
|
|
36
|
+
Provides-Extra: blob
|
|
32
37
|
Provides-Extra: search
|
|
33
38
|
Description-Content-Type: text/markdown
|
|
34
39
|
|
|
@@ -462,9 +467,10 @@ just hooks-install
|
|
|
462
467
|
```
|
|
463
468
|
|
|
464
469
|
```bash
|
|
465
|
-
just ci # full gate: index guard + lint + format + docstrings + prose + Any ratchet + pyrefly + unit and integration tests + strict docs build
|
|
470
|
+
just ci # full gate: index guard + lint + format + docstrings + prose + Any ratchet + pyrefly + unit and integration tests + stores tier + strict docs build
|
|
466
471
|
just test # unit tests
|
|
467
472
|
just test-integration # integration tests (no network)
|
|
473
|
+
just test-stores # the blob writer, the upload and remote catalogs against Azurite (needs a container runtime)
|
|
468
474
|
just test-live # live tests against real SDMX endpoints
|
|
469
475
|
just lint # ruff check
|
|
470
476
|
just typecheck # pyrefly
|
|
@@ -428,9 +428,10 @@ just hooks-install
|
|
|
428
428
|
```
|
|
429
429
|
|
|
430
430
|
```bash
|
|
431
|
-
just ci # full gate: index guard + lint + format + docstrings + prose + Any ratchet + pyrefly + unit and integration tests + strict docs build
|
|
431
|
+
just ci # full gate: index guard + lint + format + docstrings + prose + Any ratchet + pyrefly + unit and integration tests + stores tier + strict docs build
|
|
432
432
|
just test # unit tests
|
|
433
433
|
just test-integration # integration tests (no network)
|
|
434
|
+
just test-stores # the blob writer, the upload and remote catalogs against Azurite (needs a container runtime)
|
|
434
435
|
just test-live # live tests against real SDMX endpoints
|
|
435
436
|
just lint # ruff check
|
|
436
437
|
just typecheck # pyrefly
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "statcatlib"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.0"
|
|
4
4
|
description = "Queryable open data catalog engine for DCAT-AP, StatDCAT-AP, CKAN, and SDMX"
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
license-files = ["LICENSE"]
|
|
@@ -29,7 +29,7 @@ dependencies = [
|
|
|
29
29
|
"attrs>=26.1.0",
|
|
30
30
|
"duckdb>=2.0.0",
|
|
31
31
|
"httpx>=0.28",
|
|
32
|
-
"sdmxlib>=0.
|
|
32
|
+
"sdmxlib>=0.87,<0.88",
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
[[project.authors]]
|
|
@@ -43,6 +43,12 @@ search = [
|
|
|
43
43
|
"typer>=0.15",
|
|
44
44
|
"rich>=15.0",
|
|
45
45
|
]
|
|
46
|
+
blob = [
|
|
47
|
+
"obstore>=0.12,<0.13",
|
|
48
|
+
"azure-identity>=1",
|
|
49
|
+
"typer>=0.15",
|
|
50
|
+
"rich>=15.0",
|
|
51
|
+
]
|
|
46
52
|
|
|
47
53
|
[project.scripts]
|
|
48
54
|
statcat = "statcatlib.cli:main"
|
|
@@ -77,6 +83,7 @@ markers = [
|
|
|
77
83
|
"unit: fast, in-process tests with no network I/O",
|
|
78
84
|
"integration: tests that use real DuckDB and sdmxlib but no network",
|
|
79
85
|
"live: tests that make real HTTP calls to external SDMX endpoints",
|
|
86
|
+
"stores: tests that run against an object-store emulator in a container",
|
|
80
87
|
]
|
|
81
88
|
|
|
82
89
|
[tool.coverage.run]
|
|
@@ -95,9 +102,11 @@ exclude_lines = [
|
|
|
95
102
|
|
|
96
103
|
[dependency-groups]
|
|
97
104
|
dev = [
|
|
105
|
+
"azure-identity>=1",
|
|
98
106
|
"lancedb>=0.39",
|
|
99
107
|
"mkdocs-material>=9.7.6",
|
|
100
108
|
"mkdocstrings[python]>=1.0.3",
|
|
109
|
+
"obstore>=0.12,<0.13",
|
|
101
110
|
"polars>=1.39.3",
|
|
102
111
|
"pydoclint>=0.11.1,<0.12",
|
|
103
112
|
"pyrefly>=1.4.0,<1.5",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "statcatlib"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.0"
|
|
4
4
|
description = "Queryable open data catalog engine for DCAT-AP, StatDCAT-AP, CKAN, and SDMX"
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
license-files = ["LICENSE"]
|
|
@@ -27,13 +27,14 @@ dependencies = [
|
|
|
27
27
|
# by DuckDB 1.x, so by statcatlib 0.8 or earlier.
|
|
28
28
|
"duckdb>=2.0.0",
|
|
29
29
|
"httpx>=0.28",
|
|
30
|
-
# <0.
|
|
30
|
+
# <0.88: every suite library is pre-1.0, so a minor bump may break
|
|
31
31
|
# (GH-67).
|
|
32
|
-
"sdmxlib>=0.
|
|
32
|
+
"sdmxlib>=0.87,<0.88",
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
[project.optional-dependencies]
|
|
36
36
|
search = ["lancedb>=0.39", "sentence-transformers>=3.0", "typer>=0.15", "rich>=15.0"]
|
|
37
|
+
blob = ["obstore>=0.12,<0.13", "azure-identity>=1", "typer>=0.15", "rich>=15.0"]
|
|
37
38
|
|
|
38
39
|
[project.scripts]
|
|
39
40
|
statcat = "statcatlib.cli:main"
|
|
@@ -83,6 +84,7 @@ markers = [
|
|
|
83
84
|
"unit: fast, in-process tests with no network I/O",
|
|
84
85
|
"integration: tests that use real DuckDB and sdmxlib but no network",
|
|
85
86
|
"live: tests that make real HTTP calls to external SDMX endpoints",
|
|
87
|
+
"stores: tests that run against an object-store emulator in a container",
|
|
86
88
|
]
|
|
87
89
|
|
|
88
90
|
[tool.coverage.run]
|
|
@@ -104,9 +106,11 @@ exclude_lines = [
|
|
|
104
106
|
|
|
105
107
|
[dependency-groups]
|
|
106
108
|
dev = [
|
|
109
|
+
"azure-identity>=1",
|
|
107
110
|
"lancedb>=0.39",
|
|
108
111
|
"mkdocs-material>=9.7.6",
|
|
109
112
|
"mkdocstrings[python]>=1.0.3",
|
|
113
|
+
"obstore>=0.12,<0.13",
|
|
110
114
|
"polars>=1.39.3",
|
|
111
115
|
# The checkers are capped, as sdmxlib's are: uv.lock is gitignored, so an
|
|
112
116
|
# uncapped checker's next release would change what `just ci` reports
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
`model`, `sources` and `store`; `CatalogAdmin` from `admin.py`; `Catalog`, `Product`, `Edition`,
|
|
7
7
|
`SealOutcomes` and `NoLiveEditionError` from `catalog.py`; the errors `InventoryConflictError`,
|
|
8
8
|
`InventoryContentionError`, `InventoryRuleError`, `InventoryWriteRefusedError`,
|
|
9
|
-
`NonFastForwardError` and `CrossProductRuleError` from `store/inventory.py
|
|
9
|
+
`NonFastForwardError` and `CrossProductRuleError` from `store/inventory.py`; `BlobLocation` and `TokenIdentity`
|
|
10
|
+
from `store/blob.py`; `upload_edition`, `UploadReport` and `UploadCheckError` from `upload.py`; `WorkingCopyExistsError`,
|
|
10
11
|
`WorkingCopyClosedError`, `SealCheckError`, `EditionCheckError` and `JournalError` from
|
|
11
12
|
`store/working_copy.py`; `NotLoadedError` and `is_unloaded` from the store's
|
|
12
13
|
sentinel module; and part of `expr` (listed below). It exposes `checks`,
|
|
@@ -16,7 +17,7 @@ products (`catalog.py`), the operational changes to its Inventory
|
|
|
16
17
|
(`admin.py`), seal checks (`checks.py`), the `statcat` section of
|
|
17
18
|
an sdmxlib edition manifest (`bundle.py`), the integrity and support
|
|
18
19
|
checks behind `statcat verify-catalog` and `statcat doctor`
|
|
19
|
-
(`diagnostics.py`), composable query filters
|
|
20
|
+
(`diagnostics.py`), the copy of a sealed edition to object storage (`upload.py`), composable query filters
|
|
20
21
|
(`query.py`), traversal expressions (`expr.py`), SDMX URN builders (`urn.py`)
|
|
21
22
|
and test catalogs (`testing.py`).
|
|
22
23
|
|
|
@@ -28,8 +29,8 @@ and test catalogs (`testing.py`).
|
|
|
28
29
|
agents, distributions, SKOS concepts); imports only `InternationalString`
|
|
29
30
|
from sdmxlib.
|
|
30
31
|
- `store/`: [`./store/EGM.md`](./store/EGM.md) — the private `_Store`,
|
|
31
|
-
`EditionView`, `WorkingCopy` and the seal, the Inventory writer
|
|
32
|
-
`QueryBuilder`, scopes, DDL, LanceDB search index.
|
|
32
|
+
`EditionView`, `WorkingCopy` and the seal, the Inventory writer and its
|
|
33
|
+
local and blob storage, `QueryBuilder`, scopes, DDL, LanceDB search index.
|
|
33
34
|
- `sources/`: [`./sources/EGM.md`](./sources/EGM.md) — SDMX and CKAN ingest
|
|
34
35
|
connectors.
|
|
35
36
|
- `cli/`: [`./cli/EGM.md`](./cli/EGM.md) — the `statcat` Typer command.
|
|
@@ -45,7 +46,13 @@ and test catalogs (`testing.py`).
|
|
|
45
46
|
or with `exist_ok=True` reopens it for writing and writes nothing.
|
|
46
47
|
- `Catalog.open(path)`: read-only. Raises `FileNotFoundError` when *path*
|
|
47
48
|
has no `catalog.json`. A catalog with no Inventory version has no
|
|
48
|
-
products.
|
|
49
|
+
products. A URI string raises `ValueError` naming `BlobLocation`.
|
|
50
|
+
- `Catalog.open(BlobLocation)`: the same over object storage, with a
|
|
51
|
+
`_Opening` whose `root` is `<cache_dir>/<catalog_id>`. No `cache_dir` raises
|
|
52
|
+
`ValueError`, and a `catalog_id` that cannot name a directory raises
|
|
53
|
+
`IncompatibleCatalogError`. `catalog_id` is read once; `refresh` and
|
|
54
|
+
`description` read through a `BlobInventoryStorage`, and `repr` and messages
|
|
55
|
+
show `location.uri`. `Catalog.create` raises `TypeError` for one.
|
|
49
56
|
- `description`: the `CatalogDescription` in the root's
|
|
50
57
|
`extensions.statcat` of the Inventory version the live view holds (as of
|
|
51
58
|
the last `refresh()`), read with `bundle.description_from_root`; `None`
|
|
@@ -202,6 +209,33 @@ The module keeps its 0.11 name and describes editions. Imported from
|
|
|
202
209
|
- `STATCAT_SECTION = "statcat"`, `STATCAT_SECTION_FORMAT = 3`,
|
|
203
210
|
`PRODUCT_KINDS = ("data", "structure")`.
|
|
204
211
|
|
|
212
|
+
- `check_catalog_no_index(root, manifest, reg, profile) -> list[Finding]`:
|
|
213
|
+
`check_catalog` without the search index check, for an install with no
|
|
214
|
+
`lancedb`. `store/blob.py`'s `_statcat_check()` chooses between the two.
|
|
215
|
+
|
|
216
|
+
### `upload.py`
|
|
217
|
+
|
|
218
|
+
`statcat upload`'s work, on the `blob` extra. It reads and writes nothing under
|
|
219
|
+
`catalog.json`, `inventory/` or `CURRENT`.
|
|
220
|
+
|
|
221
|
+
- `upload_edition(edition_dir, location, *, release_at=None) -> UploadReport`:
|
|
222
|
+
refuses a store other than `AzureStore` (`TypeError`, naming W3) and a store with
|
|
223
|
+
no `TokenIdentity` (`ValueError`), then runs `_upload` with an `AzureBlobRest`.
|
|
224
|
+
- `_upload(edition_dir, location, *, release_at=None, rest)`: the steps, over any
|
|
225
|
+
store and any digest client with `head(path) -> RemoteFile | None`. Tests and
|
|
226
|
+
`record_uploaded` call it. A naive `release_at` raises `ValueError`.
|
|
227
|
+
- `UploadReport(edition_id, product_id, sent, kept, registered)`: `sent` and `kept`
|
|
228
|
+
hold store paths, and `registered` is advisory, since a retried create can report
|
|
229
|
+
false against its own earlier success.
|
|
230
|
+
- `UploadCheckError(findings)` (`RuntimeError`): any finding, an oversize file, a
|
|
231
|
+
path leaving its directory, a rotted local file, a differing manifest, or a
|
|
232
|
+
differing `pending/` document.
|
|
233
|
+
- `_pending_document(edition_id, release_at) -> bytes`: the `pending/` document's
|
|
234
|
+
bytes, the contract `docs/catalog-format.md` §1.5 states.
|
|
235
|
+
- `_lookup(rest, manifest)`: adapts `rest.head` to sdmxlib's `Lookup`, asking once per
|
|
236
|
+
file. `_store_path(manifest, entry)` normalises `../data/`, which `obstore` refuses.
|
|
237
|
+
- `MAX_PUT_BYTES`: Put Blob's limit, 5,000 MiB.
|
|
238
|
+
|
|
205
239
|
### `diagnostics.py`
|
|
206
240
|
|
|
207
241
|
Imported from `statcatlib.diagnostics`; nothing here is re-exported from
|
|
@@ -312,6 +346,14 @@ read.
|
|
|
312
346
|
SDMX-agency `CL_OBS_STATUS`, so every codelist a table cuts has a
|
|
313
347
|
structure home (A5). The draws are SplitMix64 seeded by SHA-256, so they
|
|
314
348
|
do not depend on Python's `random`.
|
|
349
|
+
- `inventory_corpus(writer, products=7000, *, embargoed=0.1, artefacts=False,
|
|
350
|
+
batch=500, seed=0) -> Root` writes `catalog.json` and one `try_commit` per
|
|
351
|
+
`batch` products, with no manifest behind them, so no rule runs. The leaves
|
|
352
|
+
come from sdmxlib's `record`, with hashed ids, one dataflow URN, `pull_bytes`
|
|
353
|
+
13,131,776 and, with `artefacts=True`, 10 artefact records. Commits start at
|
|
354
|
+
a fixed time and step one millisecond, so a seed writes the same bytes for a
|
|
355
|
+
given actor and statcatlib version. A catalog with a `catalog.json` raises
|
|
356
|
+
`FileExistsError`. `bench/inventory_read.py` (GH-98) drives it.
|
|
315
357
|
|
|
316
358
|
## Invariants
|
|
317
359
|
|
|
@@ -336,8 +378,14 @@ read.
|
|
|
336
378
|
index and before it closes the store and registry; it then seals. The
|
|
337
379
|
manifest records each file's digests, so a later write fails
|
|
338
380
|
`check_edition`'s files check.
|
|
339
|
-
- `Catalog.open` writes nothing.
|
|
340
|
-
|
|
381
|
+
- `Catalog.open` writes nothing to the catalog. Over a `BlobLocation`, `Product.view`
|
|
382
|
+
writes to `cache_dir` alone: the live edition's files, by a rename of a verified,
|
|
383
|
+
fsynced staged directory, and the receive stamp. No embargoed edition's files and no
|
|
384
|
+
leaf bytes reach it. No `Catalog` or `Product` method takes or returns a path
|
|
385
|
+
inside the catalog.
|
|
386
|
+
- A library call never takes a credential from the environment: `Catalog.open` over a
|
|
387
|
+
store takes a `BlobLocation` the caller built. `catalog.py` and `store/blob.py` read
|
|
388
|
+
no environment variable.
|
|
341
389
|
- Only `CatalogAdmin` changes the Inventory, apart from a seal's record and
|
|
342
390
|
the journal repair. No attribute of `Catalog`, `Product` or `EditionView`
|
|
343
391
|
returns a `CatalogAdmin` or an embargoed, superseded or withdrawn edition.
|
|
@@ -349,7 +397,9 @@ read.
|
|
|
349
397
|
`sdmxlib.LocalRegistry.edition`, which hashes the bytes of
|
|
350
398
|
`manifest.json` it parses. A missing, malformed or different manifest
|
|
351
399
|
raises sdmxlib's `ManifestError`, which `view` re-raises as
|
|
352
|
-
`IncompatibleCatalogError
|
|
400
|
+
`IncompatibleCatalogError`, chained. A local catalog's message names
|
|
401
|
+
`statcat verify-catalog`, and a blob catalog's names the cached directory to
|
|
402
|
+
delete, so the edition is pulled again. Every view a `Catalog` returns shares one
|
|
353
403
|
`sdmxlib.EditionEngine`, and `Catalog.close` closes them all.
|
|
354
404
|
- A seal check only reads: `seal` runs every check against `wc.view()`
|
|
355
405
|
before writing anything, and any finding raises `SealCheckError`.
|
|
@@ -88,6 +88,7 @@ from statcatlib.store import (
|
|
|
88
88
|
WorkingCopy,
|
|
89
89
|
xhtml_to_markdown,
|
|
90
90
|
)
|
|
91
|
+
from statcatlib.store.blob import BlobLocation, TokenIdentity
|
|
91
92
|
from statcatlib.store.inventory import (
|
|
92
93
|
CrossProductRuleError,
|
|
93
94
|
InventoryConflictError,
|
|
@@ -104,6 +105,7 @@ from statcatlib.store.working_copy import (
|
|
|
104
105
|
WorkingCopyClosedError,
|
|
105
106
|
WorkingCopyExistsError,
|
|
106
107
|
)
|
|
108
|
+
from statcatlib.upload import UploadCheckError, UploadReport, upload_edition
|
|
107
109
|
|
|
108
110
|
__all__ = [
|
|
109
111
|
"SEARCH_ANNOTATION_TYPES",
|
|
@@ -114,6 +116,7 @@ __all__ = [
|
|
|
114
116
|
"AggregateDataset",
|
|
115
117
|
"AliasedExpr",
|
|
116
118
|
"BaseDataset",
|
|
119
|
+
"BlobLocation",
|
|
117
120
|
"Catalog",
|
|
118
121
|
"CatalogAdmin",
|
|
119
122
|
"CatalogDescription",
|
|
@@ -186,7 +189,10 @@ __all__ = [
|
|
|
186
189
|
"StatisticalDataset",
|
|
187
190
|
"TemporalCoverage",
|
|
188
191
|
"ThemesScope",
|
|
192
|
+
"TokenIdentity",
|
|
189
193
|
"TraversalExpr",
|
|
194
|
+
"UploadCheckError",
|
|
195
|
+
"UploadReport",
|
|
190
196
|
"Variable",
|
|
191
197
|
"WorkingCopy",
|
|
192
198
|
"WorkingCopyClosedError",
|
|
@@ -203,6 +209,7 @@ __all__ = [
|
|
|
203
209
|
"is_unloaded",
|
|
204
210
|
"prepare_sdmx",
|
|
205
211
|
"query",
|
|
212
|
+
"upload_edition",
|
|
206
213
|
"urn",
|
|
207
214
|
"xhtml_to_markdown",
|
|
208
215
|
]
|
|
@@ -362,8 +362,8 @@ class CatalogAdmin:
|
|
|
362
362
|
(``rule="structure-home"``).
|
|
363
363
|
|
|
364
364
|
An empty *reason* raises `ValueError`. It acts on the live edition at
|
|
365
|
-
this sdmxlib's store schema
|
|
366
|
-
edition
|
|
365
|
+
this sdmxlib's store schema, and does not roll back another schema's
|
|
366
|
+
live edition.
|
|
367
367
|
"""
|
|
368
368
|
_require_reason(reason)
|
|
369
369
|
if if_live is None:
|
|
@@ -51,6 +51,7 @@ __all__ = [
|
|
|
51
51
|
"STATCAT_SECTION_FORMAT",
|
|
52
52
|
"catalog_section",
|
|
53
53
|
"check_catalog",
|
|
54
|
+
"check_catalog_no_index",
|
|
54
55
|
"description_from_json",
|
|
55
56
|
"description_from_root",
|
|
56
57
|
"description_to_json",
|
|
@@ -234,10 +235,19 @@ def _check_search(root: Path, declared: object) -> Iterator[Finding]:
|
|
|
234
235
|
detail = f"{found}, which this build does not read: check it with that release, or rebuild with build_index()"
|
|
235
236
|
yield Finding(STATCAT_SECTION, f"{_SEARCH_DIRNAME}/{_INDEX_MANIFEST}", detail)
|
|
236
237
|
return
|
|
237
|
-
|
|
238
|
+
try:
|
|
239
|
+
index = LanceIndex(search_dir)
|
|
240
|
+
except ImportError:
|
|
241
|
+
yield Finding(STATCAT_SECTION, _SEARCH_DIRNAME, "cannot check the search index without statcatlib[search]")
|
|
242
|
+
return
|
|
238
243
|
if (declared_version := declared.get("index_version")) != index.version:
|
|
239
244
|
detail = f"the manifest declares index_version {declared_version}, {_INDEX_MANIFEST} records {index.version}"
|
|
240
245
|
yield Finding(STATCAT_SECTION, MANIFEST_NAME, detail)
|
|
246
|
+
yield from _check_layout(index, languages)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _check_layout(index: LanceIndex, languages: Mapping[str, object]) -> Iterator[Finding]:
|
|
250
|
+
"""The index's tables: each declared language as declared, and none ``index.json`` does not name."""
|
|
241
251
|
layout = index.describe()
|
|
242
252
|
for lang in sorted(set(languages) | set(layout)):
|
|
243
253
|
yield from _check_language(index, lang, layout.get(lang), languages.get(lang))
|
|
@@ -297,10 +307,36 @@ def check_catalog(root: Path, manifest: Manifest, reg: EditionRegistry, profile:
|
|
|
297
307
|
Returns:
|
|
298
308
|
Everything found wrong with the catalog's part of the edition.
|
|
299
309
|
"""
|
|
310
|
+
del profile
|
|
311
|
+
return _section_findings(root, manifest, reg, index=True)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def check_catalog_no_index(root: Path, manifest: Manifest, reg: EditionRegistry, profile: Profile) -> list[Finding]:
|
|
315
|
+
"""`check_catalog` without its search index check, for an install with no `lancedb`.
|
|
316
|
+
|
|
317
|
+
A ``statcatlib[blob]`` install holds no `lancedb`, so it cannot open the
|
|
318
|
+
index. The ``files`` check of `sdmxlib.edition.check_edition` still covers the
|
|
319
|
+
index's bytes by digest.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
root: The edition directory.
|
|
323
|
+
manifest: Its manifest.
|
|
324
|
+
reg: sdmxlib's registry over the edition.
|
|
325
|
+
profile: The `check_edition` profile that runs it. Every profile runs
|
|
326
|
+
the same checks.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Everything found wrong with the rest of the catalog's part of the edition.
|
|
330
|
+
"""
|
|
331
|
+
del profile
|
|
332
|
+
return _section_findings(root, manifest, reg, index=False)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _section_findings(root: Path, manifest: Manifest, reg: EditionRegistry, *, index: bool) -> list[Finding]:
|
|
336
|
+
"""The ``statcat`` checks, with the opening of the search index when *index* is true."""
|
|
300
337
|
from statcatlib.store.schema import first_row # noqa: PLC0415
|
|
301
338
|
from statcatlib.store.store import annotation_fingerprint, statcat_schema_mismatch # noqa: PLC0415
|
|
302
339
|
|
|
303
|
-
del profile
|
|
304
340
|
conn = reg.connection
|
|
305
341
|
rows = conn.execute(
|
|
306
342
|
"""
|
|
@@ -332,7 +368,9 @@ def check_catalog(root: Path, manifest: Manifest, reg: EditionRegistry, profile:
|
|
|
332
368
|
for binding in manifest.bindings
|
|
333
369
|
]
|
|
334
370
|
findings += structure_content(conn)
|
|
335
|
-
findings +=
|
|
371
|
+
findings += _check_catalog_rows(conn, section)
|
|
372
|
+
if index:
|
|
373
|
+
findings += _check_search(root, section.get("search"))
|
|
336
374
|
search = section.get("search")
|
|
337
375
|
if isinstance(search, Mapping):
|
|
338
376
|
findings += _check_index_annotations(conn, search.get("annotations"), annotation_fingerprint)
|