statcatlib 0.16.0__tar.gz → 0.16.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 (50) hide show
  1. {statcatlib-0.16.0 → statcatlib-0.16.2}/PKG-INFO +1 -1
  2. {statcatlib-0.16.0 → statcatlib-0.16.2}/pyproject.toml +1 -1
  3. {statcatlib-0.16.0 → statcatlib-0.16.2}/pyproject.toml.orig +1 -1
  4. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/cli/EGM.md +7 -4
  5. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/cli/__init__.py +73 -15
  6. {statcatlib-0.16.0 → statcatlib-0.16.2}/LICENSE +0 -0
  7. {statcatlib-0.16.0 → statcatlib-0.16.2}/README.md +0 -0
  8. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/EGM.md +0 -0
  9. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/__init__.py +0 -0
  10. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/admin.py +0 -0
  11. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/bundle.py +0 -0
  12. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/catalog.py +0 -0
  13. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/checks.py +0 -0
  14. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/diagnostics.py +0 -0
  15. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/expr.py +0 -0
  16. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/EGM.md +0 -0
  17. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/__init__.py +0 -0
  18. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/agents.py +0 -0
  19. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/catalog.py +0 -0
  20. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/classification.py +0 -0
  21. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/dataset.py +0 -0
  22. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/distribution.py +0 -0
  23. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/licence.py +0 -0
  24. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/quality.py +0 -0
  25. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/skos.py +0 -0
  26. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/spatial.py +0 -0
  27. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/temporal.py +0 -0
  28. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/model/types.py +0 -0
  29. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/py.typed +0 -0
  30. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/query.py +0 -0
  31. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/sources/EGM.md +0 -0
  32. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/sources/__init__.py +0 -0
  33. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/sources/ckan.py +0 -0
  34. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/sources/sdmx.py +0 -0
  35. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/EGM.md +0 -0
  36. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/__init__.py +0 -0
  37. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/blob.py +0 -0
  38. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/cross_product.py +0 -0
  39. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/inventory.py +0 -0
  40. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/lance.py +0 -0
  41. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/row.py +0 -0
  42. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/schema.py +0 -0
  43. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/scopes.py +0 -0
  44. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/sentinel.py +0 -0
  45. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/store.py +0 -0
  46. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/working_copy.py +0 -0
  47. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/store/xhtml.py +0 -0
  48. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/testing.py +0 -0
  49. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/upload.py +0 -0
  50. {statcatlib-0.16.0 → statcatlib-0.16.2}/src/statcatlib/urn.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statcatlib
3
- Version: 0.16.0
3
+ Version: 0.16.2
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "statcatlib"
3
- version = "0.16.0"
3
+ version = "0.16.2"
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"]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "statcatlib"
3
- version = "0.16.0"
3
+ version = "0.16.2"
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"]
@@ -39,7 +39,7 @@ Commands:
39
39
  | `statcat verify-catalog PATH` | `verify_catalog` | `diagnostics.verify_catalog(path)` | any finding, each printed as `FAIL <file>: <detail>`; exit 2 on `CatalogUnreadableError`. Each stale data edition prints as `STALE <StaleEdition>` and leaves the exit code alone |
40
40
  | `statcat doctor PATH` | `doctor` | `diagnostics.doctor(path)` | the report holds a warning (topic `stale data editions`: a stale embargoed data edition is one; a stale live edition is `info` and leaves the exit code alone; topic `partials with no structure home`: each item is one) |
41
41
  | `statcat prune PATH --older-than DAYS --reason TEXT [--apply]` | `prune` | `sc.CatalogAdmin.open(path).prune(older_than=timedelta(days=DAYS), reason=TEXT, dry_run=not apply)` | `catalog.json` is missing, the catalog cannot be opened, the retire commit is refused, or a deletion failed (each printed as `FAIL <path>: <error>`) |
