apexdevkit 1.17.8__tar.gz → 1.17.10__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 (48) hide show
  1. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/PKG-INFO +1 -1
  2. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/mssql.py +43 -68
  3. apexdevkit-1.17.10/apexdevkit/repository/sql.py +228 -0
  4. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/sqlite.py +50 -68
  5. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/pyproject.toml +1 -1
  6. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/LICENSE +0 -0
  7. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/README.md +0 -0
  8. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/__init__.py +0 -0
  9. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/annotation/__init__.py +0 -0
  10. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/annotation/deprecate.py +0 -0
  11. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/environment.py +0 -0
  12. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/error.py +0 -0
  13. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/__init__.py +0 -0
  14. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/builder.py +0 -0
  15. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/dependable.py +0 -0
  16. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/docs.py +0 -0
  17. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/name.py +0 -0
  18. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/request.py +0 -0
  19. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/resource.py +0 -0
  20. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/response.py +0 -0
  21. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/router.py +0 -0
  22. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/schema.py +0 -0
  23. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fastapi/service.py +0 -0
  24. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/fluent.py +0 -0
  25. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/formatter.py +0 -0
  26. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/__init__.py +0 -0
  27. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/fake.py +0 -0
  28. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/fluent.py +0 -0
  29. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/httpx.py +0 -0
  30. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/json.py +0 -0
  31. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/http/url.py +0 -0
  32. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/key_fn.py +0 -0
  33. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/py.typed +0 -0
  34. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/__init__.py +0 -0
  35. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/base.py +0 -0
  36. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/connector.py +0 -0
  37. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/database.py +0 -0
  38. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/decorator.py +0 -0
  39. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/in_memory.py +0 -0
  40. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/interface.py +0 -0
  41. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/repository/mongo.py +0 -0
  42. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/server.py +0 -0
  43. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/synchronization.py +0 -0
  44. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/testing/__init__.py +0 -0
  45. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/testing/database.py +0 -0
  46. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/testing/fake.py +0 -0
  47. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/testing/rest.py +0 -0
  48. {apexdevkit-1.17.8 → apexdevkit-1.17.10}/apexdevkit/value.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: apexdevkit
3
- Version: 1.17.8
3
+ Version: 1.17.10
4
4
  Summary: Apex Development Tools for python.
5
5
  Author: Apex Dev
6
6
  Author-email: dev@apex.ge
@@ -8,6 +8,7 @@ from pymssql.exceptions import DatabaseError
8
8
  from apexdevkit.error import DoesNotExistError, ExistsError
9
9
  from apexdevkit.formatter import Formatter
10
10
  from apexdevkit.repository import Database, DatabaseCommand, RepositoryBase
11
+ from apexdevkit.repository.sql import NotNone, SqlFieldManager, _SqlField
11
12
 
12
13
  ItemT = TypeVar("ItemT")
13
14
 
@@ -143,7 +144,7 @@ class MsSqlTableBuilder(Generic[ItemT]):
143
144
  schema: str | None = None
144
145
  table: str | None = None
145
146
  formatter: Formatter[dict[str, Any], ItemT] | None = None
146
- fields: list[MsSqlField] | None = None
147
+ fields: list[_SqlField] | None = None
147
148
 
148
149
  def with_username(self, value: str) -> MsSqlTableBuilder[ItemT]:
149
150
  return MsSqlTableBuilder[ItemT](
@@ -183,18 +184,29 @@ class MsSqlTableBuilder(Generic[ItemT]):
183
184
  self.fields,
184
185
  )
185
186
 
186
- def with_fields(self, value: Iterable[MsSqlField]) -> MsSqlTableBuilder[ItemT]:
187
- field_list = list(value)
188
- if len([field for field in field_list if field.is_id]) != 1:
189
- raise ValueError("Pass only one identifier field.")
190
- if len([field for field in field_list if field.is_parent]) > 1:
191
- raise ValueError("Pass only one parent field.")
187
+ def with_fields(self, value: Iterable[_SqlField]) -> MsSqlTableBuilder[ItemT]:
188
+ key_list = list(value)
189
+ if len([key for key in key_list if key.is_id]) != 1:
190
+ raise ValueError("Pass only one identifier key.")
191
+ if len([key for key in key_list if key.is_parent]) > 1:
192
+ raise ValueError("Pass only one parent key.")
193
+ if (
194
+ len(
195
+ [
196
+ key
197
+ for key in key_list
198
+ if not key.is_filter and isinstance(key.fixed_value, NotNone)
199
+ ]
200
+ )
201
+ > 0
202
+ ):
203
+ raise ValueError("Only filter fields can be 'not null'.")
192
204
  return MsSqlTableBuilder[ItemT](
193
205
  self.username,
194
206
  self.schema,
195
207
  self.table,
196
208
  self.formatter,
197
- field_list,
209
+ key_list,
198
210
  )
