litedbmodel-runtime 2.2.13__tar.gz → 2.2.15__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 (38) hide show
  1. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/PKG-INFO +3 -3
  2. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime/__init__.py +1 -1
  3. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime/connection_routing.py +28 -21
  4. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime/driver.py +384 -74
  5. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime/exec_context.py +111 -42
  6. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime/leaves.py +4 -4
  7. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime/middleware.py +45 -13
  8. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime/tx_options.py +4 -0
  9. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime.egg-info/PKG-INFO +3 -3
  10. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime.egg-info/SOURCES.txt +3 -0
  11. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime.egg-info/requires.txt +2 -2
  12. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/pyproject.toml +3 -3
  13. litedbmodel_runtime-2.2.15/tests/test_conn_retry_livedb.py +215 -0
  14. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_connection_routing.py +2 -2
  15. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_connection_routing_livedb.py +40 -6
  16. litedbmodel_runtime-2.2.15/tests/test_driver_cells.py +94 -0
  17. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_exec_context.py +6 -6
  18. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_middleware.py +52 -7
  19. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_middleware_livedb.py +6 -6
  20. litedbmodel_runtime-2.2.15/tests/test_mysql_returning_livedb.py +345 -0
  21. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_transaction_boundary.py +104 -23
  22. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_tx_boundary_livedb.py +12 -11
  23. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/README.md +0 -0
  24. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime/dialect.py +0 -0
  25. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime/errors.py +0 -0
  26. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime/grouping.py +0 -0
  27. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime.egg-info/dependency_links.txt +0 -0
  28. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/litedbmodel_runtime.egg-info/top_level.txt +0 -0
  29. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/setup.cfg +0 -0
  30. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_conformance_corpus.py +0 -0
  31. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_consumes_bc_core.py +0 -0
  32. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_dialect.py +0 -0
  33. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_dynamic_where.py +0 -0
  34. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_grouping.py +0 -0
  35. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_hard_limit_guard.py +0 -0
  36. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_orm_bench_native.py +0 -0
  37. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_render.py +0 -0
  38. {litedbmodel_runtime-2.2.13 → litedbmodel_runtime-2.2.15}/tests/test_tx_options.py +0 -0
@@ -1,18 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: litedbmodel-runtime
3
- Version: 2.2.13
3
+ Version: 2.2.15
4
4
  Summary: Thin multi-language runtime for litedbmodel v2 SCP makeSQL bundles (Python port). Executes the published static-makeSQL artifacts (a read ReadGraph = a bc surrogate ComponentGraphIR + per-node static statement templates, or a gate-first TransactionPlan) against a SQL driver — semantics-identical to the TS reference. Delegates the Expression-IR evaluation + map/Φ/wiring orchestration to behavior-contracts.
5
5
  Author: foo-ogawa
6
6
  License: MIT
7
7
  Keywords: orm,sql,ir,runtime,conformance,litedbmodel
8
8
  Requires-Python: >=3.9
9
9
  Description-Content-Type: text/markdown
10
- Requires-Dist: behavior-contracts==0.11.20
10
+ Requires-Dist: behavior-contracts==0.11.22
11
11
  Provides-Extra: test
12
12
  Requires-Dist: pytest>=7.0; extra == "test"
13
13
  Provides-Extra: livedb
14
14
  Requires-Dist: psycopg[binary]>=3.1; extra == "livedb"
15
- Requires-Dist: pymysql>=1.1; extra == "livedb"
15
+ Requires-Dist: pymysql>=1.1.1; extra == "livedb"
16
16
 
17
17
  # litedbmodel-runtime (Python)
18
18
 
@@ -104,7 +104,7 @@ from .connection_routing import (
104
104
  )
105
105
  from .leaves import make_handlers, render_placeholders
106
106
 
107
- __version__ = "2.2.13"
107
+ __version__ = "2.2.15"
108
108
 
