boti-data 1.3.0__tar.gz → 1.3.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.
- {boti_data-1.3.0 → boti_data-1.3.2}/PKG-INFO +1 -1
- {boti_data-1.3.0 → boti_data-1.3.2}/pyproject.toml +1 -1
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sql_config.py +16 -2
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sql_model_registry.py +63 -48
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sql_repository.py +13 -6
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data.egg-info/PKG-INFO +1 -1
- {boti_data-1.3.0 → boti_data-1.3.2}/README.md +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/setup.cfg +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/__init__.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/arrow_schema.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/connection_catalog.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/datacube/__init__.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/datacube/artifact.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/datacube/base.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/datacube/contract.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/datacube/resource.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/dataset/__init__.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/dataset/hybrid.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/dataset/hybrid_support.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/__init__.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/arrow_schema_mapper.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/engine_registry.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/partitioned_execution.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/partitioned_loader.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/partitioned_planner.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/partitioned_planner_async.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/partitioned_planning_math.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/partitioned_row_materialization.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/partitioned_statements.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/partitioned_types.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sql_engine.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sql_manager.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sql_model_builder.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sql_model_module_injection.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sql_model_registry_types.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sql_readonly.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sql_resource.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/sqlalchemy_async.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/db/vector_search.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/enrichment/__init__.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/enrichment/async_enricher.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/enrichment/composite.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/enrichment/protocol.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/enrichment/specs.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/enrichment/transformers.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/field_map.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/__init__.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/arrow_kernels.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/arrow_kernels_comparison.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/arrow_kernels_compile.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/arrow_kernels_string.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/backend_ops.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/expressions.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/handler.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/parquet_compiler.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/utils.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/filters/value_parsing.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/__init__.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/_backend_strategies.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/_factory.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/_gateway_init.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/_payloads.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/_series_filters.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/arrow_adapters.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/build_context.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/chunking.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/configured_load.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/core.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/core_load.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/datacube_strategy.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/frame_convert.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/frame_strategies.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/incremental.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/load_execution.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/load_request.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/loaders.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/normalization.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/parquet_strategy.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/planning.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/policies.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/post_process.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/requests.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/return_type.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/select_cache.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/semi_join.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/sql_guard.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/sql_partitioned_exec.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/sql_size_estimation.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/sql_strategy.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/gateway/sql_strategy_configured_async.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/helper.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/joins.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/parquet/__init__.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/parquet/config.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/parquet/discovery.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/parquet/file_info.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/parquet/filesystem.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/parquet/reader.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/parquet/reader_config.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/parquet/resource.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/parquet/schema_filters.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/pipelines/__init__.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/pipelines/base.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/pipelines/parquet_pipeline.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/pipelines/registry.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/pipelines/sinks.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/pipelines/sinks_common.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/pipelines/sinks_parquet.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/pipelines/sinks_text.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/py.typed +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/schema.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/watermark/__init__.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/watermark/incremental.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data/watermark/store.py +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data.egg-info/SOURCES.txt +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data.egg-info/dependency_links.txt +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data.egg-info/requires.txt +0 -0
- {boti_data-1.3.0 → boti_data-1.3.2}/src/boti_data.egg-info/top_level.txt +0 -0
|
@@ -18,7 +18,19 @@ _ALLOWED_POOLCLASS_IMPORTS = {
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class SqlDatabaseConfig(ResourceConfig):
|
|
21
|
-
"""Immutable configuration for SQLAlchemy engine initialization.
|
|
21
|
+
"""Immutable configuration for SQLAlchemy engine initialization.
|
|
22
|
+
|
|
23
|
+
``query_only=True`` enforces read-only access at the database level (via
|
|
24
|
+
a ``SET ... READ ONLY``-style statement issued on the SQLAlchemy
|
|
25
|
+
``"connect"`` event, not just by blocking ORM methods in Python) — this
|
|
26
|
+
assumes ``connection_url`` points directly at Postgres/MySQL, or at a
|
|
27
|
+
*session*-pooling-mode connection pooler (one physical backend connection
|
|
28
|
+
dedicated to one logical client for its whole lifetime). It does **not**
|
|
29
|
+
cover a *transaction*-pooling-mode pooler (e.g. PgBouncer in ``transaction``
|
|
30
|
+
mode): there, a single physical connection can be handed to a different,
|
|
31
|
+
unrelated logical client between transactions, and session-level state set
|
|
32
|
+
once at physical-connect time is not guaranteed to survive that handoff.
|
|
33
|
+
"""
|
|
22
34
|
|
|
23
35
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
24
36
|
|
|
@@ -27,7 +39,9 @@ class SqlDatabaseConfig(ResourceConfig):
|
|
|
27
39
|
default=True,
|
|
28
40
|
description=(
|
|
29
41
|
"When true, native database-enforced read-only access is required. "
|
|
30
|
-
"Set explicitly to false to allow writes."
|
|
42
|
+
"Set explicitly to false to allow writes. Assumes a direct connection "
|
|
43
|
+
"or a session-pooling-mode proxy — transaction-pooling-mode poolers "
|
|
44
|
+
"(e.g. PgBouncer 'transaction' mode) are out of scope; see class docstring."
|
|
31
45
|
),
|
|
32
46
|
)
|
|
33
47
|
worker_connection_env_var: str | None = Field(
|
|
@@ -238,22 +238,34 @@ class SqlModelRegistry:
|
|
|
238
238
|
md = self._get_or_create_metadata(ekey, schema)
|
|
239
239
|
md_lock = self._get_or_create_md_lock(md_key)
|
|
240
240
|
qname = self._qualified_key(schema, tname)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
241
|
+
|
|
242
|
+
# The whole reflect -> resolve-or-build -> cache-write sequence must
|
|
243
|
+
# be one atomic unit per md_key: reflection alone was already
|
|
244
|
+
# serialized via md_lock, but resolve-or-build and the cache write
|
|
245
|
+
# previously ran outside it, so two concurrent first-accesses for the
|
|
246
|
+
# same table could both pass "does a mapped class already exist?"
|
|
247
|
+
# before either finished building — each got a distinct Python class
|
|
248
|
+
# mapped to the same Table.
|
|
249
|
+
with md_lock:
|
|
250
|
+
cached = self._check_cache(model_key, refresh=False)
|
|
251
|
+
if cached is not None:
|
|
252
|
+
return cached
|
|
253
|
+
|
|
254
|
+
tbl = self._reflect_table(engine, md, qname=qname, tname=tname, schema=schema)
|
|
255
|
+
context = ModelBuildContext(
|
|
256
|
+
qname=qname,
|
|
257
|
+
tname=tname,
|
|
258
|
+
base_class=base_class,
|
|
259
|
+
module_label=module_label,
|
|
260
|
+
prefer_stable_names=prefer_stable_names,
|
|
261
|
+
ekey=ekey,
|
|
262
|
+
schema=schema,
|
|
263
|
+
missing_pk_warning_suffix="Ensure ORM mutation sequences validate bounds manually.",
|
|
264
|
+
)
|
|
265
|
+
model_cls = self._resolve_or_build_model(tbl, qname, context, target_label="DB target")
|
|
266
|
+
with self._lock:
|
|
267
|
+
self._model_cache[model_key] = model_cls
|
|
268
|
+
return model_cls
|
|
257
269
|
|
|
258
270
|
async def get_model_async(
|
|
259
271
|
self,
|
|
@@ -278,26 +290,33 @@ class SqlModelRegistry:
|
|
|
278
290
|
md = self._get_or_create_metadata(ekey, schema)
|
|
279
291
|
md_lock = await self._get_or_create_md_lock_async(md_key)
|
|
280
292
|
qname = self._qualified_key(schema, tname)
|
|
281
|
-
tbl = await self._reflect_table_async(
|
|
282
|
-
engine, md, md_lock, qname=qname, tname=tname, schema=schema
|
|
283
|
-
)
|
|
284
293
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
294
|
+
# See get_model()'s comment: the whole sequence must be one atomic
|
|
295
|
+
# unit per md_key, not just the reflection step.
|
|
296
|
+
async with md_lock:
|
|
297
|
+
cached = await self._check_cache_async(model_key, refresh=False)
|
|
298
|
+
if cached is not None:
|
|
299
|
+
return cached
|
|
300
|
+
|
|
301
|
+
tbl = await self._reflect_table_async(
|
|
302
|
+
engine, md, qname=qname, tname=tname, schema=schema
|
|
303
|
+
)
|
|
304
|
+
context = ModelBuildContext(
|
|
305
|
+
qname=qname,
|
|
306
|
+
tname=tname,
|
|
307
|
+
base_class=base_class,
|
|
308
|
+
module_label=module_label,
|
|
309
|
+
prefer_stable_names=prefer_stable_names,
|
|
310
|
+
ekey=ekey,
|
|
311
|
+
schema=schema,
|
|
312
|
+
missing_pk_warning_suffix="Ensure Async ORM validation sequences capture bounds correctly.",
|
|
313
|
+
)
|
|
314
|
+
model_cls = self._resolve_or_build_model(
|
|
315
|
+
tbl, qname, context, target_label="Async DB target"
|
|
316
|
+
)
|
|
317
|
+
async with self._async_lock:
|
|
318
|
+
self._model_cache[model_key] = model_cls
|
|
319
|
+
return model_cls
|
|
301
320
|
|
|
302
321
|
def _resolve_or_build_model(
|
|
303
322
|
self,
|
|
@@ -335,12 +354,15 @@ class SqlModelRegistry:
|
|
|
335
354
|
|
|
336
355
|
# Not a copy-pasted twin: sync md.reflect() vs conn.run_sync(md.reflect) is
|
|
337
356
|
# a genuine concurrency-primitive difference (see get_model()'s comment above).
|
|
357
|
+
# Both callers (get_model/get_model_async) already hold this md_key's
|
|
358
|
+
# md_lock/md_async_lock for the whole reflect -> resolve-or-build -> cache
|
|
359
|
+
# sequence, so no locking happens here — acquiring the same (non-reentrant)
|
|
360
|
+
# lock again inside would deadlock.
|
|
338
361
|
@staticmethod
|
|
339
362
|
# spaghetti-ignore[sync-async-duplication]: see above
|
|
340
363
|
def _reflect_table(
|
|
341
364
|
engine: Engine,
|
|
342
365
|
md: MetaData,
|
|
343
|
-
md_lock: threading.Lock,
|
|
344
366
|
*,
|
|
345
367
|
qname: str,
|
|
346
368
|
tname: str,
|
|
@@ -348,18 +370,14 @@ class SqlModelRegistry:
|
|
|
348
370
|
) -> Table | None:
|
|
349
371
|
tbl = md.tables.get(qname)
|
|
350
372
|
if tbl is None:
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if tbl is None:
|
|
354
|
-
md.reflect(bind=engine, only=[tname], schema=schema)
|
|
355
|
-
tbl = md.tables.get(qname)
|
|
373
|
+
md.reflect(bind=engine, only=[tname], schema=schema)
|
|
374
|
+
tbl = md.tables.get(qname)
|
|
356
375
|
return tbl
|
|
357
376
|
|
|
358
377
|
@staticmethod
|
|
359
378
|
async def _reflect_table_async(
|
|
360
379
|
engine: AsyncEngine,
|
|
361
380
|
md: MetaData,
|
|
362
|
-
md_lock: asyncio.Lock,
|
|
363
381
|
*,
|
|
364
382
|
qname: str,
|
|
365
383
|
tname: str,
|
|
@@ -367,12 +385,9 @@ class SqlModelRegistry:
|
|
|
367
385
|
) -> Table | None:
|
|
368
386
|
tbl = md.tables.get(qname)
|
|
369
387
|
if tbl is None:
|
|
370
|
-
async with
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
async with engine.begin() as conn:
|
|
374
|
-
await conn.run_sync(md.reflect, only=[tname], schema=schema)
|
|
375
|
-
tbl = md.tables.get(qname)
|
|
388
|
+
async with engine.begin() as conn:
|
|
389
|
+
await conn.run_sync(md.reflect, only=[tname], schema=schema)
|
|
390
|
+
tbl = md.tables.get(qname)
|
|
376
391
|
return tbl
|
|
377
392
|
|
|
378
393
|
def clear(self) -> None:
|
|
@@ -26,6 +26,7 @@ from sqlalchemy.orm import Session
|
|
|
26
26
|
|
|
27
27
|
from boti_data.db.sql_config import SqlDatabaseConfig
|
|
28
28
|
from boti_data.db.sql_model_registry import get_global_registry
|
|
29
|
+
from boti_data.db.sql_readonly import ReadOnlyAsyncSession, ReadOnlySession
|
|
29
30
|
from boti_data.db.sql_resource import AsyncSqlDatabaseResource, SqlDatabaseResource
|
|
30
31
|
|
|
31
32
|
__all__ = ["AsyncSqlRepository", "AsyncSqlUnitOfWork", "SqlRepository", "SqlUnitOfWork"]
|
|
@@ -297,10 +298,13 @@ class SqlUnitOfWork:
|
|
|
297
298
|
|
|
298
299
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
299
300
|
try:
|
|
300
|
-
if exc_type is None:
|
|
301
|
-
self._session.commit()
|
|
302
|
-
else:
|
|
301
|
+
if exc_type is not None:
|
|
303
302
|
self._session.rollback()
|
|
303
|
+
elif not isinstance(self._session, ReadOnlySession):
|
|
304
|
+
self._session.commit()
|
|
305
|
+
# else: read-only session, exited cleanly — a read never needs a
|
|
306
|
+
# commit, and ReadOnlySession.commit() always raises regardless
|
|
307
|
+
# of whether anything was actually written.
|
|
304
308
|
finally:
|
|
305
309
|
# Each handed-out repository has its own GC leak-warning
|
|
306
310
|
# finalizer (see boti.core.lifecycle) even though its own
|
|
@@ -340,10 +344,13 @@ class AsyncSqlUnitOfWork:
|
|
|
340
344
|
|
|
341
345
|
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
342
346
|
try:
|
|
343
|
-
if exc_type is None:
|
|
344
|
-
await self._session.commit()
|
|
345
|
-
else:
|
|
347
|
+
if exc_type is not None:
|
|
346
348
|
await self._session.rollback()
|
|
349
|
+
elif not isinstance(self._session, ReadOnlyAsyncSession):
|
|
350
|
+
await self._session.commit()
|
|
351
|
+
# else: read-only session, exited cleanly — a read never needs a
|
|
352
|
+
# commit, and ReadOnlyAsyncSession.commit() always raises
|
|
353
|
+
# regardless of whether anything was actually written.
|
|
347
354
|
finally:
|
|
348
355
|
# See SqlUnitOfWork.__exit__'s comment: each handed-out repository
|
|
349
356
|
# has its own GC leak-warning finalizer even though its _acleanup()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|