199
211
 
200
212
  def build(self) -> SqlTable[ItemT]:
@@ -205,7 +217,7 @@ class MsSqlTableBuilder(Generic[ItemT]):
205
217
  self.schema,
206
218
  self.table,
207
219
  self.formatter,
208
- self.fields,
220
+ SqlFieldManager.Builder().with_fields(self.fields).for_mssql().build(),
209
221
  self.username,
210
222
  )
211
223
 
@@ -215,7 +227,7 @@ class DefaultSqlTable(SqlTable[ItemT]):
215
227
  schema: str
216
228
  table: str
217
229
  formatter: Formatter[dict[str, Any], ItemT]
218
- fields: list[MsSqlField]
230
+ fields: SqlFieldManager
219
231
  username: str | None = None
220
232
 
221
233
  def count_all(self) -> DatabaseCommand:
@@ -223,13 +235,17 @@ class DefaultSqlTable(SqlTable[ItemT]):
223
235
  {self._user_check}
224
236
  SELECT count(*) AS n_items
225
237
  FROM [{self.schema}].[{self.table}]
226
- {self._where_statement(include_id=False)}
238
+ {self.fields.where_statement(include_id=False)}
227
239
  REVERT
228
- """).with_data(self._data_with_replaced_parent({}))
240
+ """).with_data(self.fields.with_fixed({}))
229
241
 
230
242
  def insert(self, item: ItemT) -> DatabaseCommand:
231
- columns = ", ".join(["[" + field.name + "]" for field in self.fields])
232
- placeholders = ", ".join([f"%({key.name})s" for key in self.fields])
243
+ columns = ", ".join(
244
+ ["[" + field.name + "]" for field in self.fields if field.include_in_insert]
245
+ )
246
+ placeholders = ", ".join(
247
+ [f"%({key.name})s" for key in self.fields if key.include_in_insert]
248
+ )
233
249
  output = ", ".join(["INSERTED." + field.name for field in self.fields])
234
250
 
235
251
  return DatabaseCommand(f"""
@@ -242,7 +258,7 @@ class DefaultSqlTable(SqlTable[ItemT]):
242
258
  {placeholders}
243
259
  )
244
260
  REVERT
245
- """).with_data(self._data_with_replaced_parent(self.formatter.dump(item)))
261
+ """).with_data(self.fields.with_fixed(self.formatter.dump(item)))
246
262
 
247
263
  def select(self, item_id: str) -> DatabaseCommand:
248
264
  columns = ", ".join(["[" + field.name + "]" for field in self.fields])
@@ -252,9 +268,9 @@ class DefaultSqlTable(SqlTable[ItemT]):
252
268
  SELECT
253
269
  {columns}
254
270
  FROM [{self.schema}].[{self.table}]
255
- {self._where_statement(include_id=True)}
271
+ {self.fields.where_statement(include_id=True)}
256
272
  REVERT
257
- """).with_data(self._data_with_replaced_parent({self._id: item_id}))
273
+ """).with_data(self.fields.with_fixed({self.fields.id: item_id}))
258
274
 
259
275
  def select_all(self) -> DatabaseCommand:
260
276
  columns = ", ".join(["[" + field.name + "]" for field in self.fields])
@@ -264,10 +280,10 @@ class DefaultSqlTable(SqlTable[ItemT]):
264
280
  SELECT
265
281
  {columns}
266
282
  FROM [{self.schema}].[{self.table}]
267
- {self._where_statement(include_id=False)}
268
- {self._order}
283
+ {self.fields.where_statement(include_id=False)}
284
+ {self.fields.order}
269
285
  REVERT
270
- """).with_data(self._data_with_replaced_parent({}))
286
+ """).with_data(self.fields.with_fixed({}))
271
287
 
272
288
  def update(self, item: ItemT) -> DatabaseCommand:
273
289
  updates = ", ".join(
@@ -283,27 +299,27 @@ class DefaultSqlTable(SqlTable[ItemT]):
283
299
  UPDATE [{self.schema}].[{self.table}]
284
300
  SET
285
301
  {updates}
286
- {self._where_statement(include_id=True)}
302
+ {self.fields.where_statement(include_id=True)}
287
303
  REVERT
288
- """).with_data(self._data_with_replaced_parent(self.formatter.dump(item)))
304
+ """).with_data(self.fields.with_fixed(self.formatter.dump(item)))
289
305
 
290
306
  def delete(self, item_id: str) -> DatabaseCommand:
291
307
  return DatabaseCommand(f"""
