dclassql 0.1.0__tar.gz → 0.1.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.
- {dclassql-0.1.0 → dclassql-0.1.2}/PKG-INFO +13 -2
- dclassql-0.1.2/pyproject.toml +39 -0
- dclassql-0.1.2/src/dclassql/.gitignore +2 -0
- dclassql-0.1.2/src/dclassql/__init__.py +27 -0
- {dclassql-0.1.0 → dclassql-0.1.2}/src/dclassql/cli.py +64 -2
- dclassql-0.1.2/src/dclassql/client.py +496 -0
- dclassql-0.1.2/src/dclassql/codegen.py +507 -0
- dclassql-0.1.2/src/dclassql/generated_models/__init__.py +0 -0
- dclassql-0.1.2/src/dclassql/generated_models/test_models.py +78 -0
- dclassql-0.1.2/src/dclassql/runtime/backends/__init__.py +15 -0
- dclassql-0.1.2/src/dclassql/runtime/backends/base.py +323 -0
- dclassql-0.1.2/src/dclassql/runtime/backends/lazy.py +376 -0
- dclassql-0.1.2/src/dclassql/runtime/backends/metadata.py +31 -0
- dclassql-0.1.2/src/dclassql/runtime/backends/protocols.py +66 -0
- dclassql-0.1.2/src/dclassql/runtime/backends/sqlite.py +128 -0
- {dclassql-0.1.0 → dclassql-0.1.2}/src/dclassql/runtime/datasource.py +7 -1
- dclassql-0.1.2/src/dclassql/runtime/sqlite_adapters.py +43 -0
- dclassql-0.1.2/src/dclassql/templates/__init__.py +1 -0
- dclassql-0.1.2/src/dclassql/templates/client_module.py.jinja +12 -0
- dclassql-0.1.2/src/dclassql/templates/partials/client_class.jinja +29 -0
- dclassql-0.1.2/src/dclassql/templates/partials/datasource_config.jinja +11 -0
- dclassql-0.1.2/src/dclassql/templates/partials/exports.jinja +3 -0
- dclassql-0.1.2/src/dclassql/templates/partials/imports.jinja +17 -0
- dclassql-0.1.2/src/dclassql/templates/partials/macros.jinja +82 -0
- dclassql-0.1.2/src/dclassql/templates/partials/model_section.jinja +85 -0
- dclassql-0.1.2/src/dclassql/typing.py +7 -0
- dclassql-0.1.0/pyproject.toml +0 -25
- dclassql-0.1.0/src/dclassql/__init__.py +0 -12
- dclassql-0.1.0/src/dclassql/codegen.py +0 -508
- dclassql-0.1.0/src/dclassql/runtime/backends.py +0 -669
- {dclassql-0.1.0 → dclassql-0.1.2}/README.md +0 -0
- {dclassql-0.1.0 → dclassql-0.1.2}/src/dclassql/db_pool.py +0 -0
- {dclassql-0.1.0 → dclassql-0.1.2}/src/dclassql/model_inspector.py +0 -0
- {dclassql-0.1.0 → dclassql-0.1.2}/src/dclassql/push/__init__.py +0 -0
- {dclassql-0.1.0 → dclassql-0.1.2}/src/dclassql/push/base.py +0 -0
- {dclassql-0.1.0 → dclassql-0.1.2}/src/dclassql/push/sqlite.py +0 -0
- {dclassql-0.1.0 → dclassql-0.1.2}/src/dclassql/table_spec.py +0 -0
- {dclassql-0.1.0 → dclassql-0.1.2}/src/dclassql/unwarp.py +0 -0
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dclassql
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: A type-safe ORM generator for Python, creating fully type-hinted database clients from plain dataclass definitions.
|
|
5
|
+
Keywords: orm,codegen,sqlite,dataclass,typed
|
|
5
6
|
Author: myuanz
|
|
6
7
|
Author-email: myuanz <provefars@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Dist: jinja2>=3.1.6
|
|
7
16
|
Requires-Dist: pypika>=0.48.9
|
|
17
|
+
Requires-Dist: typing-extensions>=4.12.2
|
|
8
18
|
Requires-Python: >=3.12
|
|
19
|
+
Project-URL: Homepage, https://github.com/myuanz/dataclassql
|
|
9
20
|
Description-Content-Type: text/markdown
|
|
10
21
|
|
|
11
22
|
# DataclassQL
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dclassql"
|
|
3
|
+
version = "0.1.2"
|
|
4
|
+
description = "A type-safe ORM generator for Python, creating fully type-hinted database clients from plain dataclass definitions."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "myuanz", email = "provefars@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
license = { text = "MIT" }
|
|
10
|
+
keywords = ["orm", "codegen", "sqlite", "dataclass", "typed"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3.12",
|
|
13
|
+
"Programming Language :: Python :: 3.13",
|
|
14
|
+
"Programming Language :: Python :: 3.14",
|
|
15
|
+
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
urls = { "Homepage" = "https://github.com/myuanz/dataclassql" }
|
|
21
|
+
requires-python = ">=3.12"
|
|
22
|
+
dependencies = [
|
|
23
|
+
"jinja2>=3.1.6",
|
|
24
|
+
"pypika>=0.48.9",
|
|
25
|
+
"typing_extensions>=4.12.2",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
"dql" = "dclassql.cli:main"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["uv_build>=0.8.22,<0.9.0"]
|
|
33
|
+
build-backend = "uv_build"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"pyright>=1.1.407",
|
|
38
|
+
"pytest>=8.4.2",
|
|
39
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from .db_pool import BaseDBPool, save_local
|
|
2
|
+
from .push import db_push
|
|
3
|
+
from .runtime.backends.lazy import eager
|
|
4
|
+
from .unwarp import unwarp, unwarp_or, unwarp_or_raise
|
|
5
|
+
|
|
6
|
+
class _MissingClient:
|
|
7
|
+
def __init__(self, *args: object, **kwargs: object) -> None:
|
|
8
|
+
raise RuntimeError(
|
|
9
|
+
"dclassql.Client 尚未生成。请先运行 `dql -m <model.py> generate` 生成客户端后再导入。"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
try: # pragma: no cover - exercised in integration tests
|
|
13
|
+
from .client import Client # type: ignore
|
|
14
|
+
except ModuleNotFoundError: # pragma: no cover - fallback when未生成
|
|
15
|
+
Client = _MissingClient # type: ignore[assignment]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
'Client',
|
|
20
|
+
'db_push',
|
|
21
|
+
'eager',
|
|
22
|
+
'unwarp',
|
|
23
|
+
'unwarp_or',
|
|
24
|
+
'unwarp_or_raise',
|
|
25
|
+
'BaseDBPool',
|
|
26
|
+
'save_local',
|
|
27
|
+
]
|
|
@@ -2,17 +2,22 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import importlib.util
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
5
7
|
import sys
|
|
8
|
+
import warnings
|
|
6
9
|
from pathlib import Path
|
|
7
10
|
from types import ModuleType
|
|
8
11
|
from typing import Any, Sequence
|
|
9
12
|
|
|
10
13
|
from .codegen import generate_client
|
|
11
14
|
from .push import db_push
|
|
12
|
-
from .runtime.datasource import open_sqlite_connection
|
|
15
|
+
from .runtime.datasource import open_sqlite_connection
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
DEFAULT_MODEL_FILE = "model.py"
|
|
19
|
+
GENERATED_CLIENT_FILENAME = "client.py"
|
|
20
|
+
GENERATED_MODELS_DIRNAME = "generated_models"
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
def load_module(module_path: Path) -> ModuleType:
|
|
@@ -33,6 +38,54 @@ def load_module(module_path: Path) -> ModuleType:
|
|
|
33
38
|
return module
|
|
34
39
|
|
|
35
40
|
|
|
41
|
+
def _find_package_directory() -> Path:
|
|
42
|
+
spec = importlib.util.find_spec("dclassql")
|
|
43
|
+
if spec is None or not spec.submodule_search_locations:
|
|
44
|
+
raise RuntimeError("Cannot locate installed dclassql package to write generated client")
|
|
45
|
+
return Path(next(iter(spec.submodule_search_locations))).resolve()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def resolve_generated_path() -> Path:
|
|
49
|
+
return _find_package_directory() / GENERATED_CLIENT_FILENAME
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def resolve_models_directory() -> Path:
|
|
53
|
+
return _find_package_directory() / GENERATED_MODELS_DIRNAME
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _sanitize_name(name: str) -> str:
|
|
57
|
+
sanitized = re.sub(r"[^0-9a-zA-Z_]+", "_", name).strip("_")
|
|
58
|
+
return sanitized.lower() or "models"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def compute_model_target(module_path: Path) -> tuple[Path, str]:
|
|
62
|
+
base_dir = resolve_models_directory()
|
|
63
|
+
base_dir.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
(base_dir / "__init__.py").touch()
|
|
65
|
+
|
|
66
|
+
module_name = _sanitize_name(module_path.stem)
|
|
67
|
+
|
|
68
|
+
target_path = base_dir / f"{module_name}.py"
|
|
69
|
+
import_path = f"dclassql.{GENERATED_MODELS_DIRNAME}.{module_name}"
|
|
70
|
+
return target_path, import_path
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def materialize_model_module(module_path: Path) -> str:
|
|
74
|
+
target_path, import_path = compute_model_target(module_path)
|
|
75
|
+
if target_path.exists() or target_path.is_symlink():
|
|
76
|
+
target_path.unlink()
|
|
77
|
+
try:
|
|
78
|
+
target_path.symlink_to(module_path.resolve())
|
|
79
|
+
except OSError as exc:
|
|
80
|
+
warnings.warn(
|
|
81
|
+
f"Unable to create symlink for model '{module_path}'; falling back to copy. ({exc})",
|
|
82
|
+
RuntimeWarning,
|
|
83
|
+
stacklevel=2,
|
|
84
|
+
)
|
|
85
|
+
shutil.copy2(module_path, target_path)
|
|
86
|
+
return import_path
|
|
87
|
+
|
|
88
|
+
|
|
36
89
|
def collect_models(module: ModuleType) -> list[type[Any]]:
|
|
37
90
|
from dataclasses import is_dataclass
|
|
38
91
|
|
|
@@ -74,8 +127,17 @@ def push_database(models: Sequence[type[Any]]) -> None:
|
|
|
74
127
|
def command_generate(module_path: Path) -> None:
|
|
75
128
|
module = load_module(module_path)
|
|
76
129
|
models = collect_models(module)
|
|
130
|
+
generated_models_module = materialize_model_module(module_path)
|
|
131
|
+
original_modules = {model: model.__module__ for model in models}
|
|
132
|
+
for model in models:
|
|
133
|
+
model.__module__ = generated_models_module
|
|
77
134
|
generated = generate_client(models)
|
|
78
|
-
|
|
135
|
+
for model, original in original_modules.items():
|
|
136
|
+
model.__module__ = original
|
|
137
|
+
output_path = resolve_generated_path()
|
|
138
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
139
|
+
output_path.write_text(generated.code, encoding="utf-8")
|
|
140
|
+
sys.stdout.write(f"Client written to {output_path}\n")
|
|
79
141
|
|
|
80
142
|
|
|
81
143
|
def command_push_db(module_path: Path) -> None:
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from types import MappingProxyType
|
|
5
|
+
from typing import Any, Literal, Mapping, Sequence, NotRequired
|
|
6
|
+
from typing_extensions import TypedDict
|
|
7
|
+
|
|
8
|
+
from dclassql.db_pool import BaseDBPool, save_local
|
|
9
|
+
from dclassql.runtime.backends import BackendProtocol, ColumnSpec, ForeignKeySpec, RelationSpec
|
|
10
|
+
from dclassql.runtime.datasource import open_sqlite_connection
|
|
11
|
+
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from dclassql.generated_models.test_models import Address, BirthDay, Book, User, UserBook
|
|
14
|
+
|
|
15
|
+
@dataclass(slots=True)
|
|
16
|
+
class DataSourceConfig:
|
|
17
|
+
provider: str
|
|
18
|
+
url: str | None
|
|
19
|
+
name: str | None = None
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def key(self) -> str:
|
|
23
|
+
return self.name or self.provider
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
TAddressIncludeCol = Literal['user']
|
|
28
|
+
TAddressSortableCol = Literal['id', 'location', 'user_id']
|
|
29
|
+
|
|
30
|
+
@dataclass(slots=True, kw_only=True)
|
|
31
|
+
class AddressInsert:
|
|
32
|
+
id: int | None = None
|
|
33
|
+
location: str
|
|
34
|
+
user_id: int
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AddressInsertDict(TypedDict, closed=True):
|
|
38
|
+
id: NotRequired[int]
|
|
39
|
+
location: str
|
|
40
|
+
user_id: int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AddressWhereDict(TypedDict, total=False, closed=True):
|
|
44
|
+
id: int | None
|
|
45
|
+
location: str | None
|
|
46
|
+
user_id: int | None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AddressIncludeDict(TypedDict, total=False, closed=True):
|
|
50
|
+
user: bool
|
|
51
|
+
|
|
52
|
+
class AddressOrderByDict(TypedDict, total=False, closed=True):
|
|
53
|
+
id: Literal['asc', 'desc']
|
|
54
|
+
location: Literal['asc', 'desc']
|
|
55
|
+
user_id: Literal['asc', 'desc']
|
|
56
|
+
|
|
57
|
+
class AddressTable:
|
|
58
|
+
model = Address
|
|
59
|
+
insert_model = AddressInsert
|
|
60
|
+
datasource = DataSourceConfig(provider='sqlite', url='sqlite:///test.db', name=None)
|
|
61
|
+
column_specs: tuple[ColumnSpec, ...] = (
|
|
62
|
+
ColumnSpec(name='id', optional=False, auto_increment=True, has_default=False, has_default_factory=False),
|
|
63
|
+
ColumnSpec(name='location', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
64
|
+
ColumnSpec(name='user_id', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
65
|
+
)
|
|
66
|
+
column_specs_by_name: Mapping[str, ColumnSpec] = MappingProxyType({spec.name: spec for spec in column_specs})
|
|
67
|
+
primary_key: tuple[str, ...] = ('id',)
|
|
68
|
+
indexes: tuple[tuple[str, ...], ...] = ()
|
|
69
|
+
unique_indexes: tuple[tuple[str, ...], ...] = ()
|
|
70
|
+
foreign_keys: tuple[ForeignKeySpec, ...] = (
|
|
71
|
+
ForeignKeySpec(
|
|
72
|
+
local_columns=('user_id',),
|
|
73
|
+
remote_model=User,
|
|
74
|
+
remote_columns=('id',),
|
|
75
|
+
backref='addresses',
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
relations: tuple[RelationSpec, ...] = (
|
|
80
|
+
RelationSpec(name='user', table_name='UserTable', table_module=__name__, many=False, mapping=(('user_id', 'id'),), table_factory=lambda: UserTable),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def __init__(self, backend: BackendProtocol) -> None:
|
|
85
|
+
self._backend = backend
|
|
86
|
+
|
|
87
|
+
def insert(self, data: AddressInsert | AddressInsertDict) -> Address:
|
|
88
|
+
return self._backend.insert(self, data)
|
|
89
|
+
|
|
90
|
+
def insert_many(self, data: Sequence[AddressInsert | AddressInsertDict], *, batch_size: int | None = None) -> list[Address]:
|
|
91
|
+
return self._backend.insert_many(self, data, batch_size=batch_size)
|
|
92
|
+
|
|
93
|
+
def find_many(self, *, where: AddressWhereDict | None = None, include: AddressIncludeDict | None = None, order_by: AddressOrderByDict | None = None, take: int | None = None, skip: int | None = None) -> list[Address]:
|
|
94
|
+
return self._backend.find_many(
|
|
95
|
+
self,
|
|
96
|
+
where=where, include=include, order_by=order_by,
|
|
97
|
+
take=take, skip=skip
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def find_first(self, *, where: AddressWhereDict | None = None, include: AddressIncludeDict | None = None, order_by: AddressOrderByDict | None = None, skip: int | None = None) -> Address | None:
|
|
101
|
+
return self._backend.find_first(
|
|
102
|
+
self,
|
|
103
|
+
where=where, include=include, order_by=order_by,
|
|
104
|
+
skip=skip
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
TBirthDayIncludeCol = Literal['user']
|
|
108
|
+
TBirthDaySortableCol = Literal['user_id', 'date']
|
|
109
|
+
|
|
110
|
+
@dataclass(slots=True, kw_only=True)
|
|
111
|
+
class BirthDayInsert:
|
|
112
|
+
user_id: int
|
|
113
|
+
date: datetime
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class BirthDayInsertDict(TypedDict, closed=True):
|
|
117
|
+
user_id: int
|
|
118
|
+
date: datetime
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class BirthDayWhereDict(TypedDict, total=False, closed=True):
|
|
122
|
+
user_id: int | None
|
|
123
|
+
date: datetime | None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class BirthDayIncludeDict(TypedDict, total=False, closed=True):
|
|
127
|
+
user: bool
|
|
128
|
+
|
|
129
|
+
class BirthDayOrderByDict(TypedDict, total=False, closed=True):
|
|
130
|
+
user_id: Literal['asc', 'desc']
|
|
131
|
+
date: Literal['asc', 'desc']
|
|
132
|
+
|
|
133
|
+
class BirthDayTable:
|
|
134
|
+
model = BirthDay
|
|
135
|
+
insert_model = BirthDayInsert
|
|
136
|
+
datasource = DataSourceConfig(provider='sqlite', url='sqlite:///test.db', name=None)
|
|
137
|
+
column_specs: tuple[ColumnSpec, ...] = (
|
|
138
|
+
ColumnSpec(name='user_id', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
139
|
+
ColumnSpec(name='date', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
140
|
+
)
|
|
141
|
+
column_specs_by_name: Mapping[str, ColumnSpec] = MappingProxyType({spec.name: spec for spec in column_specs})
|
|
142
|
+
primary_key: tuple[str, ...] = ('user_id',)
|
|
143
|
+
indexes: tuple[tuple[str, ...], ...] = ()
|
|
144
|
+
unique_indexes: tuple[tuple[str, ...], ...] = ()
|
|
145
|
+
foreign_keys: tuple[ForeignKeySpec, ...] = (
|
|
146
|
+
ForeignKeySpec(
|
|
147
|
+
local_columns=('user_id',),
|
|
148
|
+
remote_model=User,
|
|
149
|
+
remote_columns=('id',),
|
|
150
|
+
backref='birthday',
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
relations: tuple[RelationSpec, ...] = (
|
|
155
|
+
RelationSpec(name='user', table_name='UserTable', table_module=__name__, many=False, mapping=(('user_id', 'id'),), table_factory=lambda: UserTable),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def __init__(self, backend: BackendProtocol) -> None:
|
|
160
|
+
self._backend = backend
|
|
161
|
+
|
|
162
|
+
def insert(self, data: BirthDayInsert | BirthDayInsertDict) -> BirthDay:
|
|
163
|
+
return self._backend.insert(self, data)
|
|
164
|
+
|
|
165
|
+
def insert_many(self, data: Sequence[BirthDayInsert | BirthDayInsertDict], *, batch_size: int | None = None) -> list[BirthDay]:
|
|
166
|
+
return self._backend.insert_many(self, data, batch_size=batch_size)
|
|
167
|
+
|
|
168
|
+
def find_many(self, *, where: BirthDayWhereDict | None = None, include: BirthDayIncludeDict | None = None, order_by: BirthDayOrderByDict | None = None, take: int | None = None, skip: int | None = None) -> list[BirthDay]:
|
|
169
|
+
return self._backend.find_many(
|
|
170
|
+
self,
|
|
171
|
+
where=where, include=include, order_by=order_by,
|
|
172
|
+
take=take, skip=skip
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def find_first(self, *, where: BirthDayWhereDict | None = None, include: BirthDayIncludeDict | None = None, order_by: BirthDayOrderByDict | None = None, skip: int | None = None) -> BirthDay | None:
|
|
176
|
+
return self._backend.find_first(
|
|
177
|
+
self,
|
|
178
|
+
where=where, include=include, order_by=order_by,
|
|
179
|
+
skip=skip
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
TBookIncludeCol = Literal['users']
|
|
183
|
+
TBookSortableCol = Literal['id', 'name']
|
|
184
|
+
|
|
185
|
+
@dataclass(slots=True, kw_only=True)
|
|
186
|
+
class BookInsert:
|
|
187
|
+
id: int | None = None
|
|
188
|
+
name: str
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class BookInsertDict(TypedDict, closed=True):
|
|
192
|
+
id: NotRequired[int]
|
|
193
|
+
name: str
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class BookWhereDict(TypedDict, total=False, closed=True):
|
|
197
|
+
id: int | None
|
|
198
|
+
name: str | None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class BookIncludeDict(TypedDict, total=False, closed=True):
|
|
202
|
+
users: bool
|
|
203
|
+
|
|
204
|
+
class BookOrderByDict(TypedDict, total=False, closed=True):
|
|
205
|
+
id: Literal['asc', 'desc']
|
|
206
|
+
name: Literal['asc', 'desc']
|
|
207
|
+
|
|
208
|
+
class BookTable:
|
|
209
|
+
model = Book
|
|
210
|
+
insert_model = BookInsert
|
|
211
|
+
datasource = DataSourceConfig(provider='sqlite', url='sqlite:///test.db', name=None)
|
|
212
|
+
column_specs: tuple[ColumnSpec, ...] = (
|
|
213
|
+
ColumnSpec(name='id', optional=False, auto_increment=True, has_default=False, has_default_factory=False),
|
|
214
|
+
ColumnSpec(name='name', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
215
|
+
)
|
|
216
|
+
column_specs_by_name: Mapping[str, ColumnSpec] = MappingProxyType({spec.name: spec for spec in column_specs})
|
|
217
|
+
primary_key: tuple[str, ...] = ('id',)
|
|
218
|
+
indexes: tuple[tuple[str, ...], ...] = (('name',),)
|
|
219
|
+
unique_indexes: tuple[tuple[str, ...], ...] = ()
|
|
220
|
+
foreign_keys: tuple[ForeignKeySpec, ...] = ()
|
|
221
|
+
|
|
222
|
+
relations: tuple[RelationSpec, ...] = (
|
|
223
|
+
RelationSpec(name='users', table_name='UserBookTable', table_module=__name__, many=True, mapping=(('id', 'book_id'),), table_factory=lambda: UserBookTable),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def __init__(self, backend: BackendProtocol) -> None:
|
|
228
|
+
self._backend = backend
|
|
229
|
+
|
|
230
|
+
def insert(self, data: BookInsert | BookInsertDict) -> Book:
|
|
231
|
+
return self._backend.insert(self, data)
|
|
232
|
+
|
|
233
|
+
def insert_many(self, data: Sequence[BookInsert | BookInsertDict], *, batch_size: int | None = None) -> list[Book]:
|
|
234
|
+
return self._backend.insert_many(self, data, batch_size=batch_size)
|
|
235
|
+
|
|
236
|
+
def find_many(self, *, where: BookWhereDict | None = None, include: BookIncludeDict | None = None, order_by: BookOrderByDict | None = None, take: int | None = None, skip: int | None = None) -> list[Book]:
|
|
237
|
+
return self._backend.find_many(
|
|
238
|
+
self,
|
|
239
|
+
where=where, include=include, order_by=order_by,
|
|
240
|
+
take=take, skip=skip
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def find_first(self, *, where: BookWhereDict | None = None, include: BookIncludeDict | None = None, order_by: BookOrderByDict | None = None, skip: int | None = None) -> Book | None:
|
|
244
|
+
return self._backend.find_first(
|
|
245
|
+
self,
|
|
246
|
+
where=where, include=include, order_by=order_by,
|
|
247
|
+
skip=skip
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
TUserIncludeCol = Literal['addresses', 'birthday', 'books']
|
|
251
|
+
TUserSortableCol = Literal['id', 'name', 'email', 'last_login']
|
|
252
|
+
|
|
253
|
+
@dataclass(slots=True, kw_only=True)
|
|
254
|
+
class UserInsert:
|
|
255
|
+
id: int | None = None
|
|
256
|
+
name: str
|
|
257
|
+
email: str
|
|
258
|
+
last_login: datetime
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class UserInsertDict(TypedDict, closed=True):
|
|
262
|
+
id: NotRequired[int]
|
|
263
|
+
name: str
|
|
264
|
+
email: str
|
|
265
|
+
last_login: datetime
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class UserWhereDict(TypedDict, total=False, closed=True):
|
|
269
|
+
id: int | None
|
|
270
|
+
name: str | None
|
|
271
|
+
email: str | None
|
|
272
|
+
last_login: datetime | None
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class UserIncludeDict(TypedDict, total=False, closed=True):
|
|
276
|
+
addresses: bool
|
|
277
|
+
birthday: bool
|
|
278
|
+
books: bool
|
|
279
|
+
|
|
280
|
+
class UserOrderByDict(TypedDict, total=False, closed=True):
|
|
281
|
+
id: Literal['asc', 'desc']
|
|
282
|
+
name: Literal['asc', 'desc']
|
|
283
|
+
email: Literal['asc', 'desc']
|
|
284
|
+
last_login: Literal['asc', 'desc']
|
|
285
|
+
|
|
286
|
+
class UserTable:
|
|
287
|
+
model = User
|
|
288
|
+
insert_model = UserInsert
|
|
289
|
+
datasource = DataSourceConfig(provider='sqlite', url='sqlite:///test.db', name=None)
|
|
290
|
+
column_specs: tuple[ColumnSpec, ...] = (
|
|
291
|
+
ColumnSpec(name='id', optional=False, auto_increment=True, has_default=False, has_default_factory=False),
|
|
292
|
+
ColumnSpec(name='name', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
293
|
+
ColumnSpec(name='email', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
294
|
+
ColumnSpec(name='last_login', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
295
|
+
)
|
|
296
|
+
column_specs_by_name: Mapping[str, ColumnSpec] = MappingProxyType({spec.name: spec for spec in column_specs})
|
|
297
|
+
primary_key: tuple[str, ...] = ('id',)
|
|
298
|
+
indexes: tuple[tuple[str, ...], ...] = (('name',), ('name', 'email'), ('last_login',),)
|
|
299
|
+
unique_indexes: tuple[tuple[str, ...], ...] = (('name', 'email'),)
|
|
300
|
+
foreign_keys: tuple[ForeignKeySpec, ...] = ()
|
|
301
|
+
|
|
302
|
+
relations: tuple[RelationSpec, ...] = (
|
|
303
|
+
RelationSpec(name='birthday', table_name='BirthDayTable', table_module=__name__, many=False, mapping=(('id', 'user_id'),), table_factory=lambda: BirthDayTable),
|
|
304
|
+
RelationSpec(name='addresses', table_name='AddressTable', table_module=__name__, many=True, mapping=(('id', 'user_id'),), table_factory=lambda: AddressTable),
|
|
305
|
+
RelationSpec(name='books', table_name='UserBookTable', table_module=__name__, many=True, mapping=(('id', 'user_id'),), table_factory=lambda: UserBookTable),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def __init__(self, backend: BackendProtocol) -> None:
|
|
310
|
+
self._backend = backend
|
|
311
|
+
|
|
312
|
+
def insert(self, data: UserInsert | UserInsertDict) -> User:
|
|
313
|
+
return self._backend.insert(self, data)
|
|
314
|
+
|
|
315
|
+
def insert_many(self, data: Sequence[UserInsert | UserInsertDict], *, batch_size: int | None = None) -> list[User]:
|
|
316
|
+
return self._backend.insert_many(self, data, batch_size=batch_size)
|
|
317
|
+
|
|
318
|
+
def find_many(self, *, where: UserWhereDict | None = None, include: UserIncludeDict | None = None, order_by: UserOrderByDict | None = None, take: int | None = None, skip: int | None = None) -> list[User]:
|
|
319
|
+
return self._backend.find_many(
|
|
320
|
+
self,
|
|
321
|
+
where=where, include=include, order_by=order_by,
|
|
322
|
+
take=take, skip=skip
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def find_first(self, *, where: UserWhereDict | None = None, include: UserIncludeDict | None = None, order_by: UserOrderByDict | None = None, skip: int | None = None) -> User | None:
|
|
326
|
+
return self._backend.find_first(
|
|
327
|
+
self,
|
|
328
|
+
where=where, include=include, order_by=order_by,
|
|
329
|
+
skip=skip
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
TUserBookIncludeCol = Literal['book', 'user']
|
|
333
|
+
TUserBookSortableCol = Literal['user_id', 'book_id', 'created_at']
|
|
334
|
+
|
|
335
|
+
@dataclass(slots=True, kw_only=True)
|
|
336
|
+
class UserBookInsert:
|
|
337
|
+
user_id: int
|
|
338
|
+
book_id: int
|
|
339
|
+
created_at: datetime
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class UserBookInsertDict(TypedDict, closed=True):
|
|
343
|
+
user_id: int
|
|
344
|
+
book_id: int
|
|
345
|
+
created_at: datetime
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class UserBookWhereDict(TypedDict, total=False, closed=True):
|
|
349
|
+
user_id: int | None
|
|
350
|
+
book_id: int | None
|
|
351
|
+
created_at: datetime | None
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class UserBookIncludeDict(TypedDict, total=False, closed=True):
|
|
355
|
+
book: bool
|
|
356
|
+
user: bool
|
|
357
|
+
|
|
358
|
+
class UserBookOrderByDict(TypedDict, total=False, closed=True):
|
|
359
|
+
user_id: Literal['asc', 'desc']
|
|
360
|
+
book_id: Literal['asc', 'desc']
|
|
361
|
+
created_at: Literal['asc', 'desc']
|
|
362
|
+
|
|
363
|
+
class UserBookTable:
|
|
364
|
+
model = UserBook
|
|
365
|
+
insert_model = UserBookInsert
|
|
366
|
+
datasource = DataSourceConfig(provider='sqlite', url='sqlite:///test.db', name=None)
|
|
367
|
+
column_specs: tuple[ColumnSpec, ...] = (
|
|
368
|
+
ColumnSpec(name='user_id', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
369
|
+
ColumnSpec(name='book_id', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
370
|
+
ColumnSpec(name='created_at', optional=False, auto_increment=False, has_default=False, has_default_factory=False),
|
|
371
|
+
)
|
|
372
|
+
column_specs_by_name: Mapping[str, ColumnSpec] = MappingProxyType({spec.name: spec for spec in column_specs})
|
|
373
|
+
primary_key: tuple[str, ...] = ('user_id', 'book_id')
|
|
374
|
+
indexes: tuple[tuple[str, ...], ...] = (('created_at',),)
|
|
375
|
+
unique_indexes: tuple[tuple[str, ...], ...] = ()
|
|
376
|
+
foreign_keys: tuple[ForeignKeySpec, ...] = (
|
|
377
|
+
ForeignKeySpec(
|
|
378
|
+
local_columns=('user_id',),
|
|
379
|
+
remote_model=User,
|
|
380
|
+
remote_columns=('id',),
|
|
381
|
+
backref='books',
|
|
382
|
+
),
|
|
383
|
+
ForeignKeySpec(
|
|
384
|
+
local_columns=('book_id',),
|
|
385
|
+
remote_model=Book,
|
|
386
|
+
remote_columns=('id',),
|
|
387
|
+
backref='users',
|
|
388
|
+
),
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
relations: tuple[RelationSpec, ...] = (
|
|
392
|
+
RelationSpec(name='user', table_name='UserTable', table_module=__name__, many=False, mapping=(('user_id', 'id'),), table_factory=lambda: UserTable),
|
|
393
|
+
RelationSpec(name='book', table_name='BookTable', table_module=__name__, many=False, mapping=(('book_id', 'id'),), table_factory=lambda: BookTable),
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def __init__(self, backend: BackendProtocol) -> None:
|
|
398
|
+
self._backend = backend
|
|
399
|
+
|
|
400
|
+
def insert(self, data: UserBookInsert | UserBookInsertDict) -> UserBook:
|
|
401
|
+
return self._backend.insert(self, data)
|
|
402
|
+
|
|
403
|
+
def insert_many(self, data: Sequence[UserBookInsert | UserBookInsertDict], *, batch_size: int | None = None) -> list[UserBook]:
|
|
404
|
+
return self._backend.insert_many(self, data, batch_size=batch_size)
|
|
405
|
+
|
|
406
|
+
def find_many(self, *, where: UserBookWhereDict | None = None, include: UserBookIncludeDict | None = None, order_by: UserBookOrderByDict | None = None, take: int | None = None, skip: int | None = None) -> list[UserBook]:
|
|
407
|
+
return self._backend.find_many(
|
|
408
|
+
self,
|
|
409
|
+
where=where, include=include, order_by=order_by,
|
|
410
|
+
take=take, skip=skip
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
def find_first(self, *, where: UserBookWhereDict | None = None, include: UserBookIncludeDict | None = None, order_by: UserBookOrderByDict | None = None, skip: int | None = None) -> UserBook | None:
|
|
414
|
+
return self._backend.find_first(
|
|
415
|
+
self,
|
|
416
|
+
where=where, include=include, order_by=order_by,
|
|
417
|
+
skip=skip
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
class Client(BaseDBPool):
|
|
421
|
+
datasources = {
|
|
422
|
+
'sqlite': DataSourceConfig(provider='sqlite', url='sqlite:///test.db', name=None),
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
@classmethod
|
|
426
|
+
@save_local
|
|
427
|
+
def _backend_sqlite(cls) -> BackendProtocol:
|
|
428
|
+
config = cls.datasources['sqlite']
|
|
429
|
+
if config.provider == 'sqlite':
|
|
430
|
+
from dclassql.runtime.backends.sqlite import SQLiteBackend
|
|
431
|
+
conn = open_sqlite_connection(config.url)
|
|
432
|
+
cls._setup_sqlite_db(conn)
|
|
433
|
+
return SQLiteBackend(conn)
|
|
434
|
+
raise ValueError(f"Unsupported provider '{config.provider}' for datasource 'sqlite'")
|
|
435
|
+
|
|
436
|
+
def __init__(self) -> None:
|
|
437
|
+
self.address = AddressTable(self._backend_sqlite())
|
|
438
|
+
self.birth_day = BirthDayTable(self._backend_sqlite())
|
|
439
|
+
self.book = BookTable(self._backend_sqlite())
|
|
440
|
+
self.user = UserTable(self._backend_sqlite())
|
|
441
|
+
self.user_book = UserBookTable(self._backend_sqlite())
|
|
442
|
+
|
|
443
|
+
@classmethod
|
|
444
|
+
def close_all(cls, verbose: bool = False) -> None:
|
|
445
|
+
super().close_all(verbose=verbose)
|
|
446
|
+
if hasattr(cls._local, '_backend_sqlite'):
|
|
447
|
+
backend = getattr(cls._local, '_backend_sqlite')
|
|
448
|
+
if hasattr(backend, 'close') and callable(getattr(backend, 'close')):
|
|
449
|
+
backend.close()
|
|
450
|
+
delattr(cls._local, '_backend_sqlite')
|
|
451
|
+
|
|
452
|
+
__all__ = (
|
|
453
|
+
"DataSourceConfig",
|
|
454
|
+
"ForeignKeySpec",
|
|
455
|
+
"Client",
|
|
456
|
+
"TAddressIncludeCol",
|
|
457
|
+
"TAddressSortableCol",
|
|
458
|
+
"AddressIncludeDict",
|
|
459
|
+
"AddressOrderByDict",
|
|
460
|
+
"AddressInsert",
|
|
461
|
+
"AddressInsertDict",
|
|
462
|
+
"AddressWhereDict",
|
|
463
|
+
"AddressTable",
|
|
464
|
+
"TBirthDayIncludeCol",
|
|
465
|
+
"TBirthDaySortableCol",
|
|
466
|
+
"BirthDayIncludeDict",
|
|
467
|
+
"BirthDayOrderByDict",
|
|
468
|
+
"BirthDayInsert",
|
|
469
|
+
"BirthDayInsertDict",
|
|
470
|
+
"BirthDayWhereDict",
|
|
471
|
+
"BirthDayTable",
|
|
472
|
+
"TBookIncludeCol",
|
|
473
|
+
"TBookSortableCol",
|
|
474
|
+
"BookIncludeDict",
|
|
475
|
+
"BookOrderByDict",
|
|
476
|
+
"BookInsert",
|
|
477
|
+
"BookInsertDict",
|
|
478
|
+
"BookWhereDict",
|
|
479
|
+
"BookTable",
|
|
480
|
+
"TUserIncludeCol",
|
|
481
|
+
"TUserSortableCol",
|
|
482
|
+
"UserIncludeDict",
|
|
483
|
+
"UserOrderByDict",
|
|
484
|
+
"UserInsert",
|
|
485
|
+
"UserInsertDict",
|
|
486
|
+
"UserWhereDict",
|
|
487
|
+
"UserTable",
|
|
488
|
+
"TUserBookIncludeCol",
|
|
489
|
+
"TUserBookSortableCol",
|
|
490
|
+
"UserBookIncludeDict",
|
|
491
|
+
"UserBookOrderByDict",
|
|
492
|
+
"UserBookInsert",
|
|
493
|
+
"UserBookInsertDict",
|
|
494
|
+
"UserBookWhereDict",
|
|
495
|
+
"UserBookTable",
|
|
496
|
+
)
|