sdmxlib 0.19.0__tar.gz → 0.19.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 (73) hide show
  1. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/PKG-INFO +1 -1
  2. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/pyproject.toml +1 -1
  3. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/storage/writers.py +141 -69
  4. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/README.md +0 -0
  5. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/__init__.py +0 -0
  6. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/api/__init__.py +0 -0
  7. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/api/client.py +0 -0
  8. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/api/federated.py +0 -0
  9. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/api/filters.py +0 -0
  10. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/api/policy.py +0 -0
  11. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/api/providers.py +0 -0
  12. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/api/query.py +0 -0
  13. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/api/registry.py +0 -0
  14. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/api/session.py +0 -0
  15. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/catalog.py +0 -0
  16. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/data_store.py +0 -0
  17. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/__init__.py +0 -0
  18. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_csv/__init__.py +0 -0
  19. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_csv/reader.py +0 -0
  20. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_csv/writer.py +0 -0
  21. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_json/__init__.py +0 -0
  22. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_json/metadata.py +0 -0
  23. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_json/reader.py +0 -0
  24. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_json/writer.py +0 -0
  25. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_ml21/__init__.py +0 -0
  26. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_ml21/namespaces.py +0 -0
  27. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_ml21/reader.py +0 -0
  28. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_ml21/writer.py +0 -0
  29. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_ml30/__init__.py +0 -0
  30. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_ml30/metadata.py +0 -0
  31. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_ml30/namespaces.py +0 -0
  32. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_ml30/reader.py +0 -0
  33. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/formats/sdmx_ml30/writer.py +0 -0
  34. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/local/__init__.py +0 -0
  35. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/local/_data_store.py +0 -0
  36. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/local/_registry.py +0 -0
  37. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/__init__.py +0 -0
  38. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/annotations.py +0 -0
  39. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/base.py +0 -0
  40. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/binding.py +0 -0
  41. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/category.py +0 -0
  42. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/code_query.py +0 -0
  43. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/codelist.py +0 -0
  44. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/collections.py +0 -0
  45. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/concept.py +0 -0
  46. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/constraint.py +0 -0
  47. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/convert.py +0 -0
  48. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/dataflow.py +0 -0
  49. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/dataset.py +0 -0
  50. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/datastructure.py +0 -0
  51. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/expr.py +0 -0
  52. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/hierarchy.py +0 -0
  53. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/in_memory_registry.py +0 -0
  54. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/istring.py +0 -0
  55. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/mapping.py +0 -0
  56. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/message.py +0 -0
  57. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/metadataflow.py +0 -0
  58. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/metadataset.py +0 -0
  59. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/metadatastructure.py +0 -0
  60. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/organisation.py +0 -0
  61. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/provision.py +0 -0
  62. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/ref.py +0 -0
  63. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/registry.py +0 -0
  64. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/representation.py +0 -0
  65. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/urn.py +0 -0
  66. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/model/validation.py +0 -0
  67. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/polars.py +0 -0
  68. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/py.typed +0 -0
  69. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/rest.py +0 -0
  70. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/storage/__init__.py +0 -0
  71. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/storage/lazy.py +0 -0
  72. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/storage/readers.py +0 -0
  73. {sdmxlib-0.19.0 → sdmxlib-0.19.2}/src/sdmxlib/storage/schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sdmxlib
3
- Version: 0.19.0
3
+ Version: 0.19.2
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.19.0"
3
+ version = "0.19.2"
4
4
  description = "SDMX structural metadata library for Python"
5
5
  readme = "README.md"
6
6
  license = { text = "Apache-2.0" }
@@ -16,6 +16,7 @@ from collections.abc import Sequence
16
16
  from typing import Any
17
17
 
18
18
  import duckdb
19
+ import pyarrow as pa
19
20
 
20
21
  from sdmxlib.model.annotations import Annotations
21
22
  from sdmxlib.model.binding import DataflowBinding
@@ -69,27 +70,19 @@ def _istring_to_map(s: InternationalString | None) -> dict[str, str]:
69
70
  return {lang: s[lang] for lang in s}