292
308
  {self._user_check}
293
309
  DELETE
294
310
  FROM [{self.schema}].[{self.table}]
295
- {self._where_statement(include_id=True)}
311
+ {self.fields.where_statement(include_id=True)}
296
312
  REVERT
297
- """).with_data(self._data_with_replaced_parent({self._id: item_id}))
313
+ """).with_data(self.fields.with_fixed({self.fields.id: item_id}))
298
314
 
299
315
  def delete_all(self) -> DatabaseCommand:
300
316
  return DatabaseCommand(f"""
301
317
  {self._user_check}
302
318
  DELETE
303
319
  FROM [{self.schema}].[{self.table}]
304
- {self._where_statement(include_id=False)}
320
+ {self.fields.where_statement(include_id=False)}
305
321
  REVERT
306
- """).with_data(self._data_with_replaced_parent({}))
322
+ """).with_data(self.fields.with_fixed({}))
307
323
 
308
324
  def load(self, data: dict[str, Any]) -> ItemT:
309
325
  return self.formatter.load(data)
@@ -311,53 +327,12 @@ class DefaultSqlTable(SqlTable[ItemT]):
311
327
  def exists(self, duplicate: ItemT) -> ExistsError:
312
328
  raw = self.formatter.dump(duplicate)
313
329
  return ExistsError(duplicate).with_duplicate(
314
- lambda i: f"{self._id}<{raw[self._id]}>"
330
+ lambda i: f"{self.fields.id}<{raw[self.fields.id]}>"
315
331
  )
316
332
 
317
- def _where_statement(self, include_id: bool = False) -> str:
318
- result = next((field for field in self.fields if field.is_parent), None)
319
- if result is None:
320
- return f"WHERE [{self._id}] = %({self._id})s" if include_id else ""
321
- else:
322
- statement = "WHERE [" + result.name + "] = %(" + result.name + ")s"
323
- if include_id:
324
- statement += f" AND [{self._id}] = %({self._id})s"
325
- return statement
326
-
327
- def _data_with_replaced_parent(self, data: dict[str, Any]) -> dict[str, Any]:
328
- result = next((field for field in self.fields if field.is_parent), None)
329
- if result is not None:
330
- data[result.name] = result.fixed_value
331
- return data
332
-
333
- @property
334
- def _id(self) -> str:
335
- result = next((field for field in self.fields if field.is_id), None)
336
- if result is None:
337
- raise ValueError("Id field is required.")
338
- return result.name
339
-
340
333
  @property
341
334
  def _user_check(self) -> str:
342
335
  if self.username is not None:
343
336
  return f"EXECUTE AS USER = '{self.username}'"
344
337
  else:
345
338
  return ""
346
-
347
- @property
348
- def _order(self) -> str:
349
- ordering = [field.name for field in self.fields if field.is_ordered]
350
- if len(ordering) > 0:
351
- return "ORDER BY " + ", ".join(ordering)
352
- else:
353
- return ""
354
-
355
-
356
- @dataclass(frozen=True)
357
- class MsSqlField:
358
- name: str
359
- is_id: bool = False
360
- is_ordered: bool = False
361
-
362
- is_parent: bool = False
363
- fixed_value: Any | None = None
@@ -0,0 +1,228 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Iterator
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class NotNone:
9
+ pass
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class _SqlField:
14
+ name: str
15
+ is_id: bool = False
16
+ is_ordered: bool = False
17
+ is_composite: bool = False
18
+ include_in_insert: bool = True
19
+
20
+ is_parent: bool = False
21
+ parent_value: Any | None = None
22
+
23
+ is_filter: bool = False
24
+ filter_value: Any | None | NotNone = None
25
+
26
+ is_fixed: bool = False
27
+ fixed_value: Any | None = None
28
+
29
+
30
+ @dataclass
31
+ class SqlFieldBuilder:
32
+ _name: str = field(init=False)
33
+ _is_id: bool = False
34
+ _is_ordered: bool = False
35
+ _is_composite: bool = False
36
+ _include_in_insert: bool = True
37
+
38
+ _is_parent: bool = False
39
+ _parent_value: Any | None = None
40
+
41
+ _is_filter: bool = False
42
+ _filter_value: Any | None | NotNone = None
43
+
44
+ _is_fixed: bool = False
45
+ _fixed_value: Any | None = None
46
+
47
+ def with_name(self, value: str) -> SqlFieldBuilder:
48
+ self._name = value
49
+
50
+ return self
51
+
52
+ def as_id(self) -> SqlFieldBuilder:
53
+ self._is_id = True
54
+
55
+ return self
56
+
57
+ def as_composite(self) -> SqlFieldBuilder:
58
+ self._is_composite = True
59
+
60
+ return self
61
+
62
+ def in_ordering(self) -> SqlFieldBuilder:
63
+ self._is_ordered = True
64
+
65
+ return self
66
+
67
+ def derive_on_insert(self) -> SqlFieldBuilder:
68
+ self._include_in_insert = False
69
+
70
+ return self
71
+
72
+ def as_parent(self, value: Any | None) -> SqlFieldBuilder:
73
+ self._is_parent = True
74
+ self._parent_value = value
75
+
76
+ return self
77
+
78
+ def as_filter(self, value: Any | None | NotNone) -> SqlFieldBuilder:
79
+ self._is_filter = True
80
+ self._filter_value = value
81
+
82
+ return self
83
+
84
+ def as_fixed(self, value: Any | None) -> SqlFieldBuilder:
85
+ self._is_fixed = True
86
+ self._fixed_value = value
87
+
88
+ return self
89
+
90
+ def build(self) -> _SqlField:
91
+ return _SqlField(
92
+ self._name,
93
+ self._is_id,
94
+ self._is_ordered,
95
+ self._is_composite,
96
+ self._include_in_insert,
97
+ self._is_parent,
98
+ self._parent_value,
99
+ self._is_filter,
100
+ self._filter_value,
101
+ self._is_fixed,
102
+ self._fixed_value,
103
+ )
104
+
105
+
106
+ @dataclass
107
+ class SqlFieldManager:
108
+ fields: list[_SqlField]
109
+ key_formatter: str
110
+ value_formatter: str
111
+
112
+ @property
113
+ def id(self) -> str:
114
+ result = next((key for key in self.fields if key.is_id), None)
115
+ if result is None:
116
+ raise ValueError("Id field is required.")
117
+ return result.name
118
+
119
+ @property
120
+ def composite(self) -> list[str]:
121
+ names = [key.name for key in self.fields if key.is_composite]
122
+ return [self.id] if len(names) == 0 else names
123
+
124
+ @property
125
+ def order(self) -> str:
126
+ ordering = [key.name for key in self.fields if key.is_ordered]
127
+ if len(ordering) > 0:
128
+ return "ORDER BY " + ", ".join(ordering)
129
+ else:
130
+ return ""
131
+
132
+ def __iter__(self) -> Iterator[_SqlField]:
133
+ yield from self.fields
134
+
135
+ def where_statement(self, include_id: bool = False) -> str:
136
+ statements = [
137
+ statement
138
+ for statement in [
139
+ self._parent_filter(),
140
+ self._id_filter(include_id),
141
+ self._general_filters(),
142
+ ]
143
+ if statement != ""
144
+ ]
145
+ if len(statements) > 0:
146
+ return "WHERE " + " AND ".join(statements)
147
+ else:
148
+ return ""
149
+
150
+ def with_fixed(self, data: dict[str, Any]) -> dict[str, Any]:
151
+ for key in self.fields:
152
+ if key.is_parent:
153
+ data[key.name] = key.parent_value
154
+ if key.is_fixed:
155
+ data[key.name] = key.fixed_value
156
+ return data
157
+
158
+ def _parent_filter(self) -> str:
159
+ result = next((key for key in self.fields if key.is_parent), None)
160
+ if result is not None:
161
+ if result.parent_value is None:
162
+ return self.key_formatter.replace("x", result.name) + " IS NULL"
163
+ else:
164
+ return (
165
+ self.key_formatter.replace("x", result.name)
166
+ + " = "
167
+ + self.value_formatter.replace("x", result.name)
168
+ )
169
+ else:
170
+ return ""
171
+
172
+ def _id_filter(self, include_id: bool = False) -> str:
173
+ return (
174
+ self.key_formatter.replace("x", self.id)
175
+ + " = "
176
+ + self.value_formatter.replace("x", self.id)
177
+ if include_id
178
+ else ""
179
+ )
180
+
181
+ def _general_filters(self) -> str:
182
+ statements: list[str] = []
183
+ for key in self.fields:
184
+ if key.is_filter:
185
+ if key.filter_value is None:
186
+ statements.append(
187
+ self.key_formatter.replace("x", key.name) + " IS NULL"
188
+ )
189
+ elif isinstance(key.filter_value, NotNone):
190
+ statements.append(
191
+ self.key_formatter.replace("x", key.name) + " IS NOT NULL"
192
+ )
193
+ else:
194
+ statements.append(
195
+ self.key_formatter.replace("x", key.name)
196
+ + " = "
197
+ + self.value_formatter.replace("x", key.name)
198
+ )
199
+
200
+ return " AND ".join(statements)
201
+
202
+ @dataclass
203
+ class Builder:
204
+ fields: list[_SqlField] = field(init=False)
205
+ key_formatter: str = field(init=False)
206
+ value_formatter: str = field(init=False)
207
+
208
+ def with_fields(self, fields: list[_SqlField]) -> SqlFieldManager.Builder:
209
+ self.fields = fields
210
+
211
+ return self
212
+
213
+ def for_sqlite(self) -> SqlFieldManager.Builder:
214
+ self.key_formatter = "x"
215
+ self.value_formatter = ":x"
216
+
217
+ return self
218
+
219
+ def for_mssql(self) -> SqlFieldManager.Builder:
220
+ self.key_formatter = "[x]"
221
+ self.value_formatter = "%(x)s"
222
+
223
+ return self
224
+
225
+ def build(self) -> SqlFieldManager:
226
+ return SqlFieldManager(
227
+ self.fields, self.key_formatter, self.value_formatter
228
+ )
@@ -8,6 +8,7 @@ from apexdevkit.error import DoesNotExistError, ExistsError
8
8
  from apexdevkit.formatter import Formatter
9
9
  from apexdevkit.repository import Database, DatabaseCommand, RepositoryBase
10
10
  from apexdevkit.repository.interface import ItemT
11
+ from apexdevkit.repository.sql import NotNone, SqlFieldManager, _SqlField
11
12
 
12
13
 
13
14
  @dataclass(frozen=True)
@@ -102,7 +103,7 @@ class UnknownError(Exception):
102
103
  class SqliteTableBuilder(Generic[ItemT]):
103
104
  table_name: str | None = None
104
105
  formatter: Formatter[dict[str, Any], ItemT] | None = None
105
- fields: list[SqliteField] | None = None
106
+ fields: list[_SqlField] | None = None
106
107
 
107
108
  def with_name(self, value: str) -> SqliteTableBuilder[ItemT]:
108
109
  return SqliteTableBuilder[ItemT](value, self.formatter, self.fields)
@@ -112,16 +113,27 @@ class SqliteTableBuilder(Generic[ItemT]):
112
113
  ) -> SqliteTableBuilder[ItemT]:
113
114
  return SqliteTableBuilder[ItemT](self.table_name, value, self.fields)
114
115
 
115
- def with_fields(self, value: Iterable[SqliteField]) -> SqliteTableBuilder[ItemT]:
116
- field_list = list(value)
117
- if len([field for field in field_list if field.is_id]) != 1:
118
- raise ValueError("Pass only one identifier field.")
119
- if len([field for field in field_list if field.is_parent]) > 1:
120
- raise ValueError("Pass only one parent field.")
116
+ def with_fields(self, value: Iterable[_SqlField]) -> SqliteTableBuilder[ItemT]:
117
+ key_list = list(value)
118
+ if len([key for key in key_list if key.is_id]) != 1:
119
+ raise ValueError("Pass only one identifier key.")
120
+ if len([key for key in key_list if key.is_parent]) > 1:
121
+ raise ValueError("Pass only one parent key.")
122
+ if (
123
+ len(
124
+ [
125
+ key
126
+ for key in key_list
127
+ if not key.is_filter and isinstance(key.fixed_value, NotNone)
128
+ ]
129
+ )
130
+ > 0
131
+ ):
132
+ raise ValueError("Only filter fields can be 'not null'.")
121
133
  return SqliteTableBuilder[ItemT](
122
134
  self.table_name,
123
135
  self.formatter,
124
- field_list,
136
+ key_list,
125
137
  )
126
138
 
127
139
  def build(self) -> SqlTable[ItemT]:
@@ -131,7 +143,7 @@ class SqliteTableBuilder(Generic[ItemT]):
131
143
  return _DefaultSqlTable(
132
144
  self.table_name,
133
145
  self.formatter,
134
- self.fields,
146
+ SqlFieldManager.Builder().with_fields(self.fields).for_sqlite().build(),
135
147
  )
136
148
 
137
149
 
@@ -139,27 +151,32 @@ class SqliteTableBuilder(Generic[ItemT]):
139
151
  class _DefaultSqlTable(SqlTable[ItemT]):
140
152
  table_name: str
141
153
  formatter: Formatter[dict[str, Any], ItemT]
142
- fields: list[SqliteField]
154
+ fields: SqlFieldManager
143
155
 
144
156
  def count_all(self) -> DatabaseCommand:
145
157
  return DatabaseCommand(f"""
