apexdevkit 1.18.5__tar.gz → 1.18.7__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.5 → apexdevkit-1.18.7}/PKG-INFO +1 -1
- apexdevkit-1.18.7/apexdevkit/id.py +54 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/query/generator.py +54 -33
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/pyproject.toml +3 -4
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/LICENSE +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/README.md +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/environment.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/error.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/request.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/fluent.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/formatter.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/http/httpx.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/query/__init__.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/query/query.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/connector.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/database.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/decorator.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/mongo.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/mssql.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/sql.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/repository/sqlite.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/server.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/synchronization.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/testing/fake.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/apexdevkit/testing/rest.py +0 -0
- {apexdevkit-1.18.5 → apexdevkit-1.18.7}/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,6 +2,7 @@ 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
|
|
5
6
|
from typing import Any, ClassVar, Generic, Iterable, Protocol, TypeVar
|
|
6
7
|
|
|
7
8
|
from apexdevkit.error import ForbiddenError
|
|
@@ -150,6 +151,10 @@ class MsSqlQueryBuilder:
|
|
|
150
151
|
username: str = field(init=False)
|
|
151
152
|
translations: dict[str, 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()]
|
|
157
|
+
|
|
153
158
|
def with_source(self, value: str) -> MsSqlQueryBuilder:
|
|
154
159
|
self.source = value
|
|
155
160
|
|
|
@@ -176,10 +181,10 @@ class MsSqlQueryBuilder:
|
|
|
176
181
|
def filter(self, options: QueryOptions) -> DatabaseCommand:
|
|
177
182
|
return MsSqlQuery(
|
|
178
183
|
user=MsSqlUserGenerator(self.username),
|
|
179
|
-
selection=MsSqlSelectionGenerator(self.
|
|
184
|
+
selection=MsSqlSelectionGenerator(self._fields),
|
|
180
185
|
filter=MsSqlSourceGenerator(self.source, options.filter),
|
|
181
186
|
condition=MsSqlConditionGenerator(options.condition, self.translations),
|
|
182
|
-
ordering=MsSqlOrderGenerator(options.ordering, self.
|
|
187
|
+
ordering=MsSqlOrderGenerator(options.ordering, self._fields),
|
|
183
188
|
paging=MsSqlPagingGenerator(options.paging),
|
|
184
189
|
).generate()
|
|
185
190
|
|
|
@@ -192,17 +197,35 @@ class MsSqlUserGenerator:
|
|
|
192
197
|
return f"EXECUTE AS USER = '{self.username}'"
|
|
193
198
|
|
|
194
199
|
|
|
200
|
+
@dataclass
|
|
201
|
+
class MsSqlField:
|
|
202
|
+
name: str
|
|
203
|
+
|
|
204
|
+
alias: str = ""
|
|
205
|
+
|
|
206
|
+
def as_select_part(self) -> str:
|
|
207
|
+
result = f"[{self.name}]"
|
|
208
|
+
|
|
209
|
+
if self.alias:
|
|
210
|
+
result += f" AS [{self.alias}]"
|
|
211
|
+
|
|
212
|
+
return result
|
|
213
|
+
|
|
214
|
+
def as_order_part(self, is_descending: bool = False) -> str:
|
|
215
|
+
result = self.alias or self.name
|
|
216
|
+
|
|
217
|
+
if is_descending:
|
|
218
|
+
result += " DESC"
|
|
219
|
+
|
|
220
|
+
return result
|
|
221
|
+
|
|
222
|
+
|
|
195
223
|
@dataclass
|
|
196
224
|
class MsSqlSelectionGenerator:
|
|
197
|
-
|
|
225
|
+
fields: list[MsSqlField] = field(default_factory=list)
|
|
198
226
|
|
|
199
227
|
def generate(self) -> str:
|
|
200
|
-
fields = ", ".join(
|
|
201
|
-
[
|
|
202
|
-
f"[{self.translations[key]}] AS [{key}]"
|
|
203
|
-
for key in self.translations.keys()
|
|
204
|
-
]
|
|
205
|
-
)
|
|
228
|
+
fields = ", ".join([f.as_select_part() for f in self.fields])
|
|
206
229
|
|
|
207
230
|
return f"SELECT {fields}"
|
|
208
231
|
|
|
@@ -242,18 +265,17 @@ class MsSqlSourceGenerator:
|
|
|
242
265
|
return f"FROM {self.source}{self._argument_list()}"
|
|
243
266
|
|
|
244
267
|
def _argument_list(self) -> str:
|
|
245
|
-
if self.filter is
|
|
246
|
-
return (
|
|
247
|
-
"("
|
|
248
|
-
+ ",".join(
|
|
249
|
-
arg.as_arg() if arg is not None else "null"
|
|
250
|
-
for arg in self.filter.args
|
|
251
|
-
)
|
|
252
|
-
+ ")"
|
|
253
|
-
)
|
|
254
|
-
else:
|
|
268
|
+
if self.filter is None:
|
|
255
269
|
return ""
|
|
256
270
|
|
|
271
|
+
return (
|
|
272
|
+
"("
|
|
273
|
+
+ ",".join(
|
|
274
|
+
arg.as_arg() if arg is not None else "null" for arg in self.filter.args
|
|
275
|
+
)
|
|
276
|
+
+ ")"
|
|
277
|
+
)
|
|
278
|
+
|
|
257
279
|
|
|
258
280
|
@dataclass
|
|
259
281
|
class MsSqlConditionGenerator:
|
|
@@ -304,27 +326,26 @@ class MsSqlConditionGenerator:
|
|
|
304
326
|
@dataclass
|
|
305
327
|
class MsSqlOrderGenerator:
|
|
306
328
|
ordering: list[Sort]
|
|
307
|
-
|
|
329
|
+
|
|
330
|
+
fields: list[MsSqlField] = field(default_factory=list)
|
|
308
331
|
|
|
309
332
|
def generate(self) -> str:
|
|
310
333
|
if not self.ordering:
|
|
311
334
|
return ""
|
|
312
|
-
self._validate()
|
|
313
335
|
|
|
314
|
-
|
|
315
|
-
f"{self.translations[item.name]} DESC"
|
|
316
|
-
if item.is_descending
|
|
317
|
-
else self.translations[item.name]
|
|
318
|
-
for item in self.ordering
|
|
319
|
-
)
|
|
336
|
+
clause = ", ".join(self.generate_one(item) for item in self.ordering)
|
|
320
337
|
|
|
321
|
-
return f"ORDER BY {
|
|
338
|
+
return f"ORDER BY {clause}"
|
|
322
339
|
|
|
323
|
-
def
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
340
|
+
def generate_one(self, item: Sort) -> str:
|
|
341
|
+
return self.field_for(item.name).as_order_part(item.is_descending)
|
|
342
|
+
|
|
343
|
+
def field_for(self, name: str) -> MsSqlField:
|
|
344
|
+
for f in self.fields:
|
|
345
|
+
if f.name == name:
|
|
346
|
+
return f
|
|
347
|
+
|
|
348
|
+
raise ForbiddenError(message=f"Invalid field name: {name}")
|
|
328
349
|
|
|
329
350
|
|
|
330
351
|
@dataclass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "apexdevkit"
|
|
3
|
-
version = "1.18.
|
|
3
|
+
version = "1.18.7"
|
|
4
4
|
description = "Apex Development Tools for python."
|
|
5
5
|
authors = ["Apex Dev <dev@apex.ge>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -16,7 +16,9 @@ python-dotenv = "*"
|
|
|
16
16
|
pymssql = "^2.3.1"
|
|
17
17
|
|
|
18
18
|
[tool.poetry.group.dev.dependencies]
|
|
19
|
+
approvaltests = "*"
|
|
19
20
|
pytest = "*"
|
|
21
|
+
pytest-approvaltests = "*"
|
|
20
22
|
pytest-cov = "*"
|
|
21
23
|
pytest-recording = "*"
|
|
22
24
|
coverage = "*"
|
|
@@ -61,9 +63,6 @@ source = [
|
|
|
61
63
|
"apexdevkit",
|
|
62
64
|
"tests",
|
|
63
65
|
]
|
|
64
|
-
omit = [
|
|
65
|
-
"apexdevkit/server.py",
|
|
66
|
-
]
|
|
67
66
|
|
|
68
67
|
[tool.coverage.report]
|
|
69
68
|
skip_empty = true
|
|
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
|