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.
Files changed (41) hide show
  1. {dclassql-0.2.0 → dclassql-0.3.0}/PKG-INFO +2 -2
  2. {dclassql-0.2.0 → dclassql-0.3.0}/README.md +1 -1
  3. {dclassql-0.2.0 → dclassql-0.3.0}/pyproject.toml +3 -3
  4. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/__init__.py +2 -2
  5. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/cli.py +0 -46
  6. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/codegen.py +9 -10
  7. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/base.py +5 -4
  8. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/protocols.py +5 -4
  9. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/where_compiler.py +33 -3
  10. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/sqlite_adapters.py +0 -3
  11. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/model_section.jinja +5 -5
  12. dclassql-0.2.0/src/dclassql/client.py +0 -863
  13. dclassql-0.2.0/src/dclassql/generated_models/__init__.py +0 -0
  14. dclassql-0.2.0/src/dclassql/generated_models/test_models.py +0 -78
  15. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/.gitignore +0 -0
  16. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/asdict.py +0 -0
  17. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/asdict.pyi +0 -0
  18. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/db_pool.py +0 -0
  19. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/model_inspector.py +0 -0
  20. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/push/__init__.py +0 -0
  21. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/push/base.py +0 -0
  22. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/push/sqlite.py +0 -0
  23. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/__init__.py +0 -0
  24. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/lazy.py +0 -0
  25. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/metadata.py +0 -0
  26. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/backends/sqlite.py +0 -0
  27. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/datasource.py +0 -0
  28. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/runtime/sql_recorder.py +0 -0
  29. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/table_spec.py +0 -0
  30. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/__init__.py +0 -0
  31. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/asdict_stub.pyi.jinja +0 -0
  32. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/client_module.py.jinja +0 -0
  33. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/client_class.jinja +0 -0
  34. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/exports.jinja +0 -0
  35. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/imports.jinja +0 -0
  36. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/macros.jinja +0 -0
  37. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/templates/partials/scalar_filters.jinja +0 -0
  38. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/typing.py +0 -0
  39. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/unwarp.py +0 -0
  40. {dclassql-0.2.0 → dclassql-0.3.0}/src/dclassql/utils/__init__.py +0 -0
  41. {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.2.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 仍在早期开发阶段, 已完成代码生成和 SQLite 支持, 后续将扩展更多数据库与查询功能.
89
+ DataclassQL 仍在早期开发阶段, 但不是无根浮萍, 我已经在另外两个项目里大量使用, 目前基于其他项目的反馈来更新.
90
90
 
91
91
  ## 一份更长的例子
92
92
 
@@ -65,7 +65,7 @@ uv add dclassql
65
65
 
66
66
  ## 当前状态
67
67
 
68
- DataclassQL 仍在早期开发阶段, 已完成代码生成和 SQLite 支持, 后续将扩展更多数据库与查询功能.
68
+ DataclassQL 仍在早期开发阶段, 但不是无根浮萍, 我已经在另外两个项目里大量使用, 目前基于其他项目的反馈来更新.
69
69
 
70
70
  ## 一份更长的例子
71
71
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dclassql"
3
- version = "0.2.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
- "dql" = "dclassql.cli:main"
29
+ "dclassql" = "dclassql.cli:main"
30
30
 
31
31
  [build-system]
32
- requires = ["uv_build>=0.8.22,<0.9.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 尚未生成。请先运行 `dql -m <model.py> generate` 生成客户端后再导入。"
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: dict[str, set[str]] = defaultdict(set)
184
+ model_imports: defaultdict[str, set[str]] = defaultdict(set)
185
185
  for info in model_infos.values():
186
- module = info.model.__module__
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
- module_imports = renderer.build_imports()
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: dict[str, set[str]] = defaultdict(set)
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.setdefault("datetime", set()).add(tp.__name__)
942
+ self._module_imports["datetime"].add(tp.__name__)
945
943
  return tp.__name__
946
- self._module_imports.setdefault(tp.__module__, set()).add(tp.__qualname__.split(".")[0])
944
+ self._module_imports[tp.__module__].add(tp.__qualname__.split(".")[0])
947
945
  return tp.__qualname__
948
946
  return repr(tp)
949
947
 
950
- def build_imports(self) -> Mapping[str, set[str]]:
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)