146
158
  SELECT count(*) as n_items
147
159
  FROM {self.table_name.upper()}
148
- {self._where_statement(include_id=False)};
149
- """).with_data(self._data_with_replaced_parent({}))
160
+ {self.fields.where_statement(include_id=False)};
161
+ """).with_data(self.fields.with_fixed({}))
150
162
 
151
163
  def insert(self, item: ItemT) -> DatabaseCommand:
152
- columns = ", ".join([field.name for field in self.fields])
153
- placeholders = ", ".join([f":{key.name}" for key in self.fields])
164
+ insert_columns = ", ".join(
165
+ [field.name for field in self.fields if field.include_in_insert]
166
+ )
167
+ return_columns = ", ".join([field.name for field in self.fields])
168
+ placeholders = ", ".join(
169
+ [f":{key.name}" for key in self.fields if key.include_in_insert]
170
+ )
154
171
 
155
172
  return DatabaseCommand(f"""
156
173
  INSERT INTO {self.table_name.upper()} (
157
- {columns}
174
+ {insert_columns}
158
175
  ) VALUES (
159
176
  {placeholders}
160
177
  )
161
- RETURNING {columns};
162
- """).with_data(self._data_with_replaced_parent(self.formatter.dump(item)))
178
+ RETURNING {return_columns};
179
+ """).with_data(self.fields.with_fixed(self.formatter.dump(item)))
163
180
 
164
181
  def select(self, item_id: str) -> DatabaseCommand:
165
182
  columns = ", ".join([field.name for field in self.fields])
@@ -168,21 +185,23 @@ class _DefaultSqlTable(SqlTable[ItemT]):
168
185
  SELECT
169
186
  {columns}
170
187
  FROM {self.table_name.upper()}
171
- {self._where_statement(include_id=True)};
172
- """).with_data(self._data_with_replaced_parent({self._id: item_id}))
188
+ {self.fields.where_statement(include_id=True)};
189
+ """).with_data(self.fields.with_fixed({self.fields.id: item_id}))
173
190
 
174
191
  def select_duplicate(self, item: ItemT) -> DatabaseCommand:
175
192
  raw = self.formatter.dump(item)
176
193
  columns = ", ".join([field.name for field in self.fields])
177
194
 
178
- duplicates = " AND ".join([f"{field} = :{field}" for field in self._composite])
195
+ duplicates = " AND ".join(
196
+ [f"{field} = :{field}" for field in self.fields.composite]
197
+ )
179
198
 
180
199
  return DatabaseCommand(f"""
