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