70
71
 
71
72
 
72
- def _sql_literal(v: Any) -> str:
73
- """Format a Python value as a DuckDB SQL literal."""
74
- if v is None:
75
- return "NULL"
76
- if isinstance(v, bool):
77
- return "TRUE" if v else "FALSE"
78
- if isinstance(v, (int, float)):
79
- return str(v)
80
- if isinstance(v, dict):
81
- if not v:
82
- return "MAP([], [])"
83
- keys = ", ".join(f"'{k}'" for k in v)
84
- vals = ", ".join(f"'{str(val).replace(chr(39), chr(39) + chr(39))}'" for val in v.values())
85
- return f"MAP([{keys}], [{vals}])"
86
- if isinstance(v, list):
87
- if not v:
88
- return "[]"
89
- items = ", ".join(f"'{str(x).replace(chr(39), chr(39) + chr(39))}'" for x in v)
90
- return f"[{items}]"
91
- s = str(v).replace("'", "''")
92
- return f"'{s}'"
73
+ def _to_arrow_array(values: list[Any]) -> pa.Array:
74
+ """Build a pyarrow Array from a column of Python values.
75
+
76
+ Dict values become MAP(VARCHAR, VARCHAR); other types use pyarrow's
77
+ default inference, which handles None, scalars, and list[str] fine.
78
+ """
79
+ sample = next((v for v in values if v is not None), None)
80
+ if isinstance(sample, dict):
81
+ return pa.array(
82
+ [None if d is None else list(d.items()) for d in values],
83
+ type=pa.map_(pa.string(), pa.string()),
84
+ )
85
+ return pa.array(values)
93
86
 
94
87
 
