pyoq-sql 1.0.2__py3-none-any.whl
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.
- pyoq/__init__.py +10 -0
- pyoq/__main__.py +5 -0
- pyoq/_native.pyi +5 -0
- pyoq/cli/__init__.py +5 -0
- pyoq/cli/commands.py +270 -0
- pyoq/cli/defaults.py +98 -0
- pyoq/cli/services.py +97 -0
- pyoq/config/__init__.py +31 -0
- pyoq/config/connection.py +161 -0
- pyoq/config/loader.py +289 -0
- pyoq/config/models.py +245 -0
- pyoq/config/values.py +142 -0
- pyoq/descriptors.py +165 -0
- pyoq/diagnostics/__init__.py +68 -0
- pyoq/diagnostics/budget.py +136 -0
- pyoq/diagnostics/events.py +137 -0
- pyoq/diagnostics/fingerprint.py +267 -0
- pyoq/diagnostics/instrumented.py +237 -0
- pyoq/diagnostics/metrics.py +61 -0
- pyoq/diagnostics/observation.py +227 -0
- pyoq/diagnostics/scoped.py +103 -0
- pyoq/django/__init__.py +15 -0
- pyoq/django/apps.py +17 -0
- pyoq/django/execution.py +317 -0
- pyoq/django/generation.py +59 -0
- pyoq/django/management/__init__.py +0 -0
- pyoq/django/management/commands/__init__.py +0 -0
- pyoq/django/management/commands/makemigrations.py +53 -0
- pyoq/django/management/commands/pyoq_codegen.py +75 -0
- pyoq/django/parameters.py +101 -0
- pyoq/django/schema.py +379 -0
- pyoq/django/settings.py +87 -0
- pyoq/django/timeouts.py +105 -0
- pyoq/dsl/__init__.py +64 -0
- pyoq/dsl/aio/__init__.py +31 -0
- pyoq/dsl/aio/context.py +295 -0
- pyoq/dsl/aio/queries.py +335 -0
- pyoq/dsl/aio/writes.py +368 -0
- pyoq/dsl/context.py +326 -0
- pyoq/dsl/entry.py +37 -0
- pyoq/dsl/labels.py +36 -0
- pyoq/dsl/queries.py +339 -0
- pyoq/dsl/result.py +164 -0
- pyoq/dsl/writes.py +360 -0
- pyoq/errors.py +317 -0
- pyoq/fastapi/__init__.py +32 -0
- pyoq/fastapi/dependencies.py +167 -0
- pyoq/fastapi/lifespan.py +119 -0
- pyoq/fetching/__init__.py +55 -0
- pyoq/fetching/collections.py +136 -0
- pyoq/fetching/execution.py +587 -0
- pyoq/fetching/joined.py +79 -0
- pyoq/fetching/nesting.py +183 -0
- pyoq/fetching/plans.py +541 -0
- pyoq/fetching/select_in.py +149 -0
- pyoq/fetching/tables.py +110 -0
- pyoq/generation/__init__.py +54 -0
- pyoq/generation/cleanup.py +44 -0
- pyoq/generation/contracts.py +248 -0
- pyoq/generation/drift.py +169 -0
- pyoq/generation/lock.py +33 -0
- pyoq/generation/manifest.py +114 -0
- pyoq/generation/model.py +1001 -0
- pyoq/generation/pipeline.py +119 -0
- pyoq/generation/rendering/__init__.py +5 -0
- pyoq/generation/rendering/domains.py +51 -0
- pyoq/generation/rendering/enums.py +29 -0
- pyoq/generation/rendering/exports.py +70 -0
- pyoq/generation/rendering/imports.py +63 -0
- pyoq/generation/rendering/package.py +56 -0
- pyoq/generation/rendering/relations.py +133 -0
- pyoq/generation/rendering/routines.py +396 -0
- pyoq/generation/rendering/rows.py +79 -0
- pyoq/generation/rendering/source.py +121 -0
- pyoq/generation/rendering/tables.py +300 -0
- pyoq/generation/rendering/writes.py +514 -0
- pyoq/generation/validation.py +27 -0
- pyoq/generation/writer.py +184 -0
- pyoq/hydration/__init__.py +24 -0
- pyoq/hydration/engine.py +155 -0
- pyoq/hydration/identity.py +194 -0
- pyoq/hydration/plan.py +116 -0
- pyoq/migrations/__init__.py +9 -0
- pyoq/migrations/alembic.py +106 -0
- pyoq/migrations/hooks.py +75 -0
- pyoq/naming.py +261 -0
- pyoq/policies/__init__.py +47 -0
- pyoq/policies/bypass.py +122 -0
- pyoq/policies/governed.py +430 -0
- pyoq/policies/model.py +242 -0
- pyoq/policies/rewriting.py +263 -0
- pyoq/py.typed +1 -0
- pyoq/query/__init__.py +312 -0
- pyoq/query/aggregates.py +172 -0
- pyoq/query/arrays.py +65 -0
- pyoq/query/binding.py +52 -0
- pyoq/query/capabilities.py +317 -0
- pyoq/query/casts.py +73 -0
- pyoq/query/choices.py +185 -0
- pyoq/query/decoding.py +360 -0
- pyoq/query/documents.py +56 -0
- pyoq/query/execution/__init__.py +63 -0
- pyoq/query/execution/aio/__init__.py +31 -0
- pyoq/query/execution/aio/operations.py +228 -0
- pyoq/query/execution/aio/pooling.py +233 -0
- pyoq/query/execution/aio/streaming.py +161 -0
- pyoq/query/execution/aio/transactions.py +105 -0
- pyoq/query/execution/batch.py +96 -0
- pyoq/query/execution/binding_style.py +30 -0
- pyoq/query/execution/compilation.py +48 -0
- pyoq/query/execution/context.py +61 -0
- pyoq/query/execution/control.py +50 -0
- pyoq/query/execution/operations.py +224 -0
- pyoq/query/execution/planning.py +107 -0
- pyoq/query/execution/pooling.py +279 -0
- pyoq/query/execution/results.py +36 -0
- pyoq/query/execution/streaming.py +178 -0
- pyoq/query/execution/transactions.py +95 -0
- pyoq/query/expressions.py +1200 -0
- pyoq/query/fields.py +60 -0
- pyoq/query/mysql/__init__.py +59 -0
- pyoq/query/mysql/aio/__init__.py +38 -0
- pyoq/query/mysql/aio/commands.py +389 -0
- pyoq/query/mysql/aio/driver.py +196 -0
- pyoq/query/mysql/aio/executor.py +123 -0
- pyoq/query/mysql/aio/factory.py +26 -0
- pyoq/query/mysql/aio/operations.py +38 -0
- pyoq/query/mysql/aio/pool.py +53 -0
- pyoq/query/mysql/aio/transactions.py +313 -0
- pyoq/query/mysql/commands.py +354 -0
- pyoq/query/mysql/compiler.py +134 -0
- pyoq/query/mysql/context.py +20 -0
- pyoq/query/mysql/executor.py +126 -0
- pyoq/query/mysql/expressions.py +244 -0
- pyoq/query/mysql/factory.py +46 -0
- pyoq/query/mysql/health.py +66 -0
- pyoq/query/mysql/identifiers.py +9 -0
- pyoq/query/mysql/model.py +79 -0
- pyoq/query/mysql/operations.py +43 -0
- pyoq/query/mysql/parameters.py +69 -0
- pyoq/query/mysql/planning.py +20 -0
- pyoq/query/mysql/pool.py +67 -0
- pyoq/query/mysql/transactions.py +331 -0
- pyoq/query/mysql/writes.py +73 -0
- pyoq/query/nodes.py +750 -0
- pyoq/query/postgres/__init__.py +48 -0
- pyoq/query/postgres/aio/__init__.py +25 -0
- pyoq/query/postgres/aio/bulk.py +56 -0
- pyoq/query/postgres/aio/commands.py +264 -0
- pyoq/query/postgres/aio/executor.py +152 -0
- pyoq/query/postgres/aio/factory.py +26 -0
- pyoq/query/postgres/aio/operations.py +26 -0
- pyoq/query/postgres/aio/pool.py +40 -0
- pyoq/query/postgres/aio/transactions.py +295 -0
- pyoq/query/postgres/bulk.py +62 -0
- pyoq/query/postgres/commands.py +238 -0
- pyoq/query/postgres/compiler.py +114 -0
- pyoq/query/postgres/context.py +20 -0
- pyoq/query/postgres/executor.py +147 -0
- pyoq/query/postgres/expressions.py +311 -0
- pyoq/query/postgres/factory.py +24 -0
- pyoq/query/postgres/health.py +24 -0
- pyoq/query/postgres/identifiers.py +9 -0
- pyoq/query/postgres/model.py +81 -0
- pyoq/query/postgres/operations.py +25 -0
- pyoq/query/postgres/parameters.py +71 -0
- pyoq/query/postgres/planning.py +20 -0
- pyoq/query/postgres/pool.py +52 -0
- pyoq/query/postgres/transactions.py +295 -0
- pyoq/query/postgres/writes.py +37 -0
- pyoq/query/projections.py +105 -0
- pyoq/query/raw.py +90 -0
- pyoq/query/recursion.py +265 -0
- pyoq/query/rendering/__init__.py +1 -0
- pyoq/query/rendering/expressions.py +913 -0
- pyoq/query/rendering/identifiers.py +40 -0
- pyoq/query/rendering/projections.py +63 -0
- pyoq/query/rendering/queries.py +334 -0
- pyoq/query/rendering/sources.py +66 -0
- pyoq/query/rendering/writes.py +176 -0
- pyoq/query/results.py +459 -0
- pyoq/query/routines.py +196 -0
- pyoq/query/rows.py +156 -0
- pyoq/query/select.py +793 -0
- pyoq/query/select_nodes.py +277 -0
- pyoq/query/sources.py +236 -0
- pyoq/query/sqlite/__init__.py +43 -0
- pyoq/query/sqlite/commands.py +201 -0
- pyoq/query/sqlite/compiler.py +139 -0
- pyoq/query/sqlite/context.py +20 -0
- pyoq/query/sqlite/executor.py +119 -0
- pyoq/query/sqlite/expressions.py +224 -0
- pyoq/query/sqlite/factory.py +32 -0
- pyoq/query/sqlite/health.py +28 -0
- pyoq/query/sqlite/identifiers.py +9 -0
- pyoq/query/sqlite/model.py +73 -0
- pyoq/query/sqlite/operations.py +36 -0
- pyoq/query/sqlite/parameters.py +50 -0
- pyoq/query/sqlite/planning.py +20 -0
- pyoq/query/sqlite/pool.py +50 -0
- pyoq/query/sqlite/streaming.py +13 -0
- pyoq/query/sqlite/transactions.py +274 -0
- pyoq/query/sqlite/writes.py +35 -0
- pyoq/query/statements.py +27 -0
- pyoq/query/values.py +23 -0
- pyoq/query/vendor.py +162 -0
- pyoq/query/windows.py +424 -0
- pyoq/query/write_nodes.py +174 -0
- pyoq/query/writes.py +628 -0
- pyoq/relations/__init__.py +66 -0
- pyoq/relations/batching.py +219 -0
- pyoq/relations/derivation.py +111 -0
- pyoq/relations/fetching.py +355 -0
- pyoq/relations/graph.py +245 -0
- pyoq/relations/loading.py +74 -0
- pyoq/relations/model.py +75 -0
- pyoq/relations/planning.py +206 -0
- pyoq/runtime/__init__.py +9 -0
- pyoq/runtime/kernels.py +25 -0
- pyoq/runtime/python.py +43 -0
- pyoq/runtime/selection.py +73 -0
- pyoq/sanic/__init__.py +32 -0
- pyoq/sanic/scope.py +197 -0
- pyoq/sanic/workers.py +129 -0
- pyoq/schema/__init__.py +108 -0
- pyoq/schema/codec.py +711 -0
- pyoq/schema/models.py +604 -0
- pyoq/schema/mysql/__init__.py +16 -0
- pyoq/schema/mysql/connection.py +73 -0
- pyoq/schema/mysql/dsn.py +72 -0
- pyoq/schema/mysql/records.py +354 -0
- pyoq/schema/mysql/reflection.py +309 -0
- pyoq/schema/mysql/source.py +30 -0
- pyoq/schema/mysql/sql.py +128 -0
- pyoq/schema/mysql/types.py +105 -0
- pyoq/schema/postgres/__init__.py +13 -0
- pyoq/schema/postgres/connection.py +63 -0
- pyoq/schema/postgres/records.py +384 -0
- pyoq/schema/postgres/reflection.py +466 -0
- pyoq/schema/postgres/source.py +30 -0
- pyoq/schema/postgres/sql.py +246 -0
- pyoq/schema/postgres/types.py +98 -0
- pyoq/schema/registry.py +45 -0
- pyoq/schema/source.py +15 -0
- pyoq/schema/sqlite/__init__.py +6 -0
- pyoq/schema/sqlite/connection.py +54 -0
- pyoq/schema/sqlite/records.py +167 -0
- pyoq/schema/sqlite/reflection.py +393 -0
- pyoq/schema/sqlite/source.py +30 -0
- pyoq/schema/sqlite/sql.py +254 -0
- pyoq/schema/sqlite/types.py +74 -0
- pyoq/serving/__init__.py +23 -0
- pyoq/serving/databases.py +107 -0
- pyoq/serving/opening.py +331 -0
- pyoq/snapshots/__init__.py +20 -0
- pyoq/snapshots/drift.py +312 -0
- pyoq/snapshots/files.py +96 -0
- pyoq/snapshots/routing.py +40 -0
- pyoq/snapshots/source.py +33 -0
- pyoq/tracing/__init__.py +5 -0
- pyoq/tracing/spans.py +89 -0
- pyoq/unset.py +14 -0
- pyoq_sql-1.0.2.dist-info/METADATA +3050 -0
- pyoq_sql-1.0.2.dist-info/RECORD +267 -0
- pyoq_sql-1.0.2.dist-info/WHEEL +4 -0
- pyoq_sql-1.0.2.dist-info/entry_points.txt +3 -0
- pyoq_sql-1.0.2.dist-info/licenses/LICENSE +373 -0
pyoq/django/settings.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""PyOQ's own settings, read from the Django project that owns them."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Final, cast
|
|
8
|
+
|
|
9
|
+
from django.conf import settings
|
|
10
|
+
|
|
11
|
+
from pyoq.config import Configuration, DatabaseDialect, DatabaseProfile
|
|
12
|
+
from pyoq.errors import OperationUnavailableError
|
|
13
|
+
|
|
14
|
+
SETTING: Final = "PYOQ"
|
|
15
|
+
|
|
16
|
+
_PROFILE: Final = "django"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def pyoq_settings() -> dict[str, object]:
|
|
20
|
+
"""The project's PyOQ settings, which it has to have written itself.
|
|
21
|
+
|
|
22
|
+
There is no default output location worth guessing. A package written to
|
|
23
|
+
the wrong place is worse than being told where to put it.
|
|
24
|
+
"""
|
|
25
|
+
configured: object = getattr(settings, SETTING, None)
|
|
26
|
+
if not isinstance(configured, Mapping):
|
|
27
|
+
message = (
|
|
28
|
+
f"Django settings define no {SETTING} mapping; add "
|
|
29
|
+
f'{SETTING} = {{"codegen_directory": ..., '
|
|
30
|
+
f'"codegen_package": ...}}'
|
|
31
|
+
)
|
|
32
|
+
raise OperationUnavailableError(message)
|
|
33
|
+
# Django settings carry no types of their own, so this is the one place
|
|
34
|
+
# that says what the mapping holds. Every read below re-checks the value.
|
|
35
|
+
entries = cast("Mapping[str, object]", configured)
|
|
36
|
+
return dict(entries)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def django_configuration(dialect: DatabaseDialect, /) -> Configuration:
|
|
40
|
+
"""Where generated output goes, with no database named.
|
|
41
|
+
|
|
42
|
+
The schema comes from migration files, so a profile here carries the
|
|
43
|
+
dialect and nothing that would open a connection.
|
|
44
|
+
"""
|
|
45
|
+
configured = pyoq_settings()
|
|
46
|
+
return Configuration(
|
|
47
|
+
project_root=_root(configured),
|
|
48
|
+
codegen_directory=Path(_required(configured, "codegen_directory")),
|
|
49
|
+
codegen_package=_required(configured, "codegen_package"),
|
|
50
|
+
selected_profile=_PROFILE,
|
|
51
|
+
profiles=(DatabaseProfile(name=_PROFILE, dialect=dialect),),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def codegen_after_makemigrations() -> bool:
|
|
56
|
+
"""Overriding a command a project already runs has to be asked for."""
|
|
57
|
+
return bool(pyoq_settings().get("codegen_after_makemigrations", False))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _root(configured: dict[str, object]) -> Path:
|
|
61
|
+
named = configured.get("project_root")
|
|
62
|
+
if named is not None:
|
|
63
|
+
return Path(str(named))
|
|
64
|
+
base = getattr(settings, "BASE_DIR", None)
|
|
65
|
+
if base is None:
|
|
66
|
+
message = (
|
|
67
|
+
f"neither {SETTING}['project_root'] nor BASE_DIR is set, so there "
|
|
68
|
+
f"is nowhere to write generated code relative to"
|
|
69
|
+
)
|
|
70
|
+
raise OperationUnavailableError(message)
|
|
71
|
+
return Path(str(base))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _required(configured: dict[str, object], key: str) -> str:
|
|
75
|
+
value = configured.get(key)
|
|
76
|
+
if not isinstance(value, str) or not value:
|
|
77
|
+
message = f"{SETTING}[{key!r}] must be a non-empty string"
|
|
78
|
+
raise OperationUnavailableError(message)
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = (
|
|
83
|
+
"SETTING",
|
|
84
|
+
"codegen_after_makemigrations",
|
|
85
|
+
"django_configuration",
|
|
86
|
+
"pyoq_settings",
|
|
87
|
+
)
|
pyoq/django/timeouts.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Bounding a statement on a connection PyOQ does not own.
|
|
2
|
+
|
|
3
|
+
A timeout is session state, and the session belongs to Django, so it has to be
|
|
4
|
+
put back exactly as it was found. Inside a transaction the database does that
|
|
5
|
+
itself; outside one, the previous value is read first and restored after.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Generator
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
from django.db import DatabaseError
|
|
15
|
+
from django.db.backends.base.base import BaseDatabaseWrapper
|
|
16
|
+
|
|
17
|
+
MILLISECONDS = 1000
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, slots=True)
|
|
21
|
+
class TimeoutSetting:
|
|
22
|
+
"""The session setting one backend uses to bound a statement."""
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
reads: bool = True
|
|
26
|
+
writes: bool = True
|
|
27
|
+
|
|
28
|
+
def read(self, connection: BaseDatabaseWrapper, /) -> str:
|
|
29
|
+
with connection.cursor() as cursor:
|
|
30
|
+
cursor.execute(f"SHOW {self.name}")
|
|
31
|
+
row = cursor.fetchone()
|
|
32
|
+
return "" if row is None else str(row[0])
|
|
33
|
+
|
|
34
|
+
def assign(
|
|
35
|
+
self,
|
|
36
|
+
connection: BaseDatabaseWrapper,
|
|
37
|
+
value: str,
|
|
38
|
+
/,
|
|
39
|
+
*,
|
|
40
|
+
local: bool,
|
|
41
|
+
) -> None:
|
|
42
|
+
scope = "LOCAL " if local else ""
|
|
43
|
+
with connection.cursor() as cursor:
|
|
44
|
+
cursor.execute(f"SET {scope}{self.name} = {value}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
POSTGRES_TIMEOUT = TimeoutSetting("statement_timeout")
|
|
48
|
+
MYSQL_TIMEOUT = TimeoutSetting("@@max_execution_time", writes=False)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@contextmanager
|
|
52
|
+
def applied(
|
|
53
|
+
setting: TimeoutSetting,
|
|
54
|
+
connection: BaseDatabaseWrapper,
|
|
55
|
+
seconds: float,
|
|
56
|
+
/,
|
|
57
|
+
) -> Generator[None]:
|
|
58
|
+
"""Bound statements for the length of this block, and leave no trace.
|
|
59
|
+
|
|
60
|
+
Inside a transaction the setting is local, so the database restores it when
|
|
61
|
+
the transaction ends. Outside one there is nothing to restore it, so the
|
|
62
|
+
previous value is read first and put back afterwards.
|
|
63
|
+
"""
|
|
64
|
+
milliseconds = max(int(seconds * MILLISECONDS), 1)
|
|
65
|
+
if connection.in_atomic_block:
|
|
66
|
+
setting.assign(connection, str(milliseconds), local=True)
|
|
67
|
+
yield
|
|
68
|
+
return
|
|
69
|
+
previous = setting.read(connection)
|
|
70
|
+
setting.assign(connection, str(milliseconds), local=False)
|
|
71
|
+
try:
|
|
72
|
+
yield
|
|
73
|
+
finally:
|
|
74
|
+
_restore(setting, connection, previous)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _restore(
|
|
78
|
+
setting: TimeoutSetting,
|
|
79
|
+
connection: BaseDatabaseWrapper,
|
|
80
|
+
previous: str,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Put the setting back without ever replacing the reason we are here.
|
|
83
|
+
|
|
84
|
+
A statement that failed can leave a connection unable to accept the next
|
|
85
|
+
one, and a restore that raised there would hide the failure that caused it.
|
|
86
|
+
Django owns the connection and will deal with it.
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
setting.assign(connection, _quoted(previous), local=False)
|
|
90
|
+
except DatabaseError:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _quoted(previous: str) -> str:
|
|
95
|
+
"""A read setting comes back with its unit, which has to go back quoted."""
|
|
96
|
+
return previous if previous.isdigit() else f"'{previous}'"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
__all__ = (
|
|
100
|
+
"MILLISECONDS",
|
|
101
|
+
"MYSQL_TIMEOUT",
|
|
102
|
+
"POSTGRES_TIMEOUT",
|
|
103
|
+
"TimeoutSetting",
|
|
104
|
+
"applied",
|
|
105
|
+
)
|
pyoq/dsl/__init__.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Building a statement and running it in one chain.
|
|
2
|
+
|
|
3
|
+
`using(database)` gives a context to build against. The builders in
|
|
4
|
+
`pyoq.query` are unchanged and still cannot reach a database on their own.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pyoq.dsl.aio import (
|
|
8
|
+
AsyncBoundConflictUpdate,
|
|
9
|
+
AsyncBoundDelete,
|
|
10
|
+
AsyncBoundInsert,
|
|
11
|
+
AsyncBoundJoinStep,
|
|
12
|
+
AsyncBoundMutation,
|
|
13
|
+
AsyncBoundQuery,
|
|
14
|
+
AsyncBoundReturning,
|
|
15
|
+
AsyncBoundSelect,
|
|
16
|
+
AsyncBoundSemiJoinStep,
|
|
17
|
+
AsyncBoundUpdate,
|
|
18
|
+
AsyncDslContext,
|
|
19
|
+
)
|
|
20
|
+
from pyoq.dsl.context import DslContext
|
|
21
|
+
from pyoq.dsl.entry import using
|
|
22
|
+
from pyoq.dsl.queries import (
|
|
23
|
+
BoundJoinStep,
|
|
24
|
+
BoundQuery,
|
|
25
|
+
BoundSelect,
|
|
26
|
+
BoundSemiJoinStep,
|
|
27
|
+
)
|
|
28
|
+
from pyoq.dsl.result import Result, Row
|
|
29
|
+
from pyoq.dsl.writes import (
|
|
30
|
+
BoundConflictUpdate,
|
|
31
|
+
BoundDelete,
|
|
32
|
+
BoundInsert,
|
|
33
|
+
BoundMutation,
|
|
34
|
+
BoundReturning,
|
|
35
|
+
BoundUpdate,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = (
|
|
39
|
+
"AsyncBoundConflictUpdate",
|
|
40
|
+
"AsyncBoundDelete",
|
|
41
|
+
"AsyncBoundInsert",
|
|
42
|
+
"AsyncBoundJoinStep",
|
|
43
|
+
"AsyncBoundMutation",
|
|
44
|
+
"AsyncBoundQuery",
|
|
45
|
+
"AsyncBoundReturning",
|
|
46
|
+
"AsyncBoundSelect",
|
|
47
|
+
"AsyncBoundSemiJoinStep",
|
|
48
|
+
"AsyncBoundUpdate",
|
|
49
|
+
"AsyncDslContext",
|
|
50
|
+
"BoundConflictUpdate",
|
|
51
|
+
"BoundDelete",
|
|
52
|
+
"BoundInsert",
|
|
53
|
+
"BoundJoinStep",
|
|
54
|
+
"BoundMutation",
|
|
55
|
+
"BoundQuery",
|
|
56
|
+
"BoundReturning",
|
|
57
|
+
"BoundSelect",
|
|
58
|
+
"BoundSemiJoinStep",
|
|
59
|
+
"BoundUpdate",
|
|
60
|
+
"DslContext",
|
|
61
|
+
"Result",
|
|
62
|
+
"Row",
|
|
63
|
+
"using",
|
|
64
|
+
)
|
pyoq/dsl/aio/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""The asynchronous counterpart of the bound builders."""
|
|
2
|
+
|
|
3
|
+
from pyoq.dsl.aio.context import AsyncDslContext
|
|
4
|
+
from pyoq.dsl.aio.queries import (
|
|
5
|
+
AsyncBoundJoinStep,
|
|
6
|
+
AsyncBoundQuery,
|
|
7
|
+
AsyncBoundSelect,
|
|
8
|
+
AsyncBoundSemiJoinStep,
|
|
9
|
+
)
|
|
10
|
+
from pyoq.dsl.aio.writes import (
|
|
11
|
+
AsyncBoundConflictUpdate,
|
|
12
|
+
AsyncBoundDelete,
|
|
13
|
+
AsyncBoundInsert,
|
|
14
|
+
AsyncBoundMutation,
|
|
15
|
+
AsyncBoundReturning,
|
|
16
|
+
AsyncBoundUpdate,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
"AsyncBoundConflictUpdate",
|
|
21
|
+
"AsyncBoundDelete",
|
|
22
|
+
"AsyncBoundInsert",
|
|
23
|
+
"AsyncBoundJoinStep",
|
|
24
|
+
"AsyncBoundMutation",
|
|
25
|
+
"AsyncBoundQuery",
|
|
26
|
+
"AsyncBoundReturning",
|
|
27
|
+
"AsyncBoundSelect",
|
|
28
|
+
"AsyncBoundSemiJoinStep",
|
|
29
|
+
"AsyncBoundUpdate",
|
|
30
|
+
"AsyncDslContext",
|
|
31
|
+
)
|
pyoq/dsl/aio/context.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""The asynchronous counterpart of a context to build against."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, TypeVar, cast, overload
|
|
6
|
+
|
|
7
|
+
from pyoq.dsl.aio.queries import AsyncBoundSelect
|
|
8
|
+
from pyoq.dsl.aio.writes import AsyncBoundDelete, AsyncBoundInsert, AsyncBoundUpdate
|
|
9
|
+
from pyoq.dsl.labels import labels_of
|
|
10
|
+
from pyoq.dsl.result import Result
|
|
11
|
+
from pyoq.query.select import SelectQuery, select, select_from
|
|
12
|
+
from pyoq.query.select_nodes import SelectNode
|
|
13
|
+
from pyoq.query.writes import (
|
|
14
|
+
InsertStatement,
|
|
15
|
+
ReturningStatement,
|
|
16
|
+
delete_from,
|
|
17
|
+
insert_into,
|
|
18
|
+
update,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
|
|
24
|
+
from pyoq.query.execution.aio import AsyncQueryOperations
|
|
25
|
+
from pyoq.query.expressions import Expression
|
|
26
|
+
from pyoq.query.projections import Projection
|
|
27
|
+
from pyoq.query.routines import Call
|
|
28
|
+
from pyoq.query.select import Query
|
|
29
|
+
from pyoq.query.sources import ColumnedTable, TableLike
|
|
30
|
+
|
|
31
|
+
ResultRow = TypeVar("ResultRow", bound=tuple[object, ...])
|
|
32
|
+
Value1 = TypeVar("Value1")
|
|
33
|
+
Value2 = TypeVar("Value2")
|
|
34
|
+
Value3 = TypeVar("Value3")
|
|
35
|
+
Value4 = TypeVar("Value4")
|
|
36
|
+
Value5 = TypeVar("Value5")
|
|
37
|
+
Value6 = TypeVar("Value6")
|
|
38
|
+
Value7 = TypeVar("Value7")
|
|
39
|
+
Value8 = TypeVar("Value8")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AsyncDslContext:
|
|
43
|
+
"""A database to build against, whose fetches are awaited."""
|
|
44
|
+
|
|
45
|
+
__slots__ = ("_operations",)
|
|
46
|
+
_operations: AsyncQueryOperations
|
|
47
|
+
|
|
48
|
+
def __init__(self, operations: AsyncQueryOperations) -> None:
|
|
49
|
+
object.__setattr__(self, "_operations", operations)
|
|
50
|
+
|
|
51
|
+
def __setattr__(self, name: str, value: object) -> None:
|
|
52
|
+
message = f"{type(self).__name__} is immutable"
|
|
53
|
+
raise AttributeError(message, name, value)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def operations(self) -> AsyncQueryOperations:
|
|
57
|
+
return self._operations
|
|
58
|
+
|
|
59
|
+
@overload
|
|
60
|
+
def select(
|
|
61
|
+
self,
|
|
62
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
63
|
+
/,
|
|
64
|
+
) -> AsyncBoundSelect[tuple[Value1]]: ...
|
|
65
|
+
|
|
66
|
+
@overload
|
|
67
|
+
def select(
|
|
68
|
+
self,
|
|
69
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
70
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
71
|
+
/,
|
|
72
|
+
) -> AsyncBoundSelect[tuple[Value1, Value2]]: ...
|
|
73
|
+
|
|
74
|
+
@overload
|
|
75
|
+
def select(
|
|
76
|
+
self,
|
|
77
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
78
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
79
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
80
|
+
/,
|
|
81
|
+
) -> AsyncBoundSelect[tuple[Value1, Value2, Value3]]: ...
|
|
82
|
+
|
|
83
|
+
@overload
|
|
84
|
+
def select(
|
|
85
|
+
self,
|
|
86
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
87
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
88
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
89
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
90
|
+
/,
|
|
91
|
+
) -> AsyncBoundSelect[tuple[Value1, Value2, Value3, Value4]]: ...
|
|
92
|
+
|
|
93
|
+
@overload
|
|
94
|
+
def select(
|
|
95
|
+
self,
|
|
96
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
97
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
98
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
99
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
100
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
101
|
+
/,
|
|
102
|
+
) -> AsyncBoundSelect[tuple[Value1, Value2, Value3, Value4, Value5]]: ...
|
|
103
|
+
|
|
104
|
+
@overload
|
|
105
|
+
def select(
|
|
106
|
+
self,
|
|
107
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
108
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
109
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
110
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
111
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
112
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
113
|
+
/,
|
|
114
|
+
) -> AsyncBoundSelect[tuple[Value1, Value2, Value3, Value4, Value5, Value6]]: ...
|
|
115
|
+
|
|
116
|
+
@overload
|
|
117
|
+
def select(
|
|
118
|
+
self,
|
|
119
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
120
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
121
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
122
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
123
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
124
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
125
|
+
value7: Expression[Value7] | Projection[Value7],
|
|
126
|
+
/,
|
|
127
|
+
) -> AsyncBoundSelect[
|
|
128
|
+
tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7]
|
|
129
|
+
]: ...
|
|
130
|
+
|
|
131
|
+
@overload
|
|
132
|
+
def select(
|
|
133
|
+
self,
|
|
134
|
+
value1: Expression[Value1] | Projection[Value1],
|
|
135
|
+
value2: Expression[Value2] | Projection[Value2],
|
|
136
|
+
value3: Expression[Value3] | Projection[Value3],
|
|
137
|
+
value4: Expression[Value4] | Projection[Value4],
|
|
138
|
+
value5: Expression[Value5] | Projection[Value5],
|
|
139
|
+
value6: Expression[Value6] | Projection[Value6],
|
|
140
|
+
value7: Expression[Value7] | Projection[Value7],
|
|
141
|
+
value8: Expression[Value8] | Projection[Value8],
|
|
142
|
+
/,
|
|
143
|
+
) -> AsyncBoundSelect[
|
|
144
|
+
tuple[Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8]
|
|
145
|
+
]: ...
|
|
146
|
+
|
|
147
|
+
def select( # type: ignore[misc]
|
|
148
|
+
self,
|
|
149
|
+
*values: object,
|
|
150
|
+
) -> AsyncBoundSelect[tuple[object, ...]]:
|
|
151
|
+
build = cast("Callable[..., SelectQuery[tuple[object, ...]]]", select)
|
|
152
|
+
return AsyncBoundSelect(self._operations, build(*values))
|
|
153
|
+
|
|
154
|
+
def select_from(
|
|
155
|
+
self, table: ColumnedTable, /
|
|
156
|
+
) -> AsyncBoundSelect[tuple[object, ...]]:
|
|
157
|
+
"""Every column the table declares, without naming them one by one.
|
|
158
|
+
|
|
159
|
+
The columns are written into the SQL rather than left as a star, so
|
|
160
|
+
adding one later cannot silently change what a caller reads back.
|
|
161
|
+
Values arrive in declared order, which is the order the generated row
|
|
162
|
+
type takes its arguments, so `.into(TableRow)` builds it.
|
|
163
|
+
"""
|
|
164
|
+
return AsyncBoundSelect(self._operations, select_from(table))
|
|
165
|
+
|
|
166
|
+
@overload
|
|
167
|
+
@overload
|
|
168
|
+
def insert_into(
|
|
169
|
+
self,
|
|
170
|
+
table: TableLike,
|
|
171
|
+
/,
|
|
172
|
+
column1: Expression[Value1],
|
|
173
|
+
) -> AsyncBoundInsert[Value1]: ...
|
|
174
|
+
|
|
175
|
+
@overload
|
|
176
|
+
def insert_into(
|
|
177
|
+
self,
|
|
178
|
+
table: TableLike,
|
|
179
|
+
/,
|
|
180
|
+
column1: Expression[Value1],
|
|
181
|
+
column2: Expression[Value2],
|
|
182
|
+
) -> AsyncBoundInsert[Value1, Value2]: ...
|
|
183
|
+
|
|
184
|
+
@overload
|
|
185
|
+
def insert_into(
|
|
186
|
+
self,
|
|
187
|
+
table: TableLike,
|
|
188
|
+
/,
|
|
189
|
+
column1: Expression[Value1],
|
|
190
|
+
column2: Expression[Value2],
|
|
191
|
+
column3: Expression[Value3],
|
|
192
|
+
) -> AsyncBoundInsert[Value1, Value2, Value3]: ...
|
|
193
|
+
|
|
194
|
+
@overload
|
|
195
|
+
def insert_into(
|
|
196
|
+
self,
|
|
197
|
+
table: TableLike,
|
|
198
|
+
/,
|
|
199
|
+
column1: Expression[Value1],
|
|
200
|
+
column2: Expression[Value2],
|
|
201
|
+
column3: Expression[Value3],
|
|
202
|
+
column4: Expression[Value4],
|
|
203
|
+
) -> AsyncBoundInsert[Value1, Value2, Value3, Value4]: ...
|
|
204
|
+
|
|
205
|
+
@overload
|
|
206
|
+
def insert_into(
|
|
207
|
+
self,
|
|
208
|
+
table: TableLike,
|
|
209
|
+
/,
|
|
210
|
+
column1: Expression[Value1],
|
|
211
|
+
column2: Expression[Value2],
|
|
212
|
+
column3: Expression[Value3],
|
|
213
|
+
column4: Expression[Value4],
|
|
214
|
+
column5: Expression[Value5],
|
|
215
|
+
) -> AsyncBoundInsert[Value1, Value2, Value3, Value4, Value5]: ...
|
|
216
|
+
|
|
217
|
+
@overload
|
|
218
|
+
def insert_into(
|
|
219
|
+
self,
|
|
220
|
+
table: TableLike,
|
|
221
|
+
/,
|
|
222
|
+
column1: Expression[Value1],
|
|
223
|
+
column2: Expression[Value2],
|
|
224
|
+
column3: Expression[Value3],
|
|
225
|
+
column4: Expression[Value4],
|
|
226
|
+
column5: Expression[Value5],
|
|
227
|
+
column6: Expression[Value6],
|
|
228
|
+
) -> AsyncBoundInsert[Value1, Value2, Value3, Value4, Value5, Value6]: ...
|
|
229
|
+
|
|
230
|
+
@overload
|
|
231
|
+
def insert_into(
|
|
232
|
+
self,
|
|
233
|
+
table: TableLike,
|
|
234
|
+
/,
|
|
235
|
+
column1: Expression[Value1],
|
|
236
|
+
column2: Expression[Value2],
|
|
237
|
+
column3: Expression[Value3],
|
|
238
|
+
column4: Expression[Value4],
|
|
239
|
+
column5: Expression[Value5],
|
|
240
|
+
column6: Expression[Value6],
|
|
241
|
+
column7: Expression[Value7],
|
|
242
|
+
) -> AsyncBoundInsert[Value1, Value2, Value3, Value4, Value5, Value6, Value7]: ...
|
|
243
|
+
|
|
244
|
+
@overload
|
|
245
|
+
def insert_into(
|
|
246
|
+
self,
|
|
247
|
+
table: TableLike,
|
|
248
|
+
/,
|
|
249
|
+
column1: Expression[Value1],
|
|
250
|
+
column2: Expression[Value2],
|
|
251
|
+
column3: Expression[Value3],
|
|
252
|
+
column4: Expression[Value4],
|
|
253
|
+
column5: Expression[Value5],
|
|
254
|
+
column6: Expression[Value6],
|
|
255
|
+
column7: Expression[Value7],
|
|
256
|
+
column8: Expression[Value8],
|
|
257
|
+
) -> AsyncBoundInsert[
|
|
258
|
+
Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8
|
|
259
|
+
]: ...
|
|
260
|
+
|
|
261
|
+
def insert_into( # type: ignore[misc]
|
|
262
|
+
self,
|
|
263
|
+
table: TableLike,
|
|
264
|
+
/,
|
|
265
|
+
*columns: object,
|
|
266
|
+
) -> AsyncBoundInsert[*tuple[object, ...]]:
|
|
267
|
+
build = cast("Callable[..., InsertStatement[*tuple[object, ...]]]", insert_into)
|
|
268
|
+
return AsyncBoundInsert(self._operations, build(table, *columns))
|
|
269
|
+
|
|
270
|
+
def update(self, table: TableLike, /) -> AsyncBoundUpdate:
|
|
271
|
+
return AsyncBoundUpdate(self._operations, update(table))
|
|
272
|
+
|
|
273
|
+
def delete_from(self, table: TableLike, /) -> AsyncBoundDelete:
|
|
274
|
+
return AsyncBoundDelete(self._operations, delete_from(table))
|
|
275
|
+
|
|
276
|
+
async def fetch(
|
|
277
|
+
self,
|
|
278
|
+
query: Query[ResultRow] | ReturningStatement[ResultRow] | Call[ResultRow],
|
|
279
|
+
/,
|
|
280
|
+
) -> Result[ResultRow]:
|
|
281
|
+
"""Run something built the ordinary way, and read it the same way."""
|
|
282
|
+
rows = tuple(await self._operations.many(query))
|
|
283
|
+
return Result(rows, _labels(query))
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _labels(
|
|
287
|
+
query: Query[ResultRow] | ReturningStatement[ResultRow] | Call[ResultRow],
|
|
288
|
+
) -> tuple[str | None, ...]:
|
|
289
|
+
if isinstance(query, ReturningStatement):
|
|
290
|
+
return labels_of(query.node.returning)
|
|
291
|
+
node = query.node
|
|
292
|
+
return labels_of(node.projections) if isinstance(node, SelectNode) else ()
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
__all__ = ("AsyncDslContext",)
|