apexdevkit 1.18.6__tar.gz → 1.18.8__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.18.6 → apexdevkit-1.18.8}/PKG-INFO +1 -1
- apexdevkit-1.18.8/apexdevkit/id.py +54 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/query/generator.py +39 -37
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/pyproject.toml +1 -1
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/LICENSE +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/README.md +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/environment.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/error.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/request.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/fluent.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/formatter.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/http/httpx.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/query/__init__.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/query/query.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/connector.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/database.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/decorator.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/mongo.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/mssql.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/sql.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/repository/sqlite.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/server.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/synchronization.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/testing/fake.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/testing/rest.py +0 -0
- {apexdevkit-1.18.6 → apexdevkit-1.18.8}/apexdevkit/value.py +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from threading import Lock
|
|
3
|
+
from time import time
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ApexID:
|
|
7
|
+
last_timestamp: int = -1
|
|
8
|
+
sequence: int = 0
|
|
9
|
+
lock: Lock = Lock()
|
|
10
|
+
|
|
11
|
+
_metadata_bitmask: int = 0x0FFF
|
|
12
|
+
_sequence_bitmask: int = 0x03FF
|
|
13
|
+
_timestamp_bitmask: int = 0x03FFFFFFFFFF
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def id(cls) -> int:
|
|
17
|
+
"""
|
|
18
|
+
Generates a unique identifier with the following structure
|
|
19
|
+
from most significant to least significant:
|
|
20
|
+
1 bit reserved
|
|
21
|
+
41 bits for unix timestamp (millisecond precision)
|
|
22
|
+
12 bits for metadata
|
|
23
|
+
10 bits for sequence
|
|
24
|
+
"""
|
|
25
|
+
with cls.lock:
|
|
26
|
+
current_timestamp = cls._get_timestamp()
|
|
27
|
+
if cls.last_timestamp == current_timestamp:
|
|
28
|
+
cls.sequence += 1
|
|
29
|
+
else:
|
|
30
|
+
cls.sequence = 0
|
|
31
|
+
|
|
32
|
+
if cls.sequence > cls._sequence_bitmask:
|
|
33
|
+
raise DuplicateIDException("Duplicate ID Generated")
|
|
34
|
+
|
|
35
|
+
cls.last_timestamp = current_timestamp
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
(0 << 63)
|
|
39
|
+
| ((cls.last_timestamp & cls._timestamp_bitmask) << 22)
|
|
40
|
+
| ((cls._metadata() & cls._metadata_bitmask) << 10)
|
|
41
|
+
| (cls.sequence & cls._sequence_bitmask)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def _get_timestamp(cls) -> int:
|
|
46
|
+
return int(time() * 1000)
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def _metadata(cls) -> int:
|
|
50
|
+
return int(os.getenv("APEX_ID_METADATA", "0"))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DuplicateIDException(Exception):
|
|
54
|
+
pass
|
|
@@ -2,11 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections import defaultdict
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from functools import cached_property
|
|
6
5
|
from typing import Any, ClassVar, Generic, Iterable, Protocol, TypeVar
|
|
7
6
|
|
|
7
|
+
from apexdevkit.annotation import deprecated
|
|
8
8
|
from apexdevkit.error import ForbiddenError
|
|
9
9
|
from apexdevkit.query.query import (
|
|
10
|
+
Aggregation,
|
|
10
11
|
AggregationOption,
|
|
11
12
|
Filter,
|
|
12
13
|
FooterOptions,
|
|
@@ -149,11 +150,7 @@ class MsSqlQuery:
|
|
|
149
150
|
class MsSqlQueryBuilder:
|
|
150
151
|
source: str = field(init=False)
|
|
151
152
|
username: str = field(init=False)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
@cached_property
|
|
155
|
-
def _fields(self) -> list[MsSqlField]:
|
|
156
|
-
return [MsSqlField(name, alias) for alias, name in self.translations.items()]
|
|
153
|
+
_fields: list[MsSqlField] = field(init=False)
|
|
157
154
|
|
|
158
155
|
def with_source(self, value: str) -> MsSqlQueryBuilder:
|
|
159
156
|
self.source = value
|
|
@@ -165,17 +162,23 @@ class MsSqlQueryBuilder:
|
|
|
165
162
|
|
|
166
163
|
return self
|
|
167
164
|
|
|
165
|
+
def with_fields(self, values: list[MsSqlField]) -> MsSqlQueryBuilder:
|
|
166
|
+
self._fields = values
|
|
167
|
+
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
@deprecated(".with_translations is deprecated, use .with_fields instead")
|
|
168
171
|
def with_translations(self, value: dict[str, str]) -> MsSqlQueryBuilder:
|
|
169
|
-
self.
|
|
172
|
+
self._fields = [MsSqlField(name, alias) for alias, name in value.items()]
|
|
170
173
|
|
|
171
174
|
return self
|
|
172
175
|
|
|
173
176
|
def aggregate(self, footer: FooterOptions) -> DatabaseCommand:
|
|
174
177
|
return MsSqlQuery(
|
|
175
178
|
user=MsSqlUserGenerator(self.username),
|
|
176
|
-
selection=MsSqlFooterGenerator(footer.aggregations, self.
|
|
179
|
+
selection=MsSqlFooterGenerator(footer.aggregations, self._fields),
|
|
177
180
|
filter=MsSqlSourceGenerator(self.source, footer.filter),
|
|
178
|
-
condition=MsSqlConditionGenerator(footer.condition, self.
|
|
181
|
+
condition=MsSqlConditionGenerator(footer.condition, self._fields),
|
|
179
182
|
).generate()
|
|
180
183
|
|
|
181
184
|
def filter(self, options: QueryOptions) -> DatabaseCommand:
|
|
@@ -183,7 +186,7 @@ class MsSqlQueryBuilder:
|
|
|
183
186
|
user=MsSqlUserGenerator(self.username),
|
|
184
187
|
selection=MsSqlSelectionGenerator(self._fields),
|
|
185
188
|
filter=MsSqlSourceGenerator(self.source, options.filter),
|
|
186
|
-
condition=MsSqlConditionGenerator(options.condition, self.
|
|
189
|
+
condition=MsSqlConditionGenerator(options.condition, self._fields),
|
|
187
190
|
ordering=MsSqlOrderGenerator(options.ordering, self._fields),
|
|
188
191
|
paging=MsSqlPagingGenerator(options.paging),
|
|
189
192
|
).generate()
|
|
@@ -219,6 +222,9 @@ class MsSqlField:
|
|
|
219
222
|
|
|
220
223
|
return result
|
|
221
224
|
|
|
225
|
+
def as_aggregation_part(self, option: Aggregation) -> str:
|
|
226
|
+
return f"{option.value}({self.name}) AS {self.alias}_{option.value.lower()}"
|
|
227
|
+
|
|
222
228
|
|
|
223
229
|
@dataclass
|
|
224
230
|
class MsSqlSelectionGenerator:
|
|
@@ -233,27 +239,27 @@ class MsSqlSelectionGenerator:
|
|
|
233
239
|
@dataclass
|
|
234
240
|
class MsSqlFooterGenerator:
|
|
235
241
|
aggregations: list[AggregationOption]
|
|
236
|
-
|
|
242
|
+
fields: list[MsSqlField]
|
|
237
243
|
|
|
238
244
|
def generate(self) -> str:
|
|
239
|
-
self._validate()
|
|
240
245
|
fields = ", ".join(
|
|
241
246
|
[
|
|
242
|
-
|
|
243
|
-
f"({self.translations[footer.name] if footer.name else '*'}) AS "
|
|
244
|
-
f"{footer.name if footer.name else 'general'}"
|
|
245
|
-
f"_{footer.aggregation.value.lower()}"
|
|
247
|
+
self.field_for(footer.name).as_aggregation_part(footer.aggregation)
|
|
246
248
|
for footer in self.aggregations
|
|
247
249
|
]
|
|
248
250
|
)
|
|
249
251
|
|
|
250
252
|
return f"SELECT {fields}"
|
|
251
253
|
|
|
252
|
-
def
|
|
253
|
-
if
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
def field_for(self, name: str | None) -> MsSqlField:
|
|
255
|
+
if name is None:
|
|
256
|
+
return MsSqlField("*", alias="general")
|
|
257
|
+
|
|
258
|
+
for f in self.fields:
|
|
259
|
+
if f.alias == name:
|
|
260
|
+
return f
|
|
261
|
+
|
|
262
|
+
raise ForbiddenError(message=f"Invalid field name: {name}")
|
|
257
263
|
|
|
258
264
|
|
|
259
265
|
@dataclass
|
|
@@ -280,12 +286,11 @@ class MsSqlSourceGenerator:
|
|
|
280
286
|
@dataclass
|
|
281
287
|
class MsSqlConditionGenerator:
|
|
282
288
|
condition: Operator | None
|
|
283
|
-
|
|
289
|
+
fields: list[MsSqlField]
|
|
284
290
|
|
|
285
291
|
def generate(self) -> str:
|
|
286
292
|
if self.condition is None:
|
|
287
293
|
return ""
|
|
288
|
-
self._validate(self.condition)
|
|
289
294
|
|
|
290
295
|
return f"WHERE ({self._traverse(self.condition)})"
|
|
291
296
|
|
|
@@ -306,22 +311,12 @@ class MsSqlConditionGenerator:
|
|
|
306
311
|
assert len(node.operands) == 1
|
|
307
312
|
|
|
308
313
|
return OperationEvaluator(
|
|
309
|
-
node.operation,
|
|
314
|
+
node.operation,
|
|
315
|
+
fields=self.fields,
|
|
310
316
|
).evaluate_for(
|
|
311
317
|
node.operands[0], # type: ignore
|
|
312
318
|
)
|
|
313
319
|
|
|
314
|
-
def _validate(self, condition: Operator | None) -> None:
|
|
315
|
-
if condition is not None and self.translations:
|
|
316
|
-
for operand in condition.operands:
|
|
317
|
-
if isinstance(operand, Operator):
|
|
318
|
-
self._validate(operand)
|
|
319
|
-
else:
|
|
320
|
-
if operand.name not in self.translations.keys():
|
|
321
|
-
raise ForbiddenError(
|
|
322
|
-
message=f"Invalid field name: {operand.name}"
|
|
323
|
-
)
|
|
324
|
-
|
|
325
320
|
|
|
326
321
|
@dataclass
|
|
327
322
|
class MsSqlOrderGenerator:
|
|
@@ -365,7 +360,7 @@ class MsSqlPagingGenerator:
|
|
|
365
360
|
@dataclass
|
|
366
361
|
class OperationEvaluator:
|
|
367
362
|
operation: Operation
|
|
368
|
-
|
|
363
|
+
fields: list[MsSqlField]
|
|
369
364
|
|
|
370
365
|
_TEMPLATES: ClassVar[dict[Operation, str]] = defaultdict(
|
|
371
366
|
lambda: "[{column}] {operation} {a}",
|
|
@@ -385,13 +380,20 @@ class OperationEvaluator:
|
|
|
385
380
|
def evaluate_for(self, node: Leaf) -> str:
|
|
386
381
|
return self._TEMPLATES[self.operation].format(
|
|
387
382
|
operation=self.operation.value,
|
|
388
|
-
column=self.
|
|
383
|
+
column=self._column_for(node),
|
|
389
384
|
raw_a=self._get_raw_value(node),
|
|
390
385
|
a=self._get_value(node, 0),
|
|
391
386
|
b=self._get_value(node, 1),
|
|
392
387
|
values=", ".join(val.eval() for val in node.values),
|
|
393
388
|
)
|
|
394
389
|
|
|
390
|
+
def _column_for(self, node: Leaf) -> str:
|
|
391
|
+
for f in self.fields:
|
|
392
|
+
if f.alias == node.name:
|
|
393
|
+
return f.name
|
|
394
|
+
|
|
395
|
+
raise ForbiddenError(message=f"Invalid field name: {node.name}")
|
|
396
|
+
|
|
395
397
|
def _get_raw_value(self, node: Leaf) -> str | None:
|
|
396
398
|
raw_a = self._get_value(node, 0)
|
|
397
399
|
return (
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|