95
88
  def _execute_bulk_insert(
@@ -98,15 +91,22 @@ def _execute_bulk_insert(
98
91
  cols: Sequence[str],
99
92
  rows: Sequence[Sequence[Any]],
100
93
  ) -> None:
101
- """Insert many rows in a single INSERT statement."""
94
+ """Insert many rows via Arrow columnar ingestion.
95
+
96
+ Avoids the multi-GB SQL parser allocation that VALUES-with-literals
97
+ causes for large bulk inserts (issue #66).
98
+ """
102
99
  if not rows:
103
100
  return
104
- col_str = ", ".join(cols)
105
- lit = _sql_literal
106
- values = ", ".join("(" + ", ".join(lit(v) for v in row) + ")" for row in rows)
107
- conn.execute(f"""
108
- INSERT INTO {table} ({col_str}) VALUES {values}
109
- """)
101
+ columns = list(zip(*rows, strict=True))
102
+ arrays = [_to_arrow_array(list(col)) for col in columns]
103
+ arrow_table = pa.Table.from_arrays(arrays, names=list(cols))
104
+ conn.register("__sdmx_bulk_insert", arrow_table)
105
+ try:
106
+ col_str = ", ".join(cols)
107
+ conn.execute(f"INSERT INTO {table} ({col_str}) SELECT * FROM __sdmx_bulk_insert")
108
+ finally:
109
+ conn.unregister("__sdmx_bulk_insert")
110
110
 
111
111
 
112
112
  # ── Spine + text + annotations helpers ──────────────────────────────────
@@ -188,44 +188,96 @@ def _put_localized_text(
188
188
  )
189
189
 
190
190
 
191
+ class _AnnotationBatch:
192
+ """Accumulator for annotation + annotation_text rows.
193
+
194
+ Collects rows across an entire artefact write and flushes them with a
195
+ single bulk-allocated id batch. Replaces the per-annotation
196
+ ``INSERT … RETURNING id`` roundtrip pattern that made persisting a
197
+ 78k-code codelist take ~130 s (issue #66).
198
+ """
199
+
200
+ __slots__ = ("ann_rows", "text_rows")
201
+
202
+ def __init__(self) -> None:
203
+ # Each row: [owner_urn, ordinal, annotation_id, title, annotation_type, url]
204
+ self.ann_rows: list[list[Any]] = []
205
+ # Each entry: (local_idx_into_ann_rows, lang, text). The local
206
+ # index is resolved to a real annotation.id at flush time.
207
+ self.text_rows: list[tuple[int, str, str]] = []
208
+
209
+ def add(self, owner_urn: str, annotations: Annotations | None) -> None:
210
+ if not annotations:
211
+ return
212
+ for ordinal, ann in enumerate(annotations):
213
+ local_idx = len(self.ann_rows)
214
+ self.ann_rows.append([owner_urn, ordinal, ann.id, ann.title, ann.type, ann.url])
215
+ if ann.text:
216
+ for lang in ann.text:
217
+ self.text_rows.append((local_idx, lang, ann.text[lang]))
218
+
219
+ def flush(self, conn: duckdb.DuckDBPyConnection) -> None:
220
+ if not self.ann_rows:
221
+ return
222
+ n = len(self.ann_rows)
223
+ id_rows = conn.execute("SELECT nextval('annotation_id_seq') FROM range(?)", [n]).fetchall()
224
+ ids = [r[0] for r in id_rows]
225
+
226
+ full_ann_rows = [[ids[i], *row] for i, row in enumerate(self.ann_rows)]
227
+ _execute_bulk_insert(
228
+ conn,
229
+ "annotation",
230
+ ["id", "owner_urn", "ordinal", "annotation_id", "title", "annotation_type", "url"],
231
+ full_ann_rows,
232
+ )
233
+
234
+ if self.text_rows:
235
+ text_rows = [[ids[idx], lang, text] for idx, lang, text in self.text_rows]
236
+ _execute_bulk_insert(
237
+ conn,
238
+ "annotation_text",
239
+ ["annotation_id", "lang", "text"],
240
+ text_rows,
241
+ )
242
+
243
+ self.ann_rows.clear()
244
+ self.text_rows.clear()
245
+
246
+
191
247
  def _put_annotations(
192
248
  conn: duckdb.DuckDBPyConnection,
193
249
  owner_urn: str,
194
250
  annotations: Annotations | None,
195
251
  ) -> None:
196
- """Insert annotation + annotation_text rows."""
197
- if not annotations:
198
- return
199
- for ordinal, ann in enumerate(annotations):
200
- row = conn.execute(
201
- """
202
- INSERT INTO annotation (owner_urn, ordinal, annotation_id, title, annotation_type, url)
203
- VALUES (?, ?, ?, ?, ?, ?)
204
- RETURNING id
205
- """,
206
- [owner_urn, ordinal, ann.id, ann.title, ann.type, ann.url],
207
- ).fetchone()
208
- ann_db_id = row[0] # type: ignore[index] # RETURNING always returns
209
- if ann.text:
210
- for lang in ann.text:
211
- conn.execute(
212
- """
213
- INSERT INTO annotation_text (annotation_id, lang, text)
214
- VALUES (?, ?, ?)
215
- """,
216
- [ann_db_id, lang, ann.text[lang]],
217
- )
252
+ """Insert annotation + annotation_text rows for one owner.
253
+
254
+ For artefacts with many annotated child items (e.g. codelists), prefer
255
+ building an :class:`_AnnotationBatch` directly and calling ``flush``
256
+ once at the end of the write — that avoids per-owner roundtrips.
257
+ """
258
+ batch = _AnnotationBatch()
259
+ batch.add(owner_urn, annotations)
260
+ batch.flush(conn)
218
261
 
219
262
 
220
263
  def _put_artefact_text_and_annotations(
221
264
  conn: duckdb.DuckDBPyConnection,
222
265
  urn: str,
223
266
  artefact: Any,
267
+ batch: _AnnotationBatch | None = None,
224
268
  ) -> None:
225
- """Write localized_text and annotations for an artefact."""
269
+ """Write localized_text and annotations for an artefact.
270
+
271
+ If ``batch`` is provided, annotations are queued on it (caller flushes);
272
+ otherwise they are flushed immediately.
273
+ """
226
274
  _put_localized_text(conn, urn, "name", getattr(artefact, "name", None))
227
275
  _put_localized_text(conn, urn, "description", getattr(artefact, "description", None))
228
- _put_annotations(conn, urn, getattr(artefact, "annotations", None))
276
+ anns = getattr(artefact, "annotations", None)
277
+ if batch is not None:
278
+ batch.add(urn, anns)
279
+ else:
280
+ _put_annotations(conn, urn, anns)
229
281
 
230
282
 
231
283
  # ── Reference graph ─────────────────────────────────────────────────────
@@ -330,6 +382,7 @@ def _delete_artefact_cascade(conn: duckdb.DuckDBPyConnection, urn: str) -> None:
330
382
  conn.execute("DELETE FROM concept WHERE scheme_urn = ?", p)
331
383
  conn.execute("DELETE FROM category WHERE scheme_urn = ?", p)
332
384
  conn.execute("DELETE FROM hierarchy_node WHERE hierarchy_urn = ?", p)
385
+ conn.execute("DELETE FROM hierarchy_level WHERE hierarchy_urn = ?", p)
333
386
  conn.execute("DELETE FROM agency WHERE scheme_urn = ?", p)
334
387
  conn.execute("DELETE FROM data_provider WHERE scheme_urn = ?", p)
335
388
  # Contact cleanup for agencies
@@ -393,7 +446,8 @@ def put_codelist(conn: duckdb.DuckDBPyConnection, cl: Codelist, *, source_regist
393
446
 
394
447
  _delete_artefact_cascade(conn, urn)
395
448
  _put_spine(conn, urn, "Codelist", cl, source_registry=source_registry)
396
- _put_artefact_text_and_annotations(conn, urn, cl)
449
+ ann_batch = _AnnotationBatch()
450
+ _put_artefact_text_and_annotations(conn, urn, cl, ann_batch)
397
451
 
398
452
  refs: list[tuple[str, str, str, int | None]] = []
399
453
 
@@ -417,8 +471,7 @@ def put_codelist(conn: duckdb.DuckDBPyConnection, cl: Codelist, *, source_regist
417
471
  ancestors,
418
472
  ]
419
473
  all_rows.append(row)
420
- # Write annotations for each code
421
- _put_annotations(conn, code_urn, code.annotations)
474
+ ann_batch.add(code_urn, code.annotations)
422
475
 
423
476
  _execute_bulk_insert(
424
477
  conn,
@@ -437,6 +490,7 @@ def put_codelist(conn: duckdb.DuckDBPyConnection, cl: Codelist, *, source_regist
437
490
  all_rows,
438
491
  )
439
492
 
493
+ ann_batch.flush(conn)
440
494
  _put_refs(conn, refs)
441
495
 
442
496
 
@@ -537,7 +591,8 @@ def put_data_structure(
537
591
 
538
592
  _delete_artefact_cascade(conn, urn)
539
593
  _put_spine(conn, urn, "DataStructure", dsd, source_registry=source_registry)
540
- _put_artefact_text_and_annotations(conn, urn, dsd)
594
+ ann_batch = _AnnotationBatch()
595
+ _put_artefact_text_and_annotations(conn, urn, dsd, ann_batch)
541
596
 
542
597
  components: list[tuple[str, Any]] = [("Dimension", dim) for dim in dsd.dimensions]
543
598
  if dsd.time_dimension is not None:
@@ -554,10 +609,11 @@ def put_data_structure(
554
609
  # Component owner URN follows the same item-URN convention as Codes
555
610
  # and Concepts (`<dsd_urn-with-component-class>.<component_id>`).
556
611
  comp_urn = _make_item_urn(dsd, urn, comp_type, comp.id)
557
- _put_annotations(conn, comp_urn, getattr(comp, "annotations", None))
612
+ ann_batch.add(comp_urn, getattr(comp, "annotations", None))
558
613
 
559
614
  if comp_rows:
560
615
  _execute_bulk_insert(conn, "dsd_component", _DSD_COMPONENT_COLS, comp_rows)
616
+ ann_batch.flush(conn)
561
617
 
562
618
  if dsd.groups:
563
619
  for group in dsd.groups:
@@ -602,7 +658,8 @@ def put_concept_scheme(
602
658
 
603
659
  _delete_artefact_cascade(conn, urn)
604
660
  _put_spine(conn, urn, "ConceptScheme", cs, source_registry=source_registry)
605
- _put_artefact_text_and_annotations(conn, urn, cs)
661
+ ann_batch = _AnnotationBatch()
662
+ _put_artefact_text_and_annotations(conn, urn, cs, ann_batch)
606
663
 
607
664
  refs: list[tuple[str, str, str, int | None]] = []
608
665
 
@@ -643,7 +700,7 @@ def put_concept_scheme(
643
700
  repr_max_length,
644
701
  ]
645
702
  )
646
- _put_annotations(conn, concept_urn_str, concept.annotations)
703
+ ann_batch.add(concept_urn_str, concept.annotations)
647
704
 
648
705
  _execute_bulk_insert(
649
706
  conn,
@@ -665,6 +722,7 @@ def put_concept_scheme(
665
722
  all_rows,
666
723
  )
667
724
 
725
+ ann_batch.flush(conn)
668
726
  _put_refs(conn, refs)
669
727
 
670
728
 
@@ -674,7 +732,8 @@ def put_hierarchy(conn: duckdb.DuckDBPyConnection, h: Hierarchy, *, source_regis
674
732
 
675
733
  _delete_artefact_cascade(conn, urn)
676
734
  _put_spine(conn, urn, "Hierarchy", h, source_registry=source_registry)
677
- _put_artefact_text_and_annotations(conn, urn, h)
735
+ ann_batch = _AnnotationBatch()
736
+ _put_artefact_text_and_annotations(conn, urn, h, ann_batch)
678
737
 
679
738
  conn.execute(
680
739
  "INSERT INTO hierarchy_meta (urn, is_leveled) VALUES (?, ?)",
@@ -740,7 +799,7 @@ def put_hierarchy(conn: duckdb.DuckDBPyConnection, h: Hierarchy, *, source_regis
740
799
  if code_urn:
741
800
  refs.append((urn, code_urn, "code", position))
742
801
 
743
- _put_annotations(conn, node_urn, node.annotations)
802
+ ann_batch.add(node_urn, node.annotations)
744
803
 
745
804
  _execute_bulk_insert(
746
805
  conn,
@@ -763,6 +822,8 @@ def put_hierarchy(conn: duckdb.DuckDBPyConnection, h: Hierarchy, *, source_regis
763
822
  )
764
823
  _put_refs(conn, refs)
765
824
 
825
+ ann_batch.flush(conn)
826
+
766
827
 
767
828
  def _flatten_hcodes(
768
829
  nodes: Any,
@@ -825,7 +886,8 @@ def put_category_scheme(
825
886
 
826
887
  _delete_artefact_cascade(conn, urn)
827
888
  _put_spine(conn, urn, "CategoryScheme", cs, source_registry=source_registry)
828
- _put_artefact_text_and_annotations(conn, urn, cs)
889
+ ann_batch = _AnnotationBatch()
890
+ _put_artefact_text_and_annotations(conn, urn, cs, ann_batch)
829
891
 
830
892
  # Flatten category tree
831
893
  flat: list[tuple[str, str | None, Any]] = []
@@ -851,7 +913,7 @@ def put_category_scheme(
851
913
  ancestors,
852
914
  ]
853
915
  )
854
- _put_annotations(conn, cat_urn, cat.annotations)
916
+ ann_batch.add(cat_urn, cat.annotations)
855
917
 
856
918
  _execute_bulk_insert(
857
919
  conn,
@@ -870,6 +932,8 @@ def put_category_scheme(
870
932
  all_rows,
871
933
  )
872
934
 
935
+ ann_batch.flush(conn)
936
+
873
937
 
874
938
  def _flatten_categories(
875
939
  categories: Any,
@@ -941,7 +1005,8 @@ def put_agency_scheme(
941
1005
 
942
1006
  _delete_artefact_cascade(conn, urn)
943
1007
  _put_spine(conn, urn, "AgencyScheme", ascheme, source_registry=source_registry)
944
- _put_artefact_text_and_annotations(conn, urn, ascheme)
1008
+ ann_batch = _AnnotationBatch()
1009
+ _put_artefact_text_and_annotations(conn, urn, ascheme, ann_batch)
945
1010
 
946
1011
  if ascheme.agencies:
947
1012
  all_rows: list[list[Any]] = []
@@ -979,7 +1044,7 @@ def put_agency_scheme(
979
1044
  ],
980
1045
  )
981
1046
 
982
- _put_annotations(conn, ag_urn, ag.annotations)
1047
+ ann_batch.add(ag_urn, ag.annotations)
983
1048
 
984
1049
  _execute_bulk_insert(
985
1050
  conn,
@@ -988,6 +1053,8 @@ def put_agency_scheme(
988
1053
  all_rows,
989
1054
  )
990
1055
 
1056
+ ann_batch.flush(conn)
1057
+
991
1058
 
992
1059
  def put_data_provider_scheme(
993
1060
  conn: duckdb.DuckDBPyConnection, dps: "DataProviderScheme", *, source_registry: str | None = None
@@ -997,7 +1064,8 @@ def put_data_provider_scheme(
997
1064
 
998
1065
  _delete_artefact_cascade(conn, urn)
999
1066
  _put_spine(conn, urn, "DataProviderScheme", dps, source_registry=source_registry)
1000
- _put_artefact_text_and_annotations(conn, urn, dps)
1067
+ ann_batch = _AnnotationBatch()
1068
+ _put_artefact_text_and_annotations(conn, urn, dps, ann_batch)
1001
1069
 
1002
1070
  if dps.providers:
1003
1071
  all_rows: list[list[Any]] = []
@@ -1012,7 +1080,7 @@ def put_data_provider_scheme(
1012
1080
  _istring_to_map(provider.description),
1013
1081
  ]
1014
1082
  )
1015
- _put_annotations(conn, p_urn, provider.annotations)
1083
+ ann_batch.add(p_urn, provider.annotations)
1016
1084
 
1017
1085
  _execute_bulk_insert(
1018
1086
  conn,
@@ -1021,6 +1089,8 @@ def put_data_provider_scheme(
1021
1089
  all_rows,
1022
1090
  )
1023
1091
 
1092
+ ann_batch.flush(conn)
1093
+
1024
1094
 
1025
1095
  def put_structure_set(
1026
1096
  conn: duckdb.DuckDBPyConnection, ss: "StructureSet", *, source_registry: str | None = None
@@ -1112,7 +1182,8 @@ def put_metadata_structure(
1112
1182
 
1113
1183
  _delete_artefact_cascade(conn, urn)
1114
1184
  _put_spine(conn, urn, "MetadataStructure", msd, source_registry=source_registry)
1115
- _put_artefact_text_and_annotations(conn, urn, msd)
1185
+ ann_batch = _AnnotationBatch()
1186
+ _put_artefact_text_and_annotations(conn, urn, msd, ann_batch)
1116
1187
 
1117
1188
  conn.execute("INSERT INTO metadata_structure (urn) VALUES (?)", [urn])
1118
1189
 
@@ -1177,10 +1248,11 @@ def put_metadata_structure(
1177
1248
  ancestors,
1178
1249
  ]
1179
1250
  )
1180
- _put_annotations(conn, attr_urn, attr.annotations)
1251
+ ann_batch.add(attr_urn, attr.annotations)
1181
1252
 
1182
1253
  _execute_bulk_insert(conn, "metadata_attribute", _METADATA_ATTRIBUTE_COLS, rows)
1183
1254
 
1255
+ ann_batch.flush(conn)
1184
1256
  _put_refs(conn, refs)
1185
1257
 
1186
1258
 
File without changes
File without changes
File without changes
File without changes