apexdevkit 1.17.4__tar.gz → 1.17.6__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.
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/PKG-INFO +1 -1
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/formatter.py +28 -7
- apexdevkit-1.17.6/apexdevkit/repository/mssql.py +467 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/pyproject.toml +1 -1
- apexdevkit-1.17.4/apexdevkit/repository/mssql.py +0 -104
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/LICENSE +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/README.md +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/environment.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/error.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/request.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/fluent.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/http/httpx.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/repository/connector.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/repository/database.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/repository/decorator.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/repository/mongo.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/repository/sqlite.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/server.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/synchronization.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/testing/fake.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/testing/rest.py +0 -0
- {apexdevkit-1.17.4 → apexdevkit-1.17.6}/apexdevkit/value.py +0 -0
|
@@ -2,9 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import pickle
|
|
4
4
|
from copy import deepcopy
|
|
5
|
-
from dataclasses import asdict, dataclass, field
|
|
6
|
-
from typing import Any, Generic, Protocol, Self, TypeVar
|
|
5
|
+
from dataclasses import asdict, dataclass, field, fields, is_dataclass
|
|
6
|
+
from typing import Any, Generic, Protocol, Self, TypeVar, get_args
|
|
7
7
|
|
|
8
|
+
from typing_extensions import get_type_hints
|
|
9
|
+
|
|
10
|
+
from apexdevkit.fluent import FluentDict
|
|
8
11
|
from apexdevkit.value import Value
|
|
9
12
|
|
|
10
13
|
_SourceT = TypeVar("_SourceT")
|
|
@@ -53,11 +56,29 @@ class DataclassFormatter(Generic[_TargetT]):
|
|
|
53
56
|
return self
|
|
54
57
|
|
|
55
58
|
def load(self, raw: dict[str, Any]) -> _TargetT:
|
|
56
|
-
raw = deepcopy(raw)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
raw = FluentDict[Any](deepcopy(raw)).select(
|
|
60
|
+
*self.resource.__annotations__.keys()
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
for key in fields(self.resource): # type: ignore
|
|
64
|
+
types = get_type_hints(self.resource)
|
|
65
|
+
key_type = types[key.name]
|
|
66
|
+
if key.name not in raw:
|
|
67
|
+
continue
|
|
68
|
+
elif key.name in self.sub_formatters.keys():
|
|
69
|
+
raw[key.name] = (
|
|
70
|
+
self.sub_formatters[key.name].load(raw.pop(key.name))
|
|
71
|
+
if raw[key.name]
|
|
72
|
+
else raw[key.name]
|
|
73
|
+
)
|
|
74
|
+
elif is_dataclass(key_type):
|
|
75
|
+
raw[key.name] = DataclassFormatter(key_type).load(raw[key.name]) # type: ignore
|
|
76
|
+
else:
|
|
77
|
+
args = get_args(key_type)
|
|
78
|
+
if len(args) == 1 and is_dataclass(args[0]):
|
|
79
|
+
raw[key.name] = ListFormatter(DataclassFormatter(args[0])).load( # type: ignore
|
|
80
|
+
raw[key.name]
|
|
81
|
+
)
|
|
61
82
|
|
|
62
83
|
return self.resource(**raw)
|
|
63
84
|
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Generic, Iterable, Iterator, TypeVar
|
|
5
|
+
|
|
6
|
+
from pymssql.exceptions import DatabaseError
|
|
7
|
+
|
|
8
|
+
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
9
|
+
from apexdevkit.formatter import Formatter
|
|
10
|
+
from apexdevkit.repository import Database, DatabaseCommand, RepositoryBase
|
|
11
|
+
|
|
12
|
+
ItemT = TypeVar("ItemT")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class MsSqlRepository(RepositoryBase[ItemT]):
|
|
17
|
+
db: Database
|
|
18
|
+
table: SqlTable[ItemT]
|
|
19
|
+
|
|
20
|
+
def __iter__(self) -> Iterator[ItemT]:
|
|
21
|
+
for raw in self.db.execute(self.table.select_all()).fetch_all():
|
|
22
|
+
yield self.table.load(raw)
|
|
23
|
+
|
|
24
|
+
def __len__(self) -> int:
|
|
25
|
+
raw = self.db.execute(self.table.count_all()).fetch_one()
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
return int(raw["n_items"])
|
|
29
|
+
except KeyError:
|
|
30
|
+
raise UnknownError(raw)
|
|
31
|
+
|
|
32
|
+
def delete(self, item_id: str) -> None:
|
|
33
|
+
self.db.execute(self.table.delete(item_id)).fetch_none()
|
|
34
|
+
|
|
35
|
+
def delete_all(self) -> None:
|
|
36
|
+
self.db.execute(self.table.delete_all()).fetch_none()
|
|
37
|
+
|
|
38
|
+
def create(self, item: ItemT) -> ItemT:
|
|
39
|
+
try:
|
|
40
|
+
return self.table.load(self.db.execute(self.table.insert(item)).fetch_one())
|
|
41
|
+
except DatabaseError as e:
|
|
42
|
+
e = MssqlException(e)
|
|
43
|
+
|
|
44
|
+
if e.is_duplication():
|
|
45
|
+
raise self.table.exists(item)
|
|
46
|
+
|
|
47
|
+
raise UnknownError(e.message)
|
|
48
|
+
|
|
49
|
+
def read(self, item_id: str) -> ItemT:
|
|
50
|
+
raw = self.db.execute(self.table.select(item_id)).fetch_one()
|
|
51
|
+
|
|
52
|
+
if not raw:
|
|
53
|
+
raise DoesNotExistError(item_id)
|
|
54
|
+
|
|
55
|
+
return self.table.load(raw)
|
|
56
|
+
|
|
57
|
+
def update(self, item: ItemT) -> None:
|
|
58
|
+
self.db.execute(self.table.update(item)).fetch_none()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SqlTable(Generic[ItemT]): # pragma: no cover
|
|
62
|
+
def count_all(self) -> DatabaseCommand:
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
def insert(self, item: ItemT) -> DatabaseCommand:
|
|
66
|
+
raise NotImplementedError
|
|
67
|
+
|
|
68
|
+
def select(self, item_id: str) -> DatabaseCommand:
|
|
69
|
+
raise NotImplementedError
|
|
70
|
+
|
|
71
|
+
def select_all(self) -> DatabaseCommand:
|
|
72
|
+
raise NotImplementedError
|
|
73
|
+
|
|
74
|
+
def delete(self, item_id: str) -> DatabaseCommand:
|
|
75
|
+
raise NotImplementedError
|
|
76
|
+
|
|
77
|
+
def delete_all(self) -> DatabaseCommand:
|
|
78
|
+
raise NotImplementedError
|
|
79
|
+
|
|
80
|
+
def update(self, item: ItemT) -> DatabaseCommand:
|
|
81
|
+
raise NotImplementedError
|
|
82
|
+
|
|
83
|
+
def load(self, data: dict[str, Any]) -> ItemT:
|
|
84
|
+
raise NotImplementedError
|
|
85
|
+
|
|
86
|
+
def exists(self, duplicate: ItemT) -> ExistsError:
|
|
87
|
+
raise NotImplementedError
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class SqlTableDecorator(Generic[ItemT]):
|
|
92
|
+
table: DefaultSqlTable[ItemT]
|
|
93
|
+
|
|
94
|
+
def count_all(self) -> DatabaseCommand:
|
|
95
|
+
return self.table.count_all()
|
|
96
|
+
|
|
97
|
+
def insert(self, item: ItemT) -> DatabaseCommand:
|
|
98
|
+
return self.table.insert(item)
|
|
99
|
+
|
|
100
|
+
def select(self, item_id: str) -> DatabaseCommand:
|
|
101
|
+
return self.table.select(item_id)
|
|
102
|
+
|
|
103
|
+
def select_all(self) -> DatabaseCommand:
|
|
104
|
+
return self.table.select_all()
|
|
105
|
+
|
|
106
|
+
def delete(self, item_id: str) -> DatabaseCommand:
|
|
107
|
+
return self.table.delete(item_id)
|
|
108
|
+
|
|
109
|
+
def delete_all(self) -> DatabaseCommand:
|
|
110
|
+
return self.table.delete_all()
|
|
111
|
+
|
|
112
|
+
def update(self, item: ItemT) -> DatabaseCommand:
|
|
113
|
+
return self.table.update(item)
|
|
114
|
+
|
|
115
|
+
def load(self, data: dict[str, Any]) -> ItemT:
|
|
116
|
+
return self.table.load(data)
|
|
117
|
+
|
|
118
|
+
def exists(self, duplicate: ItemT) -> ExistsError:
|
|
119
|
+
return self.table.exists(duplicate)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class MssqlException:
|
|
124
|
+
code: int
|
|
125
|
+
message: str
|
|
126
|
+
|
|
127
|
+
def __init__(self, e: DatabaseError):
|
|
128
|
+
self.code = e.args[0]
|
|
129
|
+
self.message = e.args[1].decode()
|
|
130
|
+
|
|
131
|
+
def is_duplication(self) -> bool:
|
|
132
|
+
return self.code in [2627, 70003]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class UnknownError(Exception):
|
|
137
|
+
raw: Any
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass(frozen=True)
|
|
141
|
+
class MsSqlTableBuilder(Generic[ItemT]):
|
|
142
|
+
username: str | None = None
|
|
143
|
+
schema: str | None = None
|
|
144
|
+
table: str | None = None
|
|
145
|
+
formatter: Formatter[dict[str, Any], ItemT] | None = None
|
|
146
|
+
fields: list[MsSqlField] | None = None
|
|
147
|
+
ordering: list[str] | None = None
|
|
148
|
+
parent_field: str | None = None
|
|
149
|
+
parent_value: Any | None = None
|
|
150
|
+
|
|
151
|
+
def with_username(self, value: str) -> MsSqlTableBuilder[ItemT]:
|
|
152
|
+
return MsSqlTableBuilder[ItemT](
|
|
153
|
+
value,
|
|
154
|
+
self.schema,
|
|
155
|
+
self.table,
|
|
156
|
+
self.formatter,
|
|
157
|
+
self.fields,
|
|
158
|
+
self.ordering,
|
|
159
|
+
self.parent_field,
|
|
160
|
+
self.parent_value,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def with_schema(self, value: str) -> MsSqlTableBuilder[ItemT]:
|
|
164
|
+
return MsSqlTableBuilder[ItemT](
|
|
165
|
+
self.username,
|
|
166
|
+
value,
|
|
167
|
+
self.table,
|
|
168
|
+
self.formatter,
|
|
169
|
+
self.fields,
|
|
170
|
+
self.ordering,
|
|
171
|
+
self.parent_field,
|
|
172
|
+
self.parent_value,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def with_table(self, value: str) -> MsSqlTableBuilder[ItemT]:
|
|
176
|
+
return MsSqlTableBuilder[ItemT](
|
|
177
|
+
self.username,
|
|
178
|
+
self.schema,
|
|
179
|
+
value,
|
|
180
|
+
self.formatter,
|
|
181
|
+
self.fields,
|
|
182
|
+
self.ordering,
|
|
183
|
+
self.parent_field,
|
|
184
|
+
self.parent_value,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def with_formatter(
|
|
188
|
+
self, value: Formatter[dict[str, Any], ItemT]
|
|
189
|
+
) -> MsSqlTableBuilder[ItemT]:
|
|
190
|
+
return MsSqlTableBuilder[ItemT](
|
|
191
|
+
self.username,
|
|
192
|
+
self.schema,
|
|
193
|
+
self.table,
|
|
194
|
+
value,
|
|
195
|
+
self.fields,
|
|
196
|
+
self.ordering,
|
|
197
|
+
self.parent_field,
|
|
198
|
+
self.parent_value,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def with_fields(self, fields: Iterable[str]) -> MsSqlTableBuilder[ItemT]:
|
|
202
|
+
return MsSqlTableBuilder[ItemT](
|
|
203
|
+
self.username,
|
|
204
|
+
self.schema,
|
|
205
|
+
self.table,
|
|
206
|
+
self.formatter,
|
|
207
|
+
[MsSqlField(field, field == "id") for field in list(fields)],
|
|
208
|
+
self.ordering,
|
|
209
|
+
self.parent_field,
|
|
210
|
+
self.parent_value,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def with_order_fields(self, ordering: Iterable[str]) -> MsSqlTableBuilder[ItemT]:
|
|
214
|
+
ordered = list(ordering)
|
|
215
|
+
assert self.fields is not None, "Set fields first."
|
|
216
|
+
|
|
217
|
+
for key in ordered:
|
|
218
|
+
if key not in [field.name for field in self.fields]:
|
|
219
|
+
raise ValueError("Missing fields in the table.")
|
|
220
|
+
return MsSqlTableBuilder[ItemT](
|
|
221
|
+
self.username,
|
|
222
|
+
self.schema,
|
|
223
|
+
self.table,
|
|
224
|
+
self.formatter,
|
|
225
|
+
self.fields,
|
|
226
|
+
ordered,
|
|
227
|
+
self.parent_field,
|
|
228
|
+
self.parent_value,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def with_id(self, identifier: str) -> MsSqlTableBuilder[ItemT]:
|
|
232
|
+
assert self.fields is not None, "Set fields first."
|
|
233
|
+
if identifier not in [field.name for field in self.fields]:
|
|
234
|
+
raise ValueError("Missing fields in the table.")
|
|
235
|
+
|
|
236
|
+
return MsSqlTableBuilder[ItemT](
|
|
237
|
+
self.username,
|
|
238
|
+
self.schema,
|
|
239
|
+
self.table,
|
|
240
|
+
self.formatter,
|
|
241
|
+
[MsSqlField(field.name, field.name == identifier) for field in self.fields],
|
|
242
|
+
self.ordering,
|
|
243
|
+
self.parent_field,
|
|
244
|
+
self.parent_value,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def with_parent(
|
|
248
|
+
self, parent_field: str, parent_value: Any
|
|
249
|
+
) -> MsSqlTableBuilder[ItemT]:
|
|
250
|
+
assert self.fields is not None, "Set fields first."
|
|
251
|
+
if parent_field not in [field.name for field in self.fields]:
|
|
252
|
+
raise ValueError("Missing fields in the table.")
|
|
253
|
+
|
|
254
|
+
return MsSqlTableBuilder[ItemT](
|
|
255
|
+
self.username,
|
|
256
|
+
self.schema,
|
|
257
|
+
self.table,
|
|
258
|
+
self.formatter,
|
|
259
|
+
self.fields,
|
|
260
|
+
self.ordering,
|
|
261
|
+
parent_field,
|
|
262
|
+
parent_value,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
def build(self) -> SqlTable[ItemT]:
|
|
266
|
+
if not self.schema or not self.table or not self.formatter or not self.fields:
|
|
267
|
+
raise ValueError("Cannot build sql table.")
|
|
268
|
+
|
|
269
|
+
return DefaultSqlTable(
|
|
270
|
+
self.username,
|
|
271
|
+
self.schema,
|
|
272
|
+
self.table,
|
|
273
|
+
self.formatter,
|
|
274
|
+
self.fields,
|
|
275
|
+
self.ordering,
|
|
276
|
+
self.parent_field,
|
|
277
|
+
self.parent_value,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@dataclass(frozen=True)
|
|
282
|
+
class DefaultSqlTable(SqlTable[ItemT]):
|
|
283
|
+
username: str | None
|
|
284
|
+
schema: str
|
|
285
|
+
table: str
|
|
286
|
+
formatter: Formatter[dict[str, Any], ItemT]
|
|
287
|
+
fields: list[MsSqlField]
|
|
288
|
+
ordering: list[str] | None
|
|
289
|
+
parent_key: str | None
|
|
290
|
+
parent_value: Any | None
|
|
291
|
+
|
|
292
|
+
def count_all(self) -> DatabaseCommand:
|
|
293
|
+
where_statement = ""
|
|
294
|
+
if self.parent_key is not None:
|
|
295
|
+
where_statement += (
|
|
296
|
+
"WHERE [" + self.parent_key + "] = " + self._parent_value_sql
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
return DatabaseCommand(f"""
|
|
300
|
+
{self._user_check}
|
|
301
|
+
SELECT count(*) AS n_items
|
|
302
|
+
FROM [{self.schema}].[{self.table}]
|
|
303
|
+
{where_statement}
|
|
304
|
+
REVERT
|
|
305
|
+
""")
|
|
306
|
+
|
|
307
|
+
def insert(self, item: ItemT) -> DatabaseCommand:
|
|
308
|
+
dumped = self.formatter.dump(item)
|
|
309
|
+
if self.parent_key is not None:
|
|
310
|
+
dumped[self.parent_key] = self.parent_value
|
|
311
|
+
|
|
312
|
+
columns = ", ".join(["[" + field.name + "]" for field in self.fields])
|
|
313
|
+
placeholders = ", ".join([f"%({key.name})s" for key in self.fields])
|
|
314
|
+
output = ", ".join(["INSERTED." + field.name for field in self.fields])
|
|
315
|
+
|
|
316
|
+
return DatabaseCommand(f"""
|
|
317
|
+
{self._user_check}
|
|
318
|
+
INSERT INTO [{self.schema}].[{self.table}] (
|
|
319
|
+
{columns}
|
|
320
|
+
) OUTPUT
|
|
321
|
+
{output}
|
|
322
|
+
VALUES (
|
|
323
|
+
{placeholders}
|
|
324
|
+
)
|
|
325
|
+
REVERT
|
|
326
|
+
""").with_data(dumped)
|
|
327
|
+
|
|
328
|
+
def select(self, item_id: str) -> DatabaseCommand:
|
|
329
|
+
raw: dict[str, Any] = {self._id: item_id}
|
|
330
|
+
where_statement = f"WHERE [{self._id}] = %({self._id})s"
|
|
331
|
+
if self.parent_key is not None:
|
|
332
|
+
raw[self.parent_key] = self.parent_value
|
|
333
|
+
where_statement += (
|
|
334
|
+
", [" + self.parent_key + "] = %(" + self.parent_key + ")s"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
columns = ", ".join(["[" + field.name + "]" for field in self.fields])
|
|
338
|
+
|
|
339
|
+
return DatabaseCommand(f"""
|
|
340
|
+
{self._user_check}
|
|
341
|
+
SELECT
|
|
342
|
+
{columns}
|
|
343
|
+
FROM [{self.schema}].[{self.table}]
|
|
344
|
+
{where_statement}
|
|
345
|
+
REVERT
|
|
346
|
+
""").with_data(raw)
|
|
347
|
+
|
|
348
|
+
def select_all(self) -> DatabaseCommand:
|
|
349
|
+
columns = ", ".join(["[" + field.name + "]" for field in self.fields])
|
|
350
|
+
where_statement = ""
|
|
351
|
+
if self.parent_key is not None:
|
|
352
|
+
where_statement += (
|
|
353
|
+
"WHERE [" + self.parent_key + "] = " + self._parent_value_sql
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
return DatabaseCommand(f"""
|
|
357
|
+
{self._user_check}
|
|
358
|
+
SELECT
|
|
359
|
+
{columns}
|
|
360
|
+
FROM [{self.schema}].[{self.table}]
|
|
361
|
+
{where_statement}
|
|
362
|
+
{self._order}
|
|
363
|
+
REVERT
|
|
364
|
+
""")
|
|
365
|
+
|
|
366
|
+
def update(self, item: ItemT) -> DatabaseCommand:
|
|
367
|
+
dumped = self.formatter.dump(item)
|
|
368
|
+
if self.parent_key is not None:
|
|
369
|
+
dumped[self.parent_key] = self.parent_value
|
|
370
|
+
|
|
371
|
+
where_statement = f"WHERE [{self._id}] = %({self._id})s"
|
|
372
|
+
if self.parent_key is not None:
|
|
373
|
+
where_statement += (
|
|
374
|
+
", [" + self.parent_key + "] = %(" + self.parent_key + ")s"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
updates = ", ".join(
|
|
378
|
+
[
|
|
379
|
+
f"{field.name} = %({field.name})s"
|
|
380
|
+
for field in self.fields
|
|
381
|
+
if not field.is_id and field.name != self.parent_key
|
|
382
|
+
]
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
return DatabaseCommand(f"""
|
|
386
|
+
{self._user_check}
|
|
387
|
+
UPDATE [{self.schema}].[{self.table}]
|
|
388
|
+
SET
|
|
389
|
+
{updates}
|
|
390
|
+
{where_statement}
|
|
391
|
+
REVERT
|
|
392
|
+
""").with_data(dumped)
|
|
393
|
+
|
|
394
|
+
def delete(self, item_id: str) -> DatabaseCommand:
|
|
395
|
+
raw: dict[str, Any] = {self._id: item_id}
|
|
396
|
+
where_statement = f"WHERE [{self._id}] = %({self._id})s"
|
|
397
|
+
if self.parent_key is not None:
|
|
398
|
+
raw[self.parent_key] = self.parent_value
|
|
399
|
+
where_statement += (
|
|
400
|
+
", [" + self.parent_key + "] = %(" + self.parent_key + ")s"
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
return DatabaseCommand(f"""
|
|
404
|
+
{self._user_check}
|
|
405
|
+
DELETE
|
|
406
|
+
FROM [{self.schema}].[{self.table}]
|
|
407
|
+
{where_statement}
|
|
408
|
+
REVERT
|
|
409
|
+
""").with_data(raw)
|
|
410
|
+
|
|
411
|
+
def delete_all(self) -> DatabaseCommand:
|
|
412
|
+
where_statement = ""
|
|
413
|
+
if self.parent_key is not None:
|
|
414
|
+
where_statement += (
|
|
415
|
+
"WHERE [" + self.parent_key + "] = " + self._parent_value_sql
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
return DatabaseCommand(f"""
|
|
419
|
+
{self._user_check}
|
|
420
|
+
DELETE
|
|
421
|
+
FROM [{self.schema}].[{self.table}]
|
|
422
|
+
{where_statement}
|
|
423
|
+
REVERT
|
|
424
|
+
""")
|
|
425
|
+
|
|
426
|
+
def load(self, data: dict[str, Any]) -> ItemT:
|
|
427
|
+
return self.formatter.load(data)
|
|
428
|
+
|
|
429
|
+
def exists(self, duplicate: ItemT) -> ExistsError:
|
|
430
|
+
raw = self.formatter.dump(duplicate)
|
|
431
|
+
return ExistsError(duplicate).with_duplicate(
|
|
432
|
+
lambda i: f"{self._id}<{raw[self._id]}>"
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def _id(self) -> str:
|
|
437
|
+
result = next((field for field in self.fields if field.is_id), None)
|
|
438
|
+
if result is None:
|
|
439
|
+
raise ValueError("Id field is required.")
|
|
440
|
+
return result.name
|
|
441
|
+
|
|
442
|
+
@property
|
|
443
|
+
def _user_check(self) -> str:
|
|
444
|
+
if self.username is not None:
|
|
445
|
+
return f"EXECUTE AS USER = '{self.username}'"
|
|
446
|
+
else:
|
|
447
|
+
return ""
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def _order(self) -> str:
|
|
451
|
+
if self.ordering is not None and len(self.ordering) > 0:
|
|
452
|
+
return "ORDER BY " + ", ".join(self.ordering)
|
|
453
|
+
else:
|
|
454
|
+
return ""
|
|
455
|
+
|
|
456
|
+
@property
|
|
457
|
+
def _parent_value_sql(self) -> str:
|
|
458
|
+
if isinstance(self.parent_value, str):
|
|
459
|
+
return "'" + self.parent_value + "'"
|
|
460
|
+
else:
|
|
461
|
+
return str(self.parent_value)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@dataclass(frozen=True)
|
|
465
|
+
class MsSqlField:
|
|
466
|
+
name: str
|
|
467
|
+
is_id: bool
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import Any, Generic, Iterator, TypeVar
|
|
5
|
-
|
|
6
|
-
from pymssql.exceptions import DatabaseError
|
|
7
|
-
|
|
8
|
-
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
9
|
-
from apexdevkit.repository import Database, DatabaseCommand, RepositoryBase
|
|
10
|
-
|
|
11
|
-
ItemT = TypeVar("ItemT")
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@dataclass
|
|
15
|
-
class MsSqlRepository(RepositoryBase[ItemT]):
|
|
16
|
-
db: Database
|
|
17
|
-
table: SqlTable[ItemT]
|
|
18
|
-
|
|
19
|
-
def __iter__(self) -> Iterator[ItemT]:
|
|
20
|
-
for raw in self.db.execute(self.table.select_all()).fetch_all():
|
|
21
|
-
yield self.table.load(raw)
|
|
22
|
-
|
|
23
|
-
def __len__(self) -> int:
|
|
24
|
-
raw = self.db.execute(self.table.count_all()).fetch_one()
|
|
25
|
-
|
|
26
|
-
try:
|
|
27
|
-
return int(raw["n_items"])
|
|
28
|
-
except KeyError:
|
|
29
|
-
raise UnknownError(raw)
|
|
30
|
-
|
|
31
|
-
def delete(self, item_id: str) -> None:
|
|
32
|
-
self.db.execute(self.table.delete(item_id)).fetch_none()
|
|
33
|
-
|
|
34
|
-
def delete_all(self) -> None:
|
|
35
|
-
self.db.execute(self.table.delete_all()).fetch_none()
|
|
36
|
-
|
|
37
|
-
def create(self, item: ItemT) -> ItemT:
|
|
38
|
-
try:
|
|
39
|
-
return self.table.load(self.db.execute(self.table.insert(item)).fetch_one())
|
|
40
|
-
except DatabaseError as e:
|
|
41
|
-
e = MssqlException(e)
|
|
42
|
-
|
|
43
|
-
if e.is_duplication():
|
|
44
|
-
raise self.table.exists(item)
|
|
45
|
-
|
|
46
|
-
raise UnknownError(e.message)
|
|
47
|
-
|
|
48
|
-
def read(self, item_id: str) -> ItemT:
|
|
49
|
-
raw = self.db.execute(self.table.select(item_id)).fetch_one()
|
|
50
|
-
|
|
51
|
-
if not raw:
|
|
52
|
-
raise DoesNotExistError(item_id)
|
|
53
|
-
|
|
54
|
-
return self.table.load(raw)
|
|
55
|
-
|
|
56
|
-
def update(self, item: ItemT) -> None:
|
|
57
|
-
self.db.execute(self.table.update(item)).fetch_none()
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class SqlTable(Generic[ItemT]): # pragma: no cover
|
|
61
|
-
def count_all(self) -> DatabaseCommand:
|
|
62
|
-
raise NotImplementedError
|
|
63
|
-
|
|
64
|
-
def insert(self, item: ItemT) -> DatabaseCommand:
|
|
65
|
-
raise NotImplementedError
|
|
66
|
-
|
|
67
|
-
def select(self, item_id: str) -> DatabaseCommand:
|
|
68
|
-
raise NotImplementedError
|
|
69
|
-
|
|
70
|
-
def select_all(self) -> DatabaseCommand:
|
|
71
|
-
raise NotImplementedError
|
|
72
|
-
|
|
73
|
-
def delete(self, item_id: str) -> DatabaseCommand:
|
|
74
|
-
raise NotImplementedError
|
|
75
|
-
|
|
76
|
-
def delete_all(self) -> DatabaseCommand:
|
|
77
|
-
raise NotImplementedError
|
|
78
|
-
|
|
79
|
-
def update(self, item: ItemT) -> DatabaseCommand:
|
|
80
|
-
raise NotImplementedError
|
|
81
|
-
|
|
82
|
-
def load(self, data: dict[str, Any]) -> ItemT:
|
|
83
|
-
raise NotImplementedError
|
|
84
|
-
|
|
85
|
-
def exists(self, duplicate: ItemT) -> ExistsError:
|
|
86
|
-
raise NotImplementedError
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@dataclass
|
|
90
|
-
class MssqlException:
|
|
91
|
-
code: int
|
|
92
|
-
message: str
|
|
93
|
-
|
|
94
|
-
def __init__(self, e: DatabaseError):
|
|
95
|
-
self.code = e.args[0]
|
|
96
|
-
self.message = e.args[1].decode()
|
|
97
|
-
|
|
98
|
-
def is_duplication(self) -> bool:
|
|
99
|
-
return self.code in [2627, 70003]
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@dataclass
|
|
103
|
-
class UnknownError(Exception):
|
|
104
|
-
raw: Any
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|