sdmxlib 0.24.0__tar.gz → 0.26.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.
Files changed (75) hide show
  1. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/PKG-INFO +1 -1
  2. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/pyproject.toml +1 -1
  3. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/__init__.py +4 -1
  4. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/data_store.py +5 -23
  5. sdmxlib-0.26.0/src/sdmxlib/sql.py +146 -0
  6. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/README.md +0 -0
  7. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/api/__init__.py +0 -0
  8. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/api/client.py +0 -0
  9. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/api/federated.py +0 -0
  10. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/api/filters.py +0 -0
  11. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/api/policy.py +0 -0
  12. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/api/providers.py +0 -0
  13. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/api/query.py +0 -0
  14. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/api/registry.py +0 -0
  15. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/api/session.py +0 -0
  16. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/catalog.py +0 -0
  17. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/__init__.py +0 -0
  18. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_csv/__init__.py +0 -0
  19. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_csv/reader.py +0 -0
  20. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_csv/writer.py +0 -0
  21. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_json/__init__.py +0 -0
  22. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_json/metadata.py +0 -0
  23. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_json/reader.py +0 -0
  24. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_json/writer.py +0 -0
  25. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_ml21/__init__.py +0 -0
  26. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_ml21/namespaces.py +0 -0
  27. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_ml21/reader.py +0 -0
  28. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_ml21/writer.py +0 -0
  29. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_ml30/__init__.py +0 -0
  30. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_ml30/metadata.py +0 -0
  31. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_ml30/namespaces.py +0 -0
  32. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_ml30/reader.py +0 -0
  33. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/formats/sdmx_ml30/writer.py +0 -0
  34. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/local/__init__.py +0 -0
  35. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/local/_data_store.py +0 -0
  36. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/local/_registry.py +0 -0
  37. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/__init__.py +0 -0
  38. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/annotations.py +0 -0
  39. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/base.py +0 -0
  40. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/binding.py +0 -0
  41. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/category.py +0 -0
  42. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/code_query.py +0 -0
  43. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/codelist.py +0 -0
  44. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/collections.py +0 -0
  45. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/concept.py +0 -0
  46. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/constraint.py +0 -0
  47. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/convert.py +0 -0
  48. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/dataflow.py +0 -0
  49. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/dataset.py +0 -0
  50. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/datastructure.py +0 -0
  51. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/expr.py +0 -0
  52. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/hierarchy.py +0 -0
  53. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/hierarchy_query.py +0 -0
  54. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/in_memory_registry.py +0 -0
  55. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/istring.py +0 -0
  56. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/mapping.py +0 -0
  57. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/message.py +0 -0
  58. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/metadataflow.py +0 -0
  59. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/metadataset.py +0 -0
  60. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/metadatastructure.py +0 -0
  61. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/organisation.py +0 -0
  62. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/provision.py +0 -0
  63. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/ref.py +0 -0
  64. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/registry.py +0 -0
  65. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/representation.py +0 -0
  66. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/urn.py +0 -0
  67. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/model/validation.py +0 -0
  68. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/polars.py +0 -0
  69. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/py.typed +0 -0
  70. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/rest.py +0 -0
  71. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/storage/__init__.py +0 -0
  72. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/storage/lazy.py +0 -0
  73. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/storage/readers.py +0 -0
  74. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/storage/schema.py +0 -0
  75. {sdmxlib-0.24.0 → sdmxlib-0.26.0}/src/sdmxlib/storage/writers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sdmxlib
3
- Version: 0.24.0
3
+ Version: 0.26.0
4
4
  Summary: SDMX structural metadata library for Python
5
5
  Keywords: sdmx,statistics,metadata,datastructure
6
6
  Author: gabrielgellner
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sdmxlib"
3
- version = "0.24.0"
3
+ version = "0.26.0"
4
4
  description = "SDMX structural metadata library for Python"
5
5
  readme = "README.md"
6
6
  license = { text = "Apache-2.0" }
@@ -7,7 +7,8 @@ from sdmxlib.api.providers import Provider
7
7
  from sdmxlib.api.query import DataQuery
8
8
  from sdmxlib.api.registry import BearerToken, FmrRegistry, RestRegistry
9
9
  from sdmxlib.catalog import DataflowEntry, FederatedCatalog
10
- from sdmxlib.data_store import DataStore, SqlRelation
10
+ from sdmxlib.data_store import DataStore
11
+ from sdmxlib.sql import CompileContext, Predicate, SqlRelation
11
12
  from sdmxlib.formats import parse, read, serialize, to_json, to_xml
12
13
  from sdmxlib.local import LazyCodelist, LazyHierarchy, Registry
13
14
  from sdmxlib.local._data_store import DuckDBDataStore