109
109
  __all__ = [
110
110
  "__version__",
@@ -101,7 +101,9 @@ class ConnectionConfig:
101
101
  - ``idle_timeout_millis``: ms an unused pooled connection stays open before it is closed
102
102
  (@default 10000).
103
103
  - ``search_path``: PG ``search_path`` set on each pooled connection at checkout (schema routing).
104
- - ``charset``: MySQL connection charset / PG ``client_encoding`` set on each pooled connection.
104
+ - ``charset``: MySQL connection charset (passed to ``pymysql.connect``, so PyMySQL escapes and
105
+ encodes bound values in the same charset the server parses them in) / PG ``client_encoding``
106
+ set on each pooled connection.
105
107
  """
106
108
 
107
109
  __slots__ = (
@@ -257,7 +259,11 @@ def session_statements(config: ResolvedConnectionConfig) -> List[str]:
257
259
  - **statement timeout** (``query_timeout`` > 0): PG ``SET statement_timeout = <ms>``; MySQL
258
260
  ``SET SESSION max_execution_time = <ms>`` (both server-side, ms).
259
261
  - **search_path**: PG ``SET search_path TO <path>``; MySQL has no schema search path ⇒ ignored.
260
- - **charset**: MySQL ``SET NAMES <charset>``; PG ``SET client_encoding TO <charset>``.
262
+ - **charset**: PG ``SET client_encoding TO <charset>`` (psycopg binds params on the server and
263
+ encodes them in the session's ``client_encoding``, so the session statement is the one source).
264
+ MySQL ⇒ nothing here: it is a CONNECT option (:func:`_mysql_connect_kwargs`), because PyMySQL
265
+ escapes and encodes params on the CLIENT in its connect charset — a ``SET NAMES`` alone would
266
+ make the server parse in a charset the client did not escape in (#353).
261
267
 
262
268
  A key with no value emits nothing (⇒ empty list for an all-default config ⇒ the session is
263
269
  untouched, backward-compatible). sqlite has no server session ⇒ empty.
@@ -274,17 +280,15 @@ def session_statements(config: ResolvedConnectionConfig) -> List[str]:
274
280
  )
275
281
  if config.search_path is not None and dialect == "postgres":
276
282
  out.append("SET search_path TO %s" % config.search_path)
277
- if config.charset is not None:
278
- out.append(
279
- "SET NAMES %s" % config.charset if dialect == "mysql" else "SET client_encoding TO %s" % config.charset
280
- )
283
+ if config.charset is not None and dialect == "postgres":
284
+ out.append("SET client_encoding TO %s" % config.charset)
281
285
  return out
282
286
 
283
287
 
284
288
  def session_reset_statements(config: ResolvedConnectionConfig) -> List[str]:
285
289
  """The RESET statements that undo :func:`session_statements` on release (per dialect), so a session
286
- knob (``statement_timeout`` / ``search_path`` / ``client_encoding`` / ``max_execution_time`` /
287
- charset) set for THIS configured connection does NOT leak to the next caller that draws the SAME
290
+ knob (``statement_timeout`` / ``search_path`` / ``client_encoding`` / ``max_execution_time``)
291
+ set for THIS configured connection does NOT leak to the next caller that draws the SAME
288
292
  underlying pooled connection — psycopg/PyMySQL do NOT auto-reset session state on release.
289
293
  ``RESET`` / ``SET … DEFAULT`` restores the server default. Only the knobs ``config`` actually set
290
294
  are reset (an all-default config ⇒ nothing to reset). Mirrors the TS ``sessionResetStatements``.
@@ -297,8 +301,8 @@ def session_reset_statements(config: ResolvedConnectionConfig) -> List[str]:
297
301
  out.append("RESET statement_timeout" if dialect == "postgres" else "SET SESSION max_execution_time = DEFAULT")
298
302
  if config.search_path is not None and dialect == "postgres":
299
303
  out.append("RESET search_path")
300
- if config.charset is not None:
301
- out.append("SET NAMES DEFAULT" if dialect == "mysql" else "RESET client_encoding")
304
+ if config.charset is not None and dialect == "postgres":
305
+ out.append("RESET client_encoding")
302
306
  return out
303
307
 
304
308
 
@@ -614,22 +618,20 @@ def with_writer(fn: Callable[[], Any]) -> Any:
614
618
  ``connection_for``, so a :func:`with_writer` there is a no-op on routing (matches v1).
615
619
 
616
620
  The read-only (write-reject) half is entered by a bare read-only marker the guarded write seam
617
- consults, AND — when an ambient routed :class:`ExecutionContext` is pinned in the current scope — by
618
- deriving its read-only view so ``ambient_read_only()`` (which reads the ctx) also sees the
619
- write-reject half. v1's single writerContext is BOTH the writer-routing and the read-only marker;
620
- both halves span the whole ``fn`` because the contextvars are per-scope.
621
+ consults, AND — for every :class:`ExecutionContext` pinned in the current scope (one per owner) — by
622
+ pinning its read-only view under the same owner, so ``ambient_read_only()`` (which reads the pinned
623
+ ctxs) also sees the write-reject half and each owner keeps its own open transaction. v1's single
624
+ writerContext is BOTH the writer-routing and the read-only marker; both halves span the whole ``fn``
625
+ because the contextvars are per-scope.
621
626
  """
622
627
  if in_writer_scope():
623
628
  return fn()
624
629
  token = _writer_scope_var.set(True)
625
630
  ro_token = _readonly_marker_var.set(True)
626
631
  try:
627
- from .exec_context import current_context, run_with_pinned_context
632
+ from .exec_context import pinned_contexts, run_with_pinned_contexts
628
633
 
629
- ambient = current_context()
630
- if ambient is not None and not ambient.read_only():
631
- return run_with_pinned_context(ambient.with_read_only(), fn)
632
- return fn()
634
+ return run_with_pinned_contexts([p.with_read_only() for p in pinned_contexts() if not p.read_only()], fn)
633
635
  finally:
634
636
  _readonly_marker_var.reset(ro_token)
635
637
  _writer_scope_var.reset(token)
@@ -907,8 +909,9 @@ def close_all_pools(closers: Sequence[PoolCloser]) -> None:
907
909
  # (min_pool/max_pool) + keep_alive — reach the real pool at build time (a pre-built pool can no longer
908
910
  # accept them). These factories map a ResolvedConnectionConfig to a psycopg / PyMySQL-backed
909
911
  # `_ConnectionPool` sized to `max_pool` and return the ConnectionPool adapter + closer. The configured
910
- # sizing is thus the SOLE source of the pool cap. Session knobs (query_timeout/search_path/charset) are
911
- # layered separately by `configured_pool` on checkout.
912
+ # sizing is thus the SOLE source of the pool cap. Session knobs (query_timeout/search_path, and the PG
913
+ # client_encoding) are layered separately by `configured_pool` on checkout; the MySQL charset is a
914
+ # PyMySQL CONNECT option (`_mysql_connect_kwargs`), since PyMySQL escapes params on the client in it.
912
915
  #
913
916
  # ── keep_alive honesty (per-driver, mirror the TS mysql2 minPool note) ──
914
917
  # - psycopg (PG): TCP keepalive is a libpq CONNECTION parameter, so `keep_alive=True` maps to
@@ -1011,6 +1014,10 @@ def _mysql_connect_kwargs(config: ResolvedConnectionConfig) -> Dict[str, Any]:
1011
1014
  if config.password is not None:
1012
1015
  kwargs["password"] = config.password
1013
1016
  kwargs["connect_timeout"] = config.connect_timeout_millis / 1000.0 or _PYMYSQL_MAX_CONNECT_TIMEOUT_SECONDS
1017
+ if config.charset is not None:
1018
+ # PyMySQL escapes and encodes params on the client in this charset and announces it to the
1019
+ # server, so client escaping and server parsing share ONE charset (#353).
1020
+ kwargs["charset"] = config.charset
1014
1021
  # NB: PyMySQL exposes no TCP-keepalive connect option ⇒ keep_alive is a documented NO-OP on MySQL
1015
1022
  # (mirror the TS mysqlPoolFactory's "mysql2 has no min idle floor" per-driver deviation note).
1016
1023
  return kwargs