dclassql 0.2.0__tar.gz → 0.3.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.
- {dclassql-0.2.0 → dclassql-0.3.0}/PKG-INFO +2 -2
- {dclassql-0.2.0 → dclassql-0.3.0}/README.md +1 -1
- {dclassql-0.2.0 → dclassql-0.3.0}/pyproject.toml +3 -3
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/__init__.py +2 -2
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/cli.py +0 -46
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/codegen.py +9 -10
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/base.py +5 -4
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/protocols.py +5 -4
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/where_compiler.py +33 -3
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/sqlite_adapters.py +0 -3
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/model_section.jinja +5 -5
- dclassql-0.2.0/src/dclassql/client.py +0 -863
- dclassql-0.2.0/src/dclassql/generated_models/__init__.py +0 -0
- dclassql-0.2.0/src/dclassql/generated_models/test_models.py +0 -78
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/.gitignore +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/asdict.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/asdict.pyi +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/db_pool.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/model_inspector.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/push/__init__.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/push/base.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/push/sqlite.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/__init__.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/lazy.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/metadata.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/sqlite.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/datasource.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/sql_recorder.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/table_spec.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/__init__.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/asdict_stub.pyi.jinja +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/client_module.py.jinja +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/client_class.jinja +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/exports.jinja +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/imports.jinja +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/macros.jinja +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/scalar_filters.jinja +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/typing.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/unwarp.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/utils/__init__.py +0 -0
- {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/utils/ensure.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dclassql
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A type-safe ORM generator for Python, creating fully type-hinted database clients from plain dataclass definitions.
|
|
5
5
|
Keywords: orm,codegen,sqlite,dataclass,typed
|
|
6
6
|
Author: myuanz
|
|
@@ -86,7 +86,7 @@ uv add dclassql
|
|
|
86
86
|
|
|
87
87
|
## 当前状态
|
|
88
88
|
|
|
89
|
-
DataclassQL 仍在早期开发阶段,
|
|
89
|
+
DataclassQL 仍在早期开发阶段, 但不是无根浮萍, 我已经在另外两个项目里大量使用, 目前基于其他项目的反馈来更新.
|
|
90
90
|
|
|
91
91
|
## 一份更长的例子
|
|
92
92
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dclassql"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "A type-safe ORM generator for Python, creating fully type-hinted database clients from plain dataclass definitions."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -26,10 +26,10 @@ dependencies = [
|
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[project.scripts]
|
|
29
|
-
"
|
|
29
|
+
"dclassql" = "dclassql.cli:main"
|
|
30
30
|
|
|
31
31
|
[build-system]
|
|
32
|
-
requires = ["uv_build>=0.
|
|
32
|
+
requires = ["uv_build>=0.11.6,<0.12.0"]
|
|
33
33
|
build-backend = "uv_build"
|
|
34
34
|
|
|
35
35
|
[dependency-groups]
|
|
@@ -10,12 +10,12 @@ from .unwarp import unwarp, unwarp_or, unwarp_or_raise
|
|
|
10
10
|
class _MissingClient:
|
|
11
11
|
def __init__(self, *args: object, **kwargs: object) -> None:
|
|
12
12
|
raise RuntimeError(
|
|
13
|
-
"dclassql.Client 尚未生成。请先运行 `
|
|
13
|
+
"dclassql.Client 尚未生成。请先运行 `dclassql -m <model.py> generate` 生成客户端后再导入。"
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
try: # pragma: no cover - exercised in integration tests
|
|
17
17
|
from .client import Client # type: ignore
|
|
18
|
-
except ModuleNotFoundError: # pragma: no cover - fallback when未生成
|
|
18
|
+
except (ModuleNotFoundError, ImportError): # pragma: no cover - fallback when未生成
|
|
19
19
|
Client = _MissingClient # type: ignore[assignment]
|
|
20
20
|
|
|
21
21
|
|
|
@@ -3,9 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
import importlib.util
|
|
5
5
|
import re
|
|
6
|
-
import shutil
|
|
7
6
|
import sys
|
|
8
|
-
import warnings
|
|
9
7
|
from pathlib import Path
|
|
10
8
|
from types import ModuleType
|
|
11
9
|
from typing import Any, Callable, Literal, Sequence
|
|
@@ -19,7 +17,6 @@ from .runtime.datasource import open_sqlite_connection
|
|
|
19
17
|
|
|
20
18
|
DEFAULT_MODEL_FILE = "model.py"
|
|
21
19
|
GENERATED_CLIENT_FILENAME = "client.py"
|
|
22
|
-
GENERATED_MODELS_DIRNAME = "generated_models"
|
|
23
20
|
|
|
24
21
|
ConfirmRebuildMode = Literal["auto", "prompt"]
|
|
25
22
|
ConfirmCallback = Callable[
|
|
@@ -77,43 +74,6 @@ def resolve_asdict_stub_path() -> Path:
|
|
|
77
74
|
return _find_package_directory() / "asdict.pyi"
|
|
78
75
|
|
|
79
76
|
|
|
80
|
-
def resolve_models_directory() -> Path:
|
|
81
|
-
return _find_package_directory() / GENERATED_MODELS_DIRNAME
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _sanitize_name(name: str) -> str:
|
|
85
|
-
sanitized = re.sub(r"[^0-9a-zA-Z_]+", "_", name).strip("_")
|
|
86
|
-
return sanitized.lower() or "models"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def compute_model_target(module_path: Path) -> tuple[Path, str]:
|
|
90
|
-
base_dir = resolve_models_directory()
|
|
91
|
-
base_dir.mkdir(parents=True, exist_ok=True)
|
|
92
|
-
(base_dir / "__init__.py").touch()
|
|
93
|
-
|
|
94
|
-
module_name = _sanitize_name(module_path.stem)
|
|
95
|
-
|
|
96
|
-
target_path = base_dir / f"{module_name}.py"
|
|
97
|
-
import_path = f"dclassql.{GENERATED_MODELS_DIRNAME}.{module_name}"
|
|
98
|
-
return target_path, import_path
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def materialize_model_module(module_path: Path) -> str:
|
|
102
|
-
target_path, import_path = compute_model_target(module_path)
|
|
103
|
-
if target_path.exists() or target_path.is_symlink():
|
|
104
|
-
target_path.unlink()
|
|
105
|
-
try:
|
|
106
|
-
target_path.symlink_to(module_path.resolve())
|
|
107
|
-
except OSError as exc:
|
|
108
|
-
warnings.warn(
|
|
109
|
-
f"Unable to create symlink for model '{module_path}'; falling back to copy. ({exc})",
|
|
110
|
-
RuntimeWarning,
|
|
111
|
-
stacklevel=2,
|
|
112
|
-
)
|
|
113
|
-
shutil.copy2(module_path, target_path)
|
|
114
|
-
return import_path
|
|
115
|
-
|
|
116
|
-
|
|
117
77
|
def collect_models(module: ModuleType) -> list[type[Any]]:
|
|
118
78
|
from dataclasses import is_dataclass
|
|
119
79
|
|
|
@@ -205,13 +165,7 @@ def push_database(
|
|
|
205
165
|
def command_generate(module_path: Path) -> None:
|
|
206
166
|
module = load_module(module_path)
|
|
207
167
|
models = collect_models(module)
|
|
208
|
-
generated_models_module = materialize_model_module(module_path)
|
|
209
|
-
original_modules = {model: model.__module__ for model in models}
|
|
210
|
-
for model in models:
|
|
211
|
-
model.__module__ = generated_models_module
|
|
212
168
|
generated = generate_client(models)
|
|
213
|
-
for model, original in original_modules.items():
|
|
214
|
-
model.__module__ = original
|
|
215
169
|
output_path = resolve_generated_path()
|
|
216
170
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
217
171
|
output_path.write_text(generated.code, encoding="utf-8")
|
|
@@ -181,21 +181,19 @@ def generate_client(models: Sequence[type[Any]]) -> GeneratedModule:
|
|
|
181
181
|
renderer = _TypeRenderer({info.model: name for name, info in model_infos.items()})
|
|
182
182
|
filter_registry = _ScalarFilterRegistry(renderer)
|
|
183
183
|
|
|
184
|
-
model_imports:
|
|
184
|
+
model_imports: defaultdict[str, set[str]] = defaultdict(set)
|
|
185
185
|
for info in model_infos.values():
|
|
186
|
-
|
|
187
|
-
model_imports.setdefault(module, set()).add(info.model.__name__)
|
|
186
|
+
model_imports[info.model.__module__].add(info.model.__name__)
|
|
188
187
|
|
|
189
188
|
model_contexts = [
|
|
190
189
|
_build_model_context(model_infos[name], renderer, model_infos, filter_registry)
|
|
191
190
|
for name in sorted(model_infos.keys())
|
|
192
191
|
]
|
|
193
192
|
|
|
194
|
-
|
|
195
|
-
combined_imports: dict[str, set[str]] = defaultdict(set)
|
|
193
|
+
combined_imports: defaultdict[str, set[str]] = defaultdict(set)
|
|
196
194
|
for module, names in model_imports.items():
|
|
197
195
|
combined_imports[module].update(names)
|
|
198
|
-
for module, names in module_imports.items():
|
|
196
|
+
for module, names in renderer.module_imports.items():
|
|
199
197
|
combined_imports[module].update(names)
|
|
200
198
|
|
|
201
199
|
import_blocks = [
|
|
@@ -897,7 +895,7 @@ class _ScalarFilterRegistry:
|
|
|
897
895
|
class _TypeRenderer:
|
|
898
896
|
def __init__(self, model_map: Mapping[type[Any], str]) -> None:
|
|
899
897
|
self._model_map = dict(model_map)
|
|
900
|
-
self._module_imports:
|
|
898
|
+
self._module_imports: defaultdict[str, set[str]] = defaultdict(set) # {module: set of names}
|
|
901
899
|
self._typing_imports: set[str] = set()
|
|
902
900
|
|
|
903
901
|
def render(self, tp: Any) -> str:
|
|
@@ -941,13 +939,14 @@ class _TypeRenderer:
|
|
|
941
939
|
if tp.__module__ == "builtins":
|
|
942
940
|
return tp.__name__
|
|
943
941
|
if tp.__module__ == "datetime":
|
|
944
|
-
self._module_imports
|
|
942
|
+
self._module_imports["datetime"].add(tp.__name__)
|
|
945
943
|
return tp.__name__
|
|
946
|
-
self._module_imports
|
|
944
|
+
self._module_imports[tp.__module__].add(tp.__qualname__.split(".")[0])
|
|
947
945
|
return tp.__qualname__
|
|
948
946
|
return repr(tp)
|
|
949
947
|
|
|
950
|
-
|
|
948
|
+
@property
|
|
949
|
+
def module_imports(self) -> defaultdict[str, set[str]]:
|
|
951
950
|
return self._module_imports
|
|
952
951
|
|
|
953
952
|
@property
|
|
@@ -25,6 +25,7 @@ class BackendBase(BackendProtocol, ABC):
|
|
|
25
25
|
query_cls: type[Query] = Query
|
|
26
26
|
table_cls: type[Table] = Table
|
|
27
27
|
parameter_cls: type[Parameter] = Parameter
|
|
28
|
+
like_escape_char: str | None = "\\"
|
|
28
29
|
|
|
29
30
|
def __init__(self, *, echo_sql: bool = False) -> None:
|
|
30
31
|
self._identity_map: dict[tuple[type[Any], tuple[Any, ...]], list[ReferenceType[object]]] = {}
|
|
@@ -33,7 +34,7 @@ class BackendBase(BackendProtocol, ABC):
|
|
|
33
34
|
def insert(
|
|
34
35
|
self,
|
|
35
36
|
table: TableProtocol[ModelT, InsertT, WhereT, IncludeT, OrderByT],
|
|
36
|
-
data: InsertT | Mapping[str, object],
|
|
37
|
+
data: InsertT | ModelT | Mapping[str, object],
|
|
37
38
|
) -> ModelT:
|
|
38
39
|
payload = table.serialize_insert(data)
|
|
39
40
|
if not payload:
|
|
@@ -61,7 +62,7 @@ class BackendBase(BackendProtocol, ABC):
|
|
|
61
62
|
def insert_many(
|
|
62
63
|
self,
|
|
63
64
|
table: TableProtocol[ModelT, InsertT, WhereT, IncludeT, OrderByT],
|
|
64
|
-
data: Sequence[InsertT | Mapping[str, object]],
|
|
65
|
+
data: Sequence[InsertT | ModelT | Mapping[str, object]],
|
|
65
66
|
*,
|
|
66
67
|
batch_size: int | None = None,
|
|
67
68
|
) -> list[ModelT]:
|
|
@@ -115,7 +116,7 @@ class BackendBase(BackendProtocol, ABC):
|
|
|
115
116
|
*,
|
|
116
117
|
where: UpsertWhereT,
|
|
117
118
|
update: Mapping[str, object],
|
|
118
|
-
insert: InsertT | Mapping[str, object],
|
|
119
|
+
insert: InsertT | ModelT | Mapping[str, object],
|
|
119
120
|
include: Mapping[str, bool] | None = None,
|
|
120
121
|
) -> ModelT:
|
|
121
122
|
where_payload = dict(where)
|
|
@@ -273,7 +274,7 @@ class BackendBase(BackendProtocol, ABC):
|
|
|
273
274
|
delete_query: QueryBuilder = self.query_cls.from_(sql_table).delete()
|
|
274
275
|
criterion, params = self._compile_where(table, sql_table, where)
|
|
275
276
|
if criterion is None:
|
|
276
|
-
raise ValueError("delete() requires a where clause")
|
|
277
|
+
raise ValueError("delete() requires a where clause, if you want to delete more rows use delete_many()")
|
|
277
278
|
delete_query = delete_query.where(criterion)
|
|
278
279
|
sql = self._render_query(delete_query)
|
|
279
280
|
returning_columns = [spec.name for spec in table.column_specs]
|
|
@@ -25,7 +25,7 @@ class TableProtocol[ModelT, InsertT, WhereT, IncludeT, OrderByT](Protocol):
|
|
|
25
25
|
column_specs_by_name: Mapping[str, ColumnSpec]
|
|
26
26
|
|
|
27
27
|
@classmethod
|
|
28
|
-
def serialize_insert(cls, data: InsertT | Mapping[str, object]) -> dict[str, object]: ...
|
|
28
|
+
def serialize_insert(cls, data: InsertT | ModelT | Mapping[str, object]) -> dict[str, object]: ...
|
|
29
29
|
@classmethod
|
|
30
30
|
def serialize_update(cls, data: Mapping[str, object]) -> dict[str, object]: ...
|
|
31
31
|
|
|
@@ -46,11 +46,12 @@ class BackendProtocol(Protocol):
|
|
|
46
46
|
query_cls: type[Query]
|
|
47
47
|
table_cls: type[Table]
|
|
48
48
|
parameter_cls: type[Parameter]
|
|
49
|
+
like_escape_char: str | None
|
|
49
50
|
|
|
50
51
|
def insert(
|
|
51
52
|
self,
|
|
52
53
|
table: TableProtocol[ModelT, InsertT, WhereT, IncludeT, OrderByT],
|
|
53
|
-
data: InsertT | Mapping[str, object],
|
|
54
|
+
data: InsertT | ModelT | Mapping[str, object],
|
|
54
55
|
) -> ModelT: ...
|
|
55
56
|
def update(
|
|
56
57
|
self,
|
|
@@ -66,14 +67,14 @@ class BackendProtocol(Protocol):
|
|
|
66
67
|
*,
|
|
67
68
|
where: UpsertWhereT,
|
|
68
69
|
update: Mapping[str, object],
|
|
69
|
-
insert: InsertT | Mapping[str, object],
|
|
70
|
+
insert: InsertT | ModelT | Mapping[str, object],
|
|
70
71
|
include: IncludeT | None = None,
|
|
71
72
|
) -> ModelT: ...
|
|
72
73
|
|
|
73
74
|
def insert_many(
|
|
74
75
|
self,
|
|
75
76
|
table: TableProtocol[ModelT, InsertT, WhereT, IncludeT, OrderByT],
|
|
76
|
-
data: Sequence[InsertT | Mapping[str, object]],
|
|
77
|
+
data: Sequence[InsertT | ModelT | Mapping[str, object]],
|
|
77
78
|
*,
|
|
78
79
|
batch_size: int | None = None,
|
|
79
80
|
) -> list[ModelT]: ...
|
|
@@ -34,6 +34,23 @@ def combine_or(criteria: Sequence[Criterion | None]) -> Criterion | None:
|
|
|
34
34
|
return result
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
class EscapeLikeCriterion(Criterion):
|
|
38
|
+
def __init__(self, field: Field, parameter: Parameter, *, escape: str, negated: bool = False) -> None:
|
|
39
|
+
self._field = field
|
|
40
|
+
self._parameter = parameter
|
|
41
|
+
self._escape = escape
|
|
42
|
+
self._negated = negated
|
|
43
|
+
|
|
44
|
+
def negate(self) -> Criterion:
|
|
45
|
+
return EscapeLikeCriterion(self._field, self._parameter, escape=self._escape, negated=not self._negated)
|
|
46
|
+
|
|
47
|
+
def get_sql(self, **kwargs: object) -> str: # type: ignore[override]
|
|
48
|
+
field_sql = self._field.get_sql(**kwargs)
|
|
49
|
+
param_sql = self._parameter.get_sql(**kwargs)
|
|
50
|
+
operator = "NOT LIKE" if self._negated else "LIKE"
|
|
51
|
+
return f"{field_sql} {operator} {param_sql} ESCAPE '{self._escape}'"
|
|
52
|
+
|
|
53
|
+
|
|
37
54
|
class WhereCompiler:
|
|
38
55
|
def __init__(
|
|
39
56
|
self,
|
|
@@ -157,13 +174,13 @@ class WhereCompiler:
|
|
|
157
174
|
return field >= self._bind_value(operand)
|
|
158
175
|
if operator == "CONTAINS":
|
|
159
176
|
text = ensure_string(operand, operator=operator)
|
|
160
|
-
return self._apply_like(field, f"%{text}%")
|
|
177
|
+
return self._apply_like(field, f"%{self._escape_like(text)}%")
|
|
161
178
|
if operator == "STARTS_WITH":
|
|
162
179
|
text = ensure_string(operand, operator=operator)
|
|
163
|
-
return self._apply_like(field, f"{text}%")
|
|
180
|
+
return self._apply_like(field, f"{self._escape_like(text)}%")
|
|
164
181
|
if operator == "ENDS_WITH":
|
|
165
182
|
text = ensure_string(operand, operator=operator)
|
|
166
|
-
return self._apply_like(field, f"%{text}")
|
|
183
|
+
return self._apply_like(field, f"%{self._escape_like(text)}")
|
|
167
184
|
if operator == "NOT":
|
|
168
185
|
if isinstance(operand, ABCMapping):
|
|
169
186
|
compiled = self._compile_filter(field, operand)
|
|
@@ -176,9 +193,22 @@ class WhereCompiler:
|
|
|
176
193
|
|
|
177
194
|
def _apply_like(self, field: Field, pattern: str) -> Criterion:
|
|
178
195
|
parameter = self._bind_value(pattern)
|
|
196
|
+
escape = self._backend.like_escape_char
|
|
197
|
+
if escape:
|
|
198
|
+
return EscapeLikeCriterion(field, parameter, escape=escape)
|
|
179
199
|
# pypika 的 Field.like 允许 Term 参数, 但类型标注仅接受 str
|
|
180
200
|
return field.like(parameter) # type: ignore[arg-type]
|
|
181
201
|
|
|
202
|
+
def _escape_like(self, text: str) -> str:
|
|
203
|
+
escape = self._backend.like_escape_char
|
|
204
|
+
if not escape:
|
|
205
|
+
return text
|
|
206
|
+
return (
|
|
207
|
+
text.replace(escape, escape + escape)
|
|
208
|
+
.replace("%", escape + "%")
|
|
209
|
+
.replace("_", escape + "_")
|
|
210
|
+
)
|
|
211
|
+
|
|
182
212
|
def _bind_value(self, value: object) -> Parameter:
|
|
183
213
|
parameter = self._backend.new_parameter()
|
|
184
214
|
self.params.append(value)
|
|
@@ -13,8 +13,6 @@ def _adapt_date_iso(value: _dt.date) -> str:
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def _adapt_datetime_iso(value: _dt.datetime) -> str:
|
|
16
|
-
if value.tzinfo is not None:
|
|
17
|
-
value = value.astimezone(_dt.timezone.utc).replace(tzinfo=None)
|
|
18
16
|
return value.isoformat(sep="T")
|
|
19
17
|
|
|
20
18
|
|
|
@@ -40,4 +38,3 @@ def register_sqlite_adapters() -> None:
|
|
|
40
38
|
sqlite3.register_converter("datetime", _convert_datetime_iso)
|
|
41
39
|
|
|
42
40
|
_REGISTERED["done"] = True
|
|
43
|
-
|
|
@@ -106,7 +106,7 @@ class {{ table_class }}(TableProtocol):
|
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
@classmethod
|
|
109
|
-
def serialize_insert(cls, data: {{ insert_class }} | Mapping[str, object]) -> dict[str, object]:
|
|
109
|
+
def serialize_insert(cls, data: {{ insert_class }} | {{ name }} | Mapping[str, object]) -> dict[str, object]:
|
|
110
110
|
if isinstance(data, Mapping):
|
|
111
111
|
result: dict[str, object] = {}
|
|
112
112
|
{% for column in model.column_specs %}
|
|
@@ -114,7 +114,7 @@ class {{ table_class }}(TableProtocol):
|
|
|
114
114
|
result[{{ column.name_repr }}] = {{ column.mapping_value_expr }}{{ ' # type: ignore[attr-defined]' if column.is_enum else '' }}
|
|
115
115
|
{% endfor %}
|
|
116
116
|
return result
|
|
117
|
-
if isinstance(data, {{ insert_class }}):
|
|
117
|
+
if isinstance(data, ({{ insert_class }}, {{ name }})):
|
|
118
118
|
return {
|
|
119
119
|
{% for column in model.column_specs %} {{ column.name_repr }}: {{ column.insert_value_expr }},{{ ' # type: ignore[attr-defined]' if column.is_enum else '' }}
|
|
120
120
|
{% endfor %} }
|
|
@@ -138,10 +138,10 @@ class {{ table_class }}(TableProtocol):
|
|
|
138
138
|
def __str__(self) -> str:
|
|
139
139
|
return self._backend.escape_identifier(self.table_name)
|
|
140
140
|
|
|
141
|
-
def insert(self, data: {{ insert_class }} | {{ insert_dict_class }}) -> {{ name }}:
|
|
141
|
+
def insert(self, data: {{ insert_class }} | {{ name }} | {{ insert_dict_class }}) -> {{ name }}:
|
|
142
142
|
return self._backend.insert(self, data)
|
|
143
143
|
|
|
144
|
-
def insert_many(self, data: Sequence[{{ insert_class }} | {{ insert_dict_class }}], *, batch_size: int | None = None) -> list[{{ name }}]:
|
|
144
|
+
def insert_many(self, data: Sequence[{{ insert_class }} | {{ name }} | {{ insert_dict_class }}], *, batch_size: int | None = None) -> list[{{ name }}]:
|
|
145
145
|
return self._backend.insert_many(self, data, batch_size=batch_size)
|
|
146
146
|
|
|
147
147
|
def update(self, *, data: {{ update_dict_class }}, where: {{ where_dict_class }}, include: {{ include_dict_class }} | None = None) -> {{ name }}:
|
|
@@ -159,7 +159,7 @@ class {{ table_class }}(TableProtocol):
|
|
|
159
159
|
*,
|
|
160
160
|
where: {{ upsert_where_alias }},
|
|
161
161
|
update: {{ upsert_update_class }},
|
|
162
|
-
insert: {{ upsert_insert_class }} | {{ upsert_insert_dict_class }},
|
|
162
|
+
insert: {{ upsert_insert_class }} | {{ name }} | {{ upsert_insert_dict_class }},
|
|
163
163
|
include: {{ include_dict_class }} | None = None,
|
|
164
164
|
) -> {{ name }}:
|
|
165
165
|
return self._backend.upsert(self, where=where, update=update, insert=insert, include=include)
|