181
200
  SELECT
182
201
  {columns}
183
202
  FROM {self.table_name.upper()}
184
203
  WHERE {duplicates};
185
- """).with_data({key: raw[key] for key in raw if key in self._composite})
204
+ """).with_data({key: raw[key] for key in raw if key in self.fields.composite})
186
205
 
187
206
  def select_all(self) -> DatabaseCommand:
188
207
  columns = ", ".join([field.name for field in self.fields])
@@ -191,8 +210,9 @@ class _DefaultSqlTable(SqlTable[ItemT]):
191
210
  SELECT
192
211
  {columns}
193
212
  FROM {self.table_name.capitalize()}
194
- {self._where_statement(include_id=False)};
195
- """).with_data(self._data_with_replaced_parent({}))
213
+ {self.fields.where_statement(include_id=False)}
214
+ {self.fields.order}
215
+ """).with_data(self.fields.with_fixed({}))
196
216
 
197
217
  def update(self, item: ItemT) -> DatabaseCommand:
198
218
  updates = ", ".join(
@@ -207,22 +227,22 @@ class _DefaultSqlTable(SqlTable[ItemT]):
207
227
  UPDATE {self.table_name.upper()}
208
228
  SET
209
229
  {updates}
210
- {self._where_statement(include_id=True)};
211
- """).with_data(self._data_with_replaced_parent(self.formatter.dump(item)))
230
+ {self.fields.where_statement(include_id=True)};
231
+ """).with_data(self.fields.with_fixed(self.formatter.dump(item)))
212
232
 