42
- | `statcat upload EDITION_DIR AZ_URL [--release-at TIME]` | `upload` | `upload_edition(edition_dir, location, release_at=)` | a finding, each printed as `FAIL <file>: <detail>`, or a `ManifestError`, `BlobStoreError` or `ClientAuthenticationError`, printed as `ERROR <message>`; exit 2 on a usage error, a bad `AZ_URL`, `s3://`, no `AZURE_STORAGE_ACCOUNT_NAME`, a key, SAS, token or endpoint variable set, even empty (`_REFUSED_VARIABLES`), a naive time, or a missing `blob` extra |
42
+ | `statcat upload EDITION_DIR AZ_URL [--release-at TIME]` | `upload` | `upload_edition(edition_dir, location, release_at=)` | a finding, each printed as `FAIL <file>: <detail>`, or a `ManifestError`, `BlobStoreError` or `ClientAuthenticationError`, printed as `ERROR <message>`; exit 2 on a usage error, a bad `AZ_URL`, `s3://`, no `AZURE_STORAGE_ACCOUNT_NAME`, a refused `obstore` variable set, even empty, in any spelling `obstore` reads (`_REFUSED_VARIABLES`, matched by `_refused_variables`: `AZURE_` then any case), a naive time, a missing `blob` extra, or an environment value `obstore` cannot parse, such as `AZURE_DISABLE_TAGGING=junk` (`GenericError` from the store build, before any request) |
43
43
 
44
44
  `build-index` options and their `WorkingCopy.build_index` keyword:
45
45
 
@@ -75,10 +75,13 @@ Private helpers that tests and later commands rely on:
75
75
  `statcatlib.cli` logger (currently unused).
76
76
  - `_usage(message)` — prints it and exits 2. `_release_time(raw)`, `_catalog_uri(raw)`
77
77
  and `_azure_account()` parse `upload`'s inputs, each through `_usage`.
78
- `_azure_location(uri, account)` builds the store with `obstore.store.from_url`,
78
+ `_refused_variables()` lists the environment's names that `obstore` would parse as a
79
+ refused key, as spelled. `_azure_location(uri, account)` builds the store with `obstore.store.from_url`,
79
80
  `TokenIdentity(AzureCredentialProvider(DefaultAzureCredential()))` and the one-hour
80
- timeout; `upload` is the only code in statcatlib that reads the environment for a
81
- store.
81
+ timeout, and turns obstore's `GenericError` from that build into `_usage`, with the text from
82
+ `_store_build_message(exc)`: obstore's first line, and the `AZURE_DISABLE_TAGGING` spelling the
83
+ environment sets, since obstore 0.12.1's message omits the variable. `upload` is the only code in
84
+ statcatlib that reads the environment for a store.
82
85
  - `_parse_codelist_policy(raw)` — accepts only mode `fts_only`
83
86
  (`_ACCEPTED_CODELIST_MODES`); raises `typer.BadParameter` otherwise.
84
87
  - `_parse_reference_metadata_blend(raw)` — `max_chars` must be a positive
@@ -60,9 +60,12 @@ _DEFAULT_MODEL = "intfloat/multilingual-e5-base"
60
60
 
61
61
  _ACCEPTED_CODELIST_MODES = frozenset({"fts_only"})
62
62
 