@@ -113,6 +114,7 @@ __all__ = [
113
114
  "CodeQuery",
114
115
  "Codelist",
115
116
  "CodelistMap",
117
+ "CompileContext",
116
118
  "ComponentFilter",
117
119
  "ComponentMap",
118
120
  "Concept",
@@ -166,6 +168,7 @@ __all__ = [
166
168
  "MetadataSet",
167
169
  "MetadataStructure",
168
170
  "Metadataflow",
171
+ "Predicate",
169
172
  "Provider",
170
173
  "ProvisionAgreement",
171
174
  "Ref",
@@ -25,35 +25,17 @@ import polars as pl
25
25
 
26
26
  from sdmxlib.model.binding import DataflowBinding
27
27
 
28
+ # SqlRelation lives in sdmxlib.sql now (suite-level public contract);
29
+ # re-exported here for backwards compatibility with code that imports
30
+ # ``from sdmxlib.data_store import SqlRelation``.
31
+ from sdmxlib.sql import SqlRelation as SqlRelation # noqa: PLC0414 — public re-export
32
+
28
33
  if TYPE_CHECKING:
29
34
  import pyarrow as pa
30
35
 
31
36
  from sdmxlib.rest import ComponentFilter
32
37
 
33
38
 
34
- @runtime_checkable
35
- class SqlRelation(Protocol):
36
- """A lazy single-column relation usable inside ``WHERE col IN (...)``.
37
-
38
- Producers (typically :class:`~sdmxlib.model.code_query.CodeQuery`)
39
- expose their compiled SELECT as a ``(sql, params)`` pair. The
40
- streaming-query SQL builder splices the SQL inline as a subquery
41
- and concatenates the params with the outer query's binds, so a
42
- 50k-row hierarchy traversal becomes one ``IN (SELECT ...)`` clause
43
- with no parameter explosion.
44
-
45
- The subquery's tables must be visible on the data store's
46
- connection. For :class:`~sdmxlib.local.DuckDBDataStore` (which
47
- shares its connection with :class:`~sdmxlib.local.Registry`),
48
- codelist relations compose automatically because both sides see
49
- the same ``sdmx.code`` table.
50
- """
51
-
52
- def to_sql(self) -> tuple[str, list[object]]:
53
- """Return ``(sql, params)`` — a SELECT producing a single column."""
54
- ...
55
-
56
-
57
39
  @runtime_checkable
58
40
  class DataStore(Protocol):
59
41
  """Read-side contract for the data plane.
@@ -0,0 +1,146 @@
1
+ """Shared SQL-composition primitives — used across pinax-suite.
2
+
3
+ Lifted from internal helpers in ``sdmxlib.data_store`` and
4
+ ``sdmxlib.model.code_query`` so other libraries (pinaxlib, future
5
+ catalog-style tools) can compose with sdmxlib's lazy SQL machinery
6
+ without re-implementing the contracts.
7
+
8
+ Three primitives:
9
+
10
+ - :class:`SqlRelation` — a producer of ``(sql, params)`` for use as a
11
+ single-column subquery. Lets a 50k-row hierarchy traversal compile
12
+ to one ``IN (SELECT …)`` clause, with no parameter explosion. This
13
+ is the load-bearing pattern for cross-library lazy composition.
14
+ - :class:`Predicate` — a typed query predicate that compiles to a SQL
15
+ fragment. Composes into a tree on a builder; the builder's
16
+ compilation walks the tree once and emits one SQL.
17
+ - :class:`CompileContext` — state threaded through one compilation
18
+ pass: the row alias for the main table, accumulators for joins,
19
+ and a join-alias dedup map (so multiple predicates touching the
20
+ same bridge table or annotation type share one join).
21
+
22
+ Topology B from ``bundle-architecture.md`` (single DuckDB file
23
+ co-locating ``sdmx.*``, ``pinax.*``, and the DuckLake catalog tables)
24
+ is what makes :class:`SqlRelation` composition routinely useful across
25
+ sdmxlib + pinaxlib: subquery tables are always visible on the same
26
+ connection.
27
+
28
+ See ``predicate-and-sqlrelation.md`` in the statcan-catalog-builder
29
+ design docs for the full design rationale and decisions D1-D10.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ from dataclasses import dataclass, field
35
+ from typing import Protocol, runtime_checkable
36
+
37
+ __all__ = [
38
+ "CompileContext",
39
+ "Predicate",
40
+ "SqlRelation",
41
+ ]
42
+
43
+
44
+ @runtime_checkable
45
+ class SqlRelation(Protocol):
46
+ """A lazy single-column relation usable inside ``WHERE col IN (...)``.
47
+
48
+ Producers (typically :class:`~sdmxlib.model.code_query.CodeQuery`,
49
+ :class:`~sdmxlib.model.hierarchy_query.HierarchyNodeQuery`, and the
50
+ pinaxlib query builders) expose their compiled SELECT as a
51
+ ``(sql, params)`` pair. The streaming-query SQL builder splices the
52
+ SQL inline as a subquery and concatenates the params with the outer
53
+ query's binds, so an N-row traversal becomes one
54
+ ``IN (SELECT ...)`` clause with no parameter explosion.
55
+
56
+ The subquery's tables must be visible on the consumer's connection.
57
+ For :class:`~sdmxlib.local.DuckDBDataStore` (which shares its
58
+ connection with :class:`~sdmxlib.local.Registry`), codelist
59
+ relations compose automatically because both sides see the same
60
+ ``sdmx.code`` table. For pinaxlib's ``Catalog``, the same applies
61
+ when the bundle uses Topology B (one DuckDB file holding sdmx.*,
62
+ pinax.*, and the DuckLake catalog).
63
+ """
64
+
65
+ def to_sql(self) -> tuple[str, list[object]]:
66
+ """Return ``(sql, params)`` — a SELECT producing a single column."""
67
+ ...
68
+
69
+
70
+ @dataclass
71
+ class CompileContext:
72
+ """State threaded through one compilation pass.
73
+
74
+ Predicate compilers receive a context object so multiple predicates
75
+ in the same query can share state — most importantly, joins. The
76
+ ``join_aliases`` map records "did we already join the bridge table
77
+ for this query?"; a second predicate touching the same join reuses
78
+ the alias instead of adding a duplicate.
79
+
80
+ Attributes:
81
+ row_alias: The main table's alias in the outer query (e.g.
82
+ ``"d"`` for ``pinax.dataset d``, ``"c"`` for ``sdmx.code c``,
83
+ ``"o"`` for an observation-table scan).
84
+ join_aliases: Dedup map for repeated joins. Keyed by an
85
+ implementation-defined join id (e.g. annotation type,
86
+ bridge table name + dimension); value is the SQL alias
87
+ assigned the first time the join was added.
88
+ join_sqls: Accumulated JOIN fragments to splice between
89
+ ``FROM`` and ``WHERE`` in the outer query.
90
+ join_params: Parameters for the accumulated joins, in
91
+ insertion order.
92
+ schemas: Logical-to-physical schema name map. Default identity
93
+ (``"sdmx"`` → ``"sdmx"``, ``"pinax"`` → ``"pinax"``);
94
+ override for federation or alternate naming.
95
+ """
96
+
97
+ row_alias: str
98
+ join_aliases: dict[str, str] = field(default_factory=dict)
99
+ join_sqls: list[str] = field(default_factory=list)
100
+ join_params: list[object] = field(default_factory=list)
101
+ schemas: dict[str, str] = field(default_factory=dict)
102
+ _alias_counter: int = 0
103
+
104
+ def schema(self, logical: str) -> str:
105
+ """Resolve a logical schema name to its physical SQL identifier."""
106
+ return self.schemas.get(logical, logical)
107
+
108
+ def next_alias(self, prefix: str = "f") -> str:
109
+ """Generate a unique SQL alias for use in a subquery or join.
110
+
111
+ Each call returns a fresh ``{prefix}{n}`` identifier. Predicate
112
+ compilers use this to give correlated subqueries unique table
113
+ aliases when multiple predicates would otherwise collide.
114
+
115
+ The counter is per-context (per-compilation-pass), so aliases are
116
+ deterministic within a query and naturally re-numbered for the
117
+ next compilation. Use a prefix like ``"ef"`` (exists filter) or
118
+ ``"j"`` (join) to make the generated SQL self-documenting.
119
+ """
120
+ self._alias_counter += 1
121
+ return f"{prefix}{self._alias_counter}"
122
+
123
+
124
+ @runtime_checkable
125
+ class Predicate(Protocol):
126
+ """A query predicate that compiles to a SQL WHERE-clause fragment.
127
+
128
+ Implementations live in pinaxlib (e.g. ``has_code``, ``MapFilter``,
129
+ ``ExistsFilter``) and sdmxlib (e.g. ``code.id == "X"``,
130
+ ``dim("FREQ") == "M"``). Both library's compilers reuse the same
131
+ :class:`CompileContext`, so a single query containing predicates
132
+ from both libraries compiles into one SQL with shared joins.
133
+
134
+ The implementation may add to ``ctx.join_sqls`` / ``ctx.join_params``
135
+ via :func:`CompileContext.join_aliases` dedup as a side effect of
136
+ compilation.
137
+
138
+ Returns:
139
+ ``(fragment, params)`` — a SQL fragment safe to AND into a
140
+ WHERE clause, and the parameters for it. The fragment must
141
+ reference the main table via ``ctx.row_alias``.
142
+ """
143
+
144
+ def to_sql(self, ctx: CompileContext) -> tuple[str, list[object]]:
145
+ """Emit a SQL fragment + params for this predicate."""
146
+ ...
File without changes
File without changes
File without changes
File without changes