213
233
  def delete(self, item_id: str) -> DatabaseCommand:
214
234
  return DatabaseCommand(f"""
215
235
  DELETE
216
236
  FROM {self.table_name.upper()}
217
- {self._where_statement(include_id=True)};
218
- """).with_data(self._data_with_replaced_parent({self._id: item_id}))
237
+ {self.fields.where_statement(include_id=True)};
238
+ """).with_data(self.fields.with_fixed({self.fields.id: item_id}))
219
239
 
220
240
  def delete_all(self) -> DatabaseCommand:
221
241
  return DatabaseCommand(f"""
222
242
  DELETE
223
243
  FROM {self.table_name.upper()}
224
- {self._where_statement(include_id=False)};
225
- """).with_data(self._data_with_replaced_parent({}))
244
+ {self.fields.where_statement(include_id=False)};
245
+ """).with_data(self.fields.with_fixed({}))
226
246
 
227
247
  def load(self, data: dict[str, Any]) -> ItemT:
228
248
  return self.formatter.load(data)
@@ -231,44 +251,6 @@ class _DefaultSqlTable(SqlTable[ItemT]):
231
251
  raw = self.formatter.dump(item)
232
252
  return ExistsError(item).with_duplicate(
233
253
  lambda i: ",".join(
234
- [f"{key}<{raw[key]}>" for key in raw if key in self._composite]
254
+ [f"{key}<{raw[key]}>" for key in raw if key in self.fields.composite]
235
255
  )
236
256
  )