63
- #: The `obstore` variables `statcat upload` refuses, as `_store/_azure.pyi` spells them. Keys and SAS break spec
64
- #: §10.1. A token writes under another identity than the one the upload records. An endpoint sends the writes to
65
- #: another host than the digest read.
63
+ #: The `obstore` variables `statcat upload` refuses: every ``azure_`` spelling that object_store 0.14.2's
64
+ #: `AzureConfigKey::from_str` accepts for these keys, the crate obstore 0.12.1 bundles. Spec §10.1 allows the
65
+ #: publish job no account key or SAS. The credential provider's token wins beside the key, SAS and token variables
66
+ #: on obstore 0.12.1, and a later obstore could change that order. An endpoint, the emulator or the Fabric
67
+ #: endpoint sends the writes to another host than the digest read. Skipping the signature sends unsigned writes. A
68
+ #: customer-provided key encrypts blobs that the digest read sends no key for.
66
69
  _REFUSED_VARIABLES = (
67
70
  "AZURE_STORAGE_ACCOUNT_KEY",
68
71
  "AZURE_STORAGE_ACCESS_KEY",
@@ -72,8 +75,16 @@ _REFUSED_VARIABLES = (
72
75
  "AZURE_STORAGE_TOKEN",
73
76
  "AZURE_STORAGE_ENDPOINT",
74
77
  "AZURE_ENDPOINT",
78
+ "AZURE_STORAGE_USE_EMULATOR",
79
+ "AZURE_USE_FABRIC_ENDPOINT",
80
+ "AZURE_SKIP_SIGNATURE",
81
+ "AZURE_STORAGE_ENCRYPTION_KEY",
75
82
  )
76
83
 
84
+ #: The refused names as `MicrosoftAzureBuilder::from_env` compares them: it reads each variable whose name starts
85
+ #: with ``AZURE_`` and parses the name lowercased.
86
+ _REFUSED_KEYS = frozenset(name.lower() for name in _REFUSED_VARIABLES)
87
+
77
88
  #: How long one request may take: a 5,000 MiB Put at about 1.4 MB/s.
78
89
  _UPLOAD_TIMEOUT = "1h"
79
90
 
@@ -633,10 +644,24 @@ def _catalog_uri(raw: str) -> str:
633
644
  return f"az://{'/'.join(segments)}/"
634
645
 
635
646
 
647
+ def _refused_variables() -> list[str]:
648
+ """The environment's names that `obstore` parses as a refused key, spelled as the environment spells them.
649
+
650
+ `MicrosoftAzureBuilder::from_env` lowercases ASCII letters only, so a name with any other character never
651
+ matches a key there, and is not refused here.
652
+ """
653
+ return sorted(
654
+ name for name in os.environ if name.startswith("AZURE_") and name.isascii() and name.lower() in _REFUSED_KEYS
655
+ )
656
+
657
+
636
658
  def _azure_account() -> str:
637
659
  """The storage account from the environment; a refused variable, even empty, or no account, exits 2."""
638
- if found := [name for name in _REFUSED_VARIABLES if name in os.environ]:
639
- _usage(f"upload acts under one identity at the account's URL; unset {', '.join(found)}")
660
+ if found := _refused_variables():
661
+ _usage(
662
+ "upload writes with its own credential, signed and with no customer-provided key, "
663
+ f"to the account's URL; unset {', '.join(found)}"
664
+ )
640
665
  account = os.environ.get("AZURE_STORAGE_ACCOUNT_NAME")
641
666
  if not account:
642
667
  _usage("set AZURE_STORAGE_ACCOUNT_NAME to the storage account's name")
@@ -644,22 +669,49 @@ def _azure_account() -> str:
644
669
 
645
670
 
646
671
  def _azure_location(uri: str, account: str) -> BlobLocation:
647
- """The catalog at *uri*, reached with `DefaultAzureCredential`; a missing ``blob`` extra exits 2."""
672
+ """The catalog at *uri*, reached with `DefaultAzureCredential`.
673
+
674
+ A missing ``blob`` extra exits 2, and so does an environment value `obstore` cannot parse, such as
675
+ ``AZURE_DISABLE_TAGGING=junk``: obstore 0.12.1 raises `GenericError` from the store build, before any request.
676
+ """
648
677
  try:
649
678
  from azure.identity import DefaultAzureCredential # noqa: PLC0415
650
679
  from obstore.auth.azure import AzureCredentialProvider # noqa: PLC0415
680
+ from obstore.exceptions import GenericError # noqa: PLC0415
651
681
  from obstore.store import from_url # noqa: PLC0415
652
682
  except ImportError:
653
683
  _usage("upload needs the blob extra: uv add 'statcatlib[blob]' (or pip install 'statcatlib[blob]')")
654
- store = from_url(
655
- uri.removesuffix("/"),
656
- account_name=account,
657
- credential_provider=TokenIdentity(AzureCredentialProvider(DefaultAzureCredential())),
658
- client_options={"timeout": _UPLOAD_TIMEOUT},
659
- )
684
+ credential = TokenIdentity(AzureCredentialProvider(DefaultAzureCredential()))
685
+ try:
686
+ store = from_url(
687
+ uri.removesuffix("/"),
688
+ account_name=account,
689
+ credential_provider=credential,
690
+ client_options={"timeout": _UPLOAD_TIMEOUT},
691
+ )
692
+ except GenericError as exc:
693
+ _usage(_store_build_message(exc))
660
694
  return BlobLocation(uri, store)
661
695
 
662
696
 
697
+ def _store_build_message(exc: Exception) -> str:
698
+ """The exit-2 text for a store build that failed on the environment.
699
+
700
+ obstore 0.12.1's message names the value and omits the variable, so the text names
701
+ ``AZURE_DISABLE_TAGGING`` when the environment sets it, in any case after ``AZURE_``.
702
+ """
703
+ reason = str(exc).splitlines()[0] if str(exc) else type(exc).__name__
704
+ message = f"obstore cannot build the store from the environment ({reason})"
705
+ tagging = sorted(
706
+ name
707
+ for name in os.environ
708
+ if name.startswith("AZURE_") and name.isascii() and name.lower() == "azure_disable_tagging"
709
+ )
710
+ if tagging:
711
+ message += f"; {', '.join(tagging)} takes true or false"
712
+ return message
713
+
714
+
663
715
  @app.command("upload")
664
716
  def upload(
665
717
  edition_dir: Path = typer.Argument(
@@ -684,11 +736,17 @@ def upload(
684
736
  release job. It exits 0 when the edition is uploaded, or already registered
685
737
  with the same bytes; 1 on any finding, each printed as ``FAIL <file>: <detail>``,
686
738
  or on a store or credential failure, printed as ``ERROR <message>``; and 2 on
687
- a usage error, a missing ``blob`` extra, or any of these variables set, even
688
- empty: ``AZURE_STORAGE_ACCOUNT_KEY``, ``AZURE_STORAGE_ACCESS_KEY``,
739
+ a usage error, a missing ``blob`` extra, an environment value ``obstore``
740
+ cannot parse, such as ``AZURE_DISABLE_TAGGING=junk``, or any of these
741
+ variables set, even empty: ``AZURE_STORAGE_ACCOUNT_KEY``,
742
+ ``AZURE_STORAGE_ACCESS_KEY``,
689
743
  ``AZURE_STORAGE_MASTER_KEY``, ``AZURE_STORAGE_SAS_KEY``,
690
744
  ``AZURE_STORAGE_SAS_TOKEN``, ``AZURE_STORAGE_TOKEN``,
691
- ``AZURE_STORAGE_ENDPOINT`` and ``AZURE_ENDPOINT``.
745
+ ``AZURE_STORAGE_ENDPOINT``, ``AZURE_ENDPOINT``,
746
+ ``AZURE_STORAGE_USE_EMULATOR``, ``AZURE_USE_FABRIC_ENDPOINT``,
747
+ ``AZURE_SKIP_SIGNATURE`` and ``AZURE_STORAGE_ENCRYPTION_KEY``. Any case
748
+ after ``AZURE_`` counts, such as ``AZURE_storage_endpoint``, since
749
+ ``obstore`` reads that spelling too.
692
750
  """
693
751
  when = _release_time(release_at)
694
752
  uri = _catalog_uri(az_url)
File without changes
File without changes