237
-
238
- def _where_statement(self, include_id: bool = False) -> str:
239
- result = next((field for field in self.fields if field.is_parent), None)
240
- if result is None:
241
- return f"WHERE {self._id} = :{self._id}" if include_id else ""
242
- else:
243
- statement = "WHERE " + result.name + " = :" + result.name
244
- if include_id:
245
- statement += f" AND {self._id} = :{self._id}"
246
- return statement
247
-
248
- def _data_with_replaced_parent(self, data: dict[str, Any]) -> dict[str, Any]:
249
- result = next((field for field in self.fields if field.is_parent), None)
250
- if result is not None:
251
- data[result.name] = result.fixed_value
252
- return data
253
-
254
- @property
255
- def _id(self) -> str:
256
- result = next((field for field in self.fields if field.is_id), None)
257
- if result is None:
258
- raise ValueError("Id field is required.")
259
- return result.name
260
-
261
- @property
262
- def _composite(self) -> list[str]:
263
- names = [field.name for field in self.fields if field.is_composite]
264
- return [self._id] if len(names) == 0 else names
265
-
266
-
267
- @dataclass(frozen=True)
268
- class SqliteField:
269
- name: str
270
- is_id: bool = False
271
- is_composite: bool = False
272
-
273
- is_parent: bool = False
274
- fixed_value: Any | None = None
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "apexdevkit"
3
- version = "1.17.8"
3
+ version = "1.17.10"
4
4
  description = "Apex Development Tools for python."
5
5
  authors = ["Apex Dev <dev@apex.ge>"]
6
6
  readme = "README.md"
File without changes
File without changes