apexdevkit 1.22.1__tar.gz → 1.23.1__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.22.1 → apexdevkit-1.23.1}/PKG-INFO +2 -2
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/annotation/deprecate.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/error.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/builder.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/dependable.py +3 -2
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/resource.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/response.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/schema.py +6 -5
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/service.py +4 -4
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fluent.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/formatter.py +2 -3
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/http/fluent.py +5 -5
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/http/httpx/client.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/query/generator.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/query/query.py +1 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/base.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/connector.py +5 -5
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/database.py +4 -2
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/decorator.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/in_memory.py +6 -5
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/interface.py +2 -2
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/mssql.py +30 -11
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/repository.py +18 -5
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/sql.py +15 -3
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/sqlite.py +6 -5
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/server.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/synchronization.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/testing/database.py +3 -3
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/testing/fake.py +13 -13
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/testing/rest.py +2 -1
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/pyproject.toml +18 -12
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/LICENSE +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/README.md +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/environment.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/name.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/request.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/http/httpx/__init__.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/http/httpx/hooks.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/id.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/query/__init__.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/repository/__init__.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.22.1 → apexdevkit-1.23.1}/apexdevkit/value.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: apexdevkit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.23.1
|
|
4
4
|
Summary: Apex Development Tools for python.
|
|
5
5
|
Author: Apex Dev
|
|
6
6
|
Author-email: dev@apex.ge
|
|
7
|
-
Requires-Python: >=3.11
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Mapping
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, Self
|
|
6
7
|
|
|
7
8
|
from fastapi import APIRouter, FastAPI
|
|
8
9
|
from starlette.middleware.cors import CORSMiddleware
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
1
2
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Annotated, Any,
|
|
3
|
+
from typing import Annotated, Any, Protocol
|
|
3
4
|
|
|
4
5
|
from fastapi import Depends, Path
|
|
5
6
|
from fastapi.requests import Request
|
|
@@ -52,7 +53,7 @@ class ParentDependency:
|
|
|
52
53
|
try:
|
|
53
54
|
return builder.with_parent(parent_id)
|
|
54
55
|
except DoesNotExistError as e:
|
|
55
|
-
raise ApiError(404, RestfulResponse(self.parent).not_found(e))
|
|
56
|
+
raise ApiError(404, RestfulResponse(self.parent).not_found(e)) from e
|
|
56
57
|
|
|
57
58
|
return Annotated[RestfulServiceBuilder, Depends(_)]
|
|
58
59
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
+
from collections.abc import Callable, Iterable
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from functools import cached_property
|
|
4
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
from pydantic import BaseModel, create_model
|
|
7
8
|
|
|
@@ -39,10 +40,10 @@ class RestfulSchema:
|
|
|
39
40
|
)
|
|
40
41
|
|
|
41
42
|
self._schema_for("Item", {self.name.singular: schema})
|
|
42
|
-
self._schema_for("Collection", {self.name.plural:
|
|
43
|
-
self._schema_for("CreateMany", {self.name.plural:
|
|
44
|
-
self._schema_for("UpdateMany", {self.name.plural:
|
|
45
|
-
self._schema_for("ReplaceMany", {self.name.plural:
|
|
43
|
+
self._schema_for("Collection", {self.name.plural: list[schema], "count": int})
|
|
44
|
+
self._schema_for("CreateMany", {self.name.plural: list[create_schema]})
|
|
45
|
+
self._schema_for("UpdateMany", {self.name.plural: list[update_many_item]})
|
|
46
|
+
self._schema_for("ReplaceMany", {self.name.plural: list[replace_schema]})
|
|
46
47
|
|
|
47
48
|
def _schema_for(self, action: str, fields: dict[str, Any]) -> type[BaseModel]:
|
|
48
49
|
if action not in self.schemas:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from abc import
|
|
3
|
+
from collections.abc import Iterable, Mapping
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from functools import cached_property
|
|
6
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, Generic, TypeVar
|
|
7
7
|
|
|
8
8
|
from apexdevkit.formatter import Formatter
|
|
9
9
|
from apexdevkit.query.query import FooterOptions, QueryOptions, Summary
|
|
@@ -14,7 +14,7 @@ RawItem = Mapping[str, Any]
|
|
|
14
14
|
RawCollection = Iterable[RawItem]
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class _RawItemWithId(
|
|
17
|
+
class _RawItemWithId(dict[str, Any]):
|
|
18
18
|
def __post_init__(self) -> None:
|
|
19
19
|
assert "id" in self
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ class _RawItemWithId(Dict[str, Any]):
|
|
|
22
22
|
RawCollectionWithId = Iterable[_RawItemWithId]
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
class RestfulService
|
|
25
|
+
class RestfulService: # pragma: no cover
|
|
26
26
|
def create_one(self, item: RawItem) -> RawItem:
|
|
27
27
|
raise NotImplementedError(self.create_one.__name__)
|
|
28
28
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import pickle
|
|
4
|
+
from collections.abc import Mapping
|
|
4
5
|
from copy import deepcopy
|
|
5
6
|
from dataclasses import asdict, dataclass, field, fields, is_dataclass
|
|
6
|
-
from typing import Any, Generic,
|
|
7
|
-
|
|
8
|
-
from typing_extensions import get_type_hints
|
|
7
|
+
from typing import Any, Generic, Protocol, Self, TypeVar, get_args, get_type_hints
|
|
9
8
|
|
|
10
9
|
from apexdevkit.fluent import FluentDict
|
|
11
10
|
from apexdevkit.value import Value
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from enum import Enum, auto
|
|
5
|
-
from typing import Any, Protocol
|
|
5
|
+
from typing import Any, Protocol
|
|
6
6
|
|
|
7
7
|
from apexdevkit.http.json import JsonDict
|
|
8
8
|
|
|
@@ -103,25 +103,25 @@ class FluentHttpRequest:
|
|
|
103
103
|
class FluentHttpResponse:
|
|
104
104
|
response: HttpResponse
|
|
105
105
|
|
|
106
|
-
def on_bad_request(self, raises: Exception |
|
|
106
|
+
def on_bad_request(self, raises: Exception | type[Exception]) -> FluentHttpResponse:
|
|
107
107
|
if self.response.code() == 400:
|
|
108
108
|
raise raises
|
|
109
109
|
|
|
110
110
|
return self
|
|
111
111
|
|
|
112
|
-
def on_conflict(self, raises: Exception |
|
|
112
|
+
def on_conflict(self, raises: Exception | type[Exception]) -> FluentHttpResponse:
|
|
113
113
|
if self.response.code() == 409:
|
|
114
114
|
raise raises
|
|
115
115
|
|
|
116
116
|
return self
|
|
117
117
|
|
|
118
|
-
def on_not_found(self, raises: Exception |
|
|
118
|
+
def on_not_found(self, raises: Exception | type[Exception]) -> FluentHttpResponse:
|
|
119
119
|
if self.response.code() == 404:
|
|
120
120
|
raise raises
|
|
121
121
|
|
|
122
122
|
return self
|
|
123
123
|
|
|
124
|
-
def on_failure(self, raises:
|
|
124
|
+
def on_failure(self, raises: type[Exception]) -> FluentHttpResponse:
|
|
125
125
|
if self.response.code() < 200 or self.response.code() > 299:
|
|
126
126
|
raise raises(self.response.raw(), self.response.code())
|
|
127
127
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections import defaultdict
|
|
4
|
+
from collections.abc import Iterable, Mapping
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Any, ClassVar, Generic,
|
|
6
|
+
from typing import Any, ClassVar, Generic, Protocol, TypeVar
|
|
6
7
|
|
|
7
8
|
from apexdevkit.annotation import deprecated
|
|
8
9
|
from apexdevkit.error import ForbiddenError
|
|
@@ -59,7 +59,7 @@ class SummaryExtractor:
|
|
|
59
59
|
def _value(self, value: Any) -> NumericValue | DateValue | StringValue | NullValue:
|
|
60
60
|
if value is None:
|
|
61
61
|
return NullValue()
|
|
62
|
-
if isinstance(value,
|
|
62
|
+
if isinstance(value, int | float | Decimal):
|
|
63
63
|
return NumericValue.from_decimal(Decimal(value))
|
|
64
64
|
if self.aggregation.name and (
|
|
65
65
|
"date" in self.aggregation.name.lower()
|
|
@@ -4,7 +4,7 @@ import sqlite3
|
|
|
4
4
|
from contextlib import AbstractContextManager
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from functools import cached_property
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
8
8
|
|
|
9
9
|
import pymssql
|
|
10
10
|
from pymssql import Connection as _Connection
|
|
@@ -17,7 +17,7 @@ from apexdevkit.repository import Connection
|
|
|
17
17
|
class SqliteFileConnector:
|
|
18
18
|
dsn: str
|
|
19
19
|
|
|
20
|
-
def connect(self) ->
|
|
20
|
+
def connect(self) -> AbstractContextManager[Connection]:
|
|
21
21
|
connection = sqlite3.connect(self.dsn)
|
|
22
22
|
connection.row_factory = sqlite3.Row
|
|
23
23
|
|
|
@@ -28,11 +28,11 @@ class SqliteFileConnector:
|
|
|
28
28
|
class SqliteInMemoryConnector:
|
|
29
29
|
dsn: str = ":memory:"
|
|
30
30
|
|
|
31
|
-
def connect(self) ->
|
|
31
|
+
def connect(self) -> AbstractContextManager[Connection]:
|
|
32
32
|
return self._connection
|
|
33
33
|
|
|
34
34
|
@cached_property
|
|
35
|
-
def _connection(self) ->
|
|
35
|
+
def _connection(self) -> AbstractContextManager[Connection]:
|
|
36
36
|
connection = sqlite3.connect(self.dsn, check_same_thread=False)
|
|
37
37
|
connection.row_factory = sqlite3.Row
|
|
38
38
|
|
|
@@ -48,7 +48,7 @@ class MsSqlConnector:
|
|
|
48
48
|
db_tds_version = "7.0"
|
|
49
49
|
db_port: str | None = None
|
|
50
50
|
|
|
51
|
-
def connect(self) ->
|
|
51
|
+
def connect(self) -> AbstractContextManager[Connection]:
|
|
52
52
|
return ConnectionContextManager(self._connection())
|
|
53
53
|
|
|
54
54
|
def _connection(self) -> Connection:
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable, Mapping
|
|
4
|
+
from contextlib import AbstractContextManager
|
|
3
5
|
from copy import deepcopy
|
|
4
6
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Any,
|
|
7
|
+
from typing import Any, Protocol
|
|
6
8
|
|
|
7
9
|
_RawData = Mapping[str, Any]
|
|
8
10
|
|
|
@@ -45,7 +47,7 @@ class Database:
|
|
|
45
47
|
|
|
46
48
|
|
|
47
49
|
class Connector(Protocol): # pragma: no cover
|
|
48
|
-
def connect(self) ->
|
|
50
|
+
def connect(self) -> AbstractContextManager[Connection]:
|
|
49
51
|
pass
|
|
50
52
|
|
|
51
53
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
3
4
|
from contextlib import suppress
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, Generic, Protocol, Self
|
|
6
7
|
|
|
7
8
|
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
8
9
|
from apexdevkit.formatter import Formatter, PickleFormatter
|
|
@@ -131,14 +132,14 @@ class _SingleKeyRepository(RepositoryBase[ItemT]):
|
|
|
131
132
|
def delete(self, item_id: str) -> None:
|
|
132
133
|
try:
|
|
133
134
|
self.store.drop(item_id)
|
|
134
|
-
except KeyError:
|
|
135
|
-
raise DoesNotExistError(item_id)
|
|
135
|
+
except KeyError as e:
|
|
136
|
+
raise DoesNotExistError(item_id) from e
|
|
136
137
|
|
|
137
138
|
def read(self, item_id: str) -> ItemT:
|
|
138
139
|
try:
|
|
139
140
|
return self.store.get(item_id)
|
|
140
|
-
except KeyError:
|
|
141
|
-
raise DoesNotExistError(item_id)
|
|
141
|
+
except KeyError as e:
|
|
142
|
+
raise DoesNotExistError(item_id) from e
|
|
142
143
|
|
|
143
144
|
def __iter__(self) -> Iterator[ItemT]:
|
|
144
145
|
return iter(self.store.values())
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable, Iterator, Mapping
|
|
3
4
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Any, Generic,
|
|
5
|
+
from typing import Any, Generic, TypeVar
|
|
5
6
|
|
|
6
7
|
from pymssql.exceptions import DatabaseError, OperationalError
|
|
7
8
|
|
|
@@ -27,8 +28,8 @@ class MsSqlRepository(RepositoryBase[ItemT]):
|
|
|
27
28
|
|
|
28
29
|
try:
|
|
29
30
|
return int(raw["n_items"])
|
|
30
|
-
except KeyError:
|
|
31
|
-
raise UnknownError(raw)
|
|
31
|
+
except KeyError as e:
|
|
32
|
+
raise UnknownError(raw) from e
|
|
32
33
|
|
|
33
34
|
def delete(self, item_id: str) -> None:
|
|
34
35
|
self.db.execute(self.table.delete(item_id)).fetch_none()
|
|
@@ -40,19 +41,17 @@ class MsSqlRepository(RepositoryBase[ItemT]):
|
|
|
40
41
|
try:
|
|
41
42
|
return self.table.load(self.db.execute(self.table.insert(item)).fetch_one())
|
|
42
43
|
except DatabaseError as e:
|
|
43
|
-
|
|
44
|
+
if MssqlException(e).is_duplication():
|
|
45
|
+
raise self.table.exists(item) from e
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
raise self.table.exists(item)
|
|
47
|
-
|
|
48
|
-
raise UnknownError(e.message)
|
|
47
|
+
raise UnknownError(MssqlException(e).message) from e
|
|
49
48
|
|
|
50
49
|
def read(self, item_id: str) -> ItemT:
|
|
51
50
|
try:
|
|
52
51
|
raw = self.db.execute(self.table.select(item_id)).fetch_one()
|
|
53
52
|
except OperationalError as e:
|
|
54
53
|
if "Conversion failed" in str(e):
|
|
55
|
-
raise DoesNotExistError(item_id)
|
|
54
|
+
raise DoesNotExistError(item_id) from e
|
|
56
55
|
else:
|
|
57
56
|
raise e
|
|
58
57
|
|
|
@@ -151,6 +150,7 @@ class MsSqlTableBuilder(Generic[ItemT]):
|
|
|
151
150
|
table: str | None = None
|
|
152
151
|
formatter: Formatter[Mapping[str, Any], ItemT] | None = None
|
|
153
152
|
fields: list[_SqlField] | None = None
|
|
153
|
+
custom_filters: list[str] | None = None
|
|
154
154
|
|
|
155
155
|
def with_username(self, value: str) -> MsSqlTableBuilder[ItemT]:
|
|
156
156
|
return MsSqlTableBuilder[ItemT](
|
|
@@ -159,6 +159,7 @@ class MsSqlTableBuilder(Generic[ItemT]):
|
|
|
159
159
|
self.table,
|
|
160
160
|
self.formatter,
|
|
161
161
|
self.fields,
|
|
162
|
+
self.custom_filters,
|
|
162
163
|
)
|
|
163
164
|
|
|
164
165
|
def with_schema(self, value: str) -> MsSqlTableBuilder[ItemT]:
|
|
@@ -168,6 +169,7 @@ class MsSqlTableBuilder(Generic[ItemT]):
|
|
|
168
169
|
self.table,
|
|
169
170
|
self.formatter,
|
|
170
171
|
self.fields,
|
|
172
|
+
self.custom_filters,
|
|
171
173
|
)
|
|
172
174
|
|
|
173
175
|
def with_table(self, value: str) -> MsSqlTableBuilder[ItemT]:
|
|
@@ -177,6 +179,7 @@ class MsSqlTableBuilder(Generic[ItemT]):
|
|
|
177
179
|
value,
|
|
178
180
|
self.formatter,
|
|
179
181
|
self.fields,
|
|
182
|
+
self.custom_filters,
|
|
180
183
|
)
|
|
181
184
|
|
|
182
185
|
def with_formatter(
|
|
@@ -188,6 +191,7 @@ class MsSqlTableBuilder(Generic[ItemT]):
|
|
|
188
191
|
self.table,
|
|
189
192
|
value,
|
|
190
193
|
self.fields,
|
|
194
|
+
self.custom_filters,
|
|
191
195
|
)
|
|
192
196
|
|
|
193
197
|
def with_fields(self, value: Iterable[_SqlField]) -> MsSqlTableBuilder[ItemT]:
|
|
@@ -213,17 +217,32 @@ class MsSqlTableBuilder(Generic[ItemT]):
|
|
|
213
217
|
self.table,
|
|
214
218
|
self.formatter,
|
|
215
219
|
key_list,
|
|
220
|
+
self.custom_filters,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def with_custom_filters(self, filters: Iterable[str]) -> MsSqlTableBuilder[ItemT]:
|
|
224
|
+
return MsSqlTableBuilder[ItemT](
|
|
225
|
+
self.username,
|
|
226
|
+
self.schema,
|
|
227
|
+
self.table,
|
|
228
|
+
self.formatter,
|
|
229
|
+
self.fields,
|
|
230
|
+
list(filters),
|
|
216
231
|
)
|
|
217
232
|
|
|
218
233
|
def build(self) -> SqlTable[ItemT]:
|
|
219
234
|
if not self.schema or not self.table or not self.formatter or not self.fields:
|
|
220
235
|
raise ValueError("Cannot build sql table.")
|
|
221
236
|
|
|
237
|
+
field_manager = SqlFieldManager.Builder().with_fields(self.fields)
|
|
238
|
+
if self.custom_filters and len(self.custom_filters) > 0:
|
|
239
|
+
field_manager = field_manager.with_custom_filters(self.custom_filters)
|
|
240
|
+
|
|
222
241
|
return DefaultSqlTable(
|
|
223
242
|
self.schema,
|
|
224
243
|
self.table,
|
|
225
244
|
self.formatter,
|
|
226
|
-
|
|
245
|
+
field_manager.for_mssql().build(),
|
|
227
246
|
self.username,
|
|
228
247
|
)
|
|
229
248
|
|
|
@@ -253,7 +272,7 @@ class DefaultSqlTable(SqlTable[ItemT]):
|
|
|
253
272
|
[f"%({key.name})s" for key in self.fields if key.include_in_insert]
|
|
254
273
|
)
|
|
255
274
|
try:
|
|
256
|
-
self.fields.id
|
|
275
|
+
_ = self.fields.id
|
|
257
276
|
output = ", ".join(
|
|
258
277
|
["[" + field.name + "] AS " + field.name for field in self.fields]
|
|
259
278
|
)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable, Iterator
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import
|
|
5
|
+
from typing import Generic, TypeVar
|
|
5
6
|
|
|
6
7
|
from apexdevkit.error import DoesNotExistError
|
|
7
8
|
from apexdevkit.formatter import Formatter
|
|
@@ -75,22 +76,34 @@ class MultipleRepositoryBuilder(Generic[ItemT]):
|
|
|
75
76
|
self,
|
|
76
77
|
repository: Repository[ItemT],
|
|
77
78
|
condition: Callable[[ItemT], bool] = lambda item: True,
|
|
78
|
-
formatter: Formatter[ItemT, ItemT] =
|
|
79
|
+
formatter: Formatter[ItemT, ItemT] | None = None,
|
|
79
80
|
id_prefix: str = "",
|
|
80
81
|
) -> MultipleRepositoryBuilder[ItemT]:
|
|
81
82
|
return MultipleRepositoryBuilder[ItemT](
|
|
82
83
|
self.repositories
|
|
83
|
-
+ [
|
|
84
|
+
+ [
|
|
85
|
+
_InnerRepository(
|
|
86
|
+
repository,
|
|
87
|
+
condition,
|
|
88
|
+
formatter or NoFormatter[ItemT](),
|
|
89
|
+
id_prefix,
|
|
90
|
+
)
|
|
91
|
+
]
|
|
84
92
|
)
|
|
85
93
|
|
|
86
94
|
def and_repository(
|
|
87
95
|
self,
|
|
88
96
|
repository: Repository[ItemT],
|
|
89
97
|
condition: Callable[[ItemT], bool] = lambda item: True,
|
|
90
|
-
formatter: Formatter[ItemT, ItemT] =
|
|
98
|
+
formatter: Formatter[ItemT, ItemT] | None = None,
|
|
91
99
|
id_prefix: str = "",
|
|
92
100
|
) -> MultipleRepositoryBuilder[ItemT]:
|
|
93
|
-
return self.with_repository(
|
|
101
|
+
return self.with_repository(
|
|
102
|
+
repository,
|
|
103
|
+
condition,
|
|
104
|
+
formatter or NoFormatter[ItemT](),
|
|
105
|
+
id_prefix,
|
|
106
|
+
)
|
|
94
107
|
|
|
95
108
|
def build(self) -> MultipleRepository[ItemT]:
|
|
96
109
|
return MultipleRepository[ItemT](self.repositories)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterator, Mapping
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
@dataclass(frozen=True)
|
|
@@ -120,6 +121,7 @@ class SqlFieldBuilder:
|
|
|
120
121
|
@dataclass
|
|
121
122
|
class SqlFieldManager:
|
|
122
123
|
fields: list[_SqlField]
|
|
124
|
+
custom_filters: list[str]
|
|
123
125
|
key_formatter: str
|
|
124
126
|
value_formatter: str
|
|
125
127
|
|
|
@@ -209,7 +211,7 @@ class SqlFieldManager:
|
|
|
209
211
|
)
|
|
210
212
|
|
|
211
213
|
def _general_filters(self) -> str:
|
|
212
|
-
statements: list[str] = []
|
|
214
|
+
statements: list[str] = [] + self.custom_filters
|
|
213
215
|
for key in self.fields:
|
|
214
216
|
if key.is_filter:
|
|
215
217
|
inner_statements: list[str] = []
|
|
@@ -237,6 +239,8 @@ class SqlFieldManager:
|
|
|
237
239
|
|
|
238
240
|
@dataclass
|
|
239
241
|
class Builder:
|
|
242
|
+
custom_filters: list[str] = field(default_factory=list)
|
|
243
|
+
|
|
240
244
|
fields: list[_SqlField] = field(init=False)
|
|
241
245
|
key_formatter: str = field(init=False)
|
|
242
246
|
value_formatter: str = field(init=False)
|
|
@@ -246,6 +250,11 @@ class SqlFieldManager:
|
|
|
246
250
|
|
|
247
251
|
return self
|
|
248
252
|
|
|
253
|
+
def with_custom_filters(self, fields: list[str]) -> SqlFieldManager.Builder:
|
|
254
|
+
self.custom_filters = fields
|
|
255
|
+
|
|
256
|
+
return self
|
|
257
|
+
|
|
249
258
|
def for_sqlite(self) -> SqlFieldManager.Builder:
|
|
250
259
|
self.key_formatter = "x"
|
|
251
260
|
self.value_formatter = ":x"
|
|
@@ -260,5 +269,8 @@ class SqlFieldManager:
|
|
|
260
269
|
|
|
261
270
|
def build(self) -> SqlFieldManager:
|
|
262
271
|
return SqlFieldManager(
|
|
263
|
-
self.fields,
|
|
272
|
+
self.fields,
|
|
273
|
+
self.custom_filters,
|
|
274
|
+
self.key_formatter,
|
|
275
|
+
self.value_formatter,
|
|
264
276
|
)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable, Iterator, Mapping
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from sqlite3 import IntegrityError
|
|
5
|
-
from typing import Any, Generic
|
|
6
|
+
from typing import Any, Generic
|
|
6
7
|
|
|
7
8
|
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
8
9
|
from apexdevkit.formatter import Formatter
|
|
@@ -28,8 +29,8 @@ class SqliteRepository(RepositoryBase[ItemT]):
|
|
|
28
29
|
|
|
29
30
|
try:
|
|
30
31
|
return int(raw["n_items"])
|
|
31
|
-
except KeyError:
|
|
32
|
-
raise UnknownError(raw)
|
|
32
|
+
except KeyError as e:
|
|
33
|
+
raise UnknownError(raw) from e
|
|
33
34
|
|
|
34
35
|
def create(self, item: ItemT) -> ItemT:
|
|
35
36
|
try:
|
|
@@ -183,7 +184,7 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
183
184
|
|
|
184
185
|
return DatabaseCommand(f"""
|
|
185
186
|
SELECT
|
|
186
|
-
{columns}
|
|
187
|
+
{columns}
|
|
187
188
|
FROM {self.table_name.upper()}
|
|
188
189
|
{self.fields.where_statement(include_id=True)};
|
|
189
190
|
""").with_data(self.fields.with_fixed({self.fields.id: item_id}))
|
|
@@ -198,7 +199,7 @@ class _DefaultSqlTable(SqlTable[ItemT]):
|
|
|
198
199
|
|
|
199
200
|
return DatabaseCommand(f"""
|
|
200
201
|
SELECT
|
|
201
|
-
{columns}
|
|
202
|
+
{columns}
|
|
202
203
|
FROM {self.table_name.upper()}
|
|
203
204
|
WHERE {duplicates};
|
|
204
205
|
""").with_data({key: raw[key] for key in raw if key in self.fields.composite})
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable, Iterator
|
|
3
4
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Generic,
|
|
5
|
+
from typing import Generic, Protocol, TypeVar
|
|
5
6
|
|
|
6
7
|
ItemT = TypeVar("ItemT")
|
|
7
8
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from contextlib import nullcontext
|
|
3
|
+
from contextlib import AbstractContextManager, nullcontext
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Self
|
|
6
6
|
|
|
7
7
|
from apexdevkit.repository import DatabaseCommand
|
|
8
8
|
|
|
@@ -27,7 +27,7 @@ class FakeConnector:
|
|
|
27
27
|
def fetchall(self) -> list[dict[str, Any]]:
|
|
28
28
|
return self.results.pop() # type: ignore
|
|
29
29
|
|
|
30
|
-
def connect(self) ->
|
|
30
|
+
def connect(self) -> AbstractContextManager[Self]:
|
|
31
31
|
return nullcontext(self)
|
|
32
32
|
|
|
33
33
|
def cursor(self) -> Self:
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import random
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from functools import cached_property
|
|
6
|
-
from typing import Any, Generic,
|
|
6
|
+
from typing import Any, Generic, TypeVar
|
|
7
7
|
|
|
8
8
|
from faker import Faker
|
|
9
9
|
|
|
@@ -70,7 +70,7 @@ class Fake:
|
|
|
70
70
|
|
|
71
71
|
@dataclass(frozen=True)
|
|
72
72
|
class FakeResource(Generic[ItemT]):
|
|
73
|
-
item_type:
|
|
73
|
+
item_type: type[ItemT] = field()
|
|
74
74
|
fake: Fake = field(default_factory=Fake)
|
|
75
75
|
|
|
76
76
|
@cached_property
|
|
@@ -89,7 +89,7 @@ class FakeResource(Generic[ItemT]):
|
|
|
89
89
|
|
|
90
90
|
@dataclass(frozen=True)
|
|
91
91
|
class FakeValue(FakeResource[Value]):
|
|
92
|
-
item_type:
|
|
92
|
+
item_type: type[Value] = field(default=Value)
|
|
93
93
|
|
|
94
94
|
@cached_property
|
|
95
95
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -101,7 +101,7 @@ class FakeValue(FakeResource[Value]):
|
|
|
101
101
|
|
|
102
102
|
@dataclass(frozen=True)
|
|
103
103
|
class FakeNumericValue(FakeResource[NumericValue]):
|
|
104
|
-
item_type:
|
|
104
|
+
item_type: type[NumericValue] = field(default=NumericValue)
|
|
105
105
|
|
|
106
106
|
@cached_property
|
|
107
107
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -113,7 +113,7 @@ class FakeNumericValue(FakeResource[NumericValue]):
|
|
|
113
113
|
|
|
114
114
|
@dataclass(frozen=True)
|
|
115
115
|
class FakeStringValue(FakeResource[StringValue]):
|
|
116
|
-
item_type:
|
|
116
|
+
item_type: type[StringValue] = field(default=StringValue)
|
|
117
117
|
|
|
118
118
|
@cached_property
|
|
119
119
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -124,7 +124,7 @@ class FakeStringValue(FakeResource[StringValue]):
|
|
|
124
124
|
|
|
125
125
|
@dataclass(frozen=True)
|
|
126
126
|
class FakeDateValue(FakeResource[DateValue]):
|
|
127
|
-
item_type:
|
|
127
|
+
item_type: type[DateValue] = field(default=DateValue)
|
|
128
128
|
|
|
129
129
|
@cached_property
|
|
130
130
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -134,7 +134,7 @@ class FakeDateValue(FakeResource[DateValue]):
|
|
|
134
134
|
@dataclass(frozen=True)
|
|
135
135
|
class FakeLeaf(FakeResource[Leaf]):
|
|
136
136
|
values: list[NumericValue | StringValue | DateValue] = field(default_factory=list)
|
|
137
|
-
item_type:
|
|
137
|
+
item_type: type[Leaf] = field(default=Leaf)
|
|
138
138
|
|
|
139
139
|
@cached_property
|
|
140
140
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -147,7 +147,7 @@ class FakeLeaf(FakeResource[Leaf]):
|
|
|
147
147
|
@dataclass(frozen=True)
|
|
148
148
|
class FakeOperator(FakeResource[Operator]):
|
|
149
149
|
operands: list[Operator | Leaf] = field(default_factory=list)
|
|
150
|
-
item_type:
|
|
150
|
+
item_type: type[Operator] = field(default=Operator)
|
|
151
151
|
|
|
152
152
|
@cached_property
|
|
153
153
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -160,7 +160,7 @@ class FakeOperator(FakeResource[Operator]):
|
|
|
160
160
|
@dataclass(frozen=True)
|
|
161
161
|
class FakeSort(FakeResource[Sort]):
|
|
162
162
|
is_descending: bool | None = None
|
|
163
|
-
item_type:
|
|
163
|
+
item_type: type[Sort] = field(default=Sort)
|
|
164
164
|
|
|
165
165
|
@cached_property
|
|
166
166
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -174,7 +174,7 @@ class FakeSort(FakeResource[Sort]):
|
|
|
174
174
|
|
|
175
175
|
@dataclass(frozen=True)
|
|
176
176
|
class FakePage(FakeResource[Page]):
|
|
177
|
-
item_type:
|
|
177
|
+
item_type: type[Page] = field(default=Page)
|
|
178
178
|
|
|
179
179
|
@cached_property
|
|
180
180
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -188,7 +188,7 @@ class FakePage(FakeResource[Page]):
|
|
|
188
188
|
@dataclass(frozen=True)
|
|
189
189
|
class FakeFilter(FakeResource[Filter]):
|
|
190
190
|
args: list[NumericValue | StringValue] = field(default_factory=list)
|
|
191
|
-
item_type:
|
|
191
|
+
item_type: type[Filter] = field(default=Filter)
|
|
192
192
|
|
|
193
193
|
@cached_property
|
|
194
194
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -203,7 +203,7 @@ class FakeQueryOptions(FakeResource[QueryOptions]):
|
|
|
203
203
|
condition: Operator | None = None
|
|
204
204
|
ordering: list[Sort] = field(default_factory=list)
|
|
205
205
|
paging: Page | None = None
|
|
206
|
-
item_type:
|
|
206
|
+
item_type: type[QueryOptions] = field(default=QueryOptions)
|
|
207
207
|
|
|
208
208
|
@cached_property
|
|
209
209
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -217,7 +217,7 @@ class FakeQueryOptions(FakeResource[QueryOptions]):
|
|
|
217
217
|
|
|
218
218
|
@dataclass(frozen=True)
|
|
219
219
|
class FakeAggregationOption(FakeResource[AggregationOption]):
|
|
220
|
-
item_type:
|
|
220
|
+
item_type: type[AggregationOption] = field(default=AggregationOption)
|
|
221
221
|
|
|
222
222
|
@cached_property
|
|
223
223
|
def _raw(self) -> dict[str, Any]:
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from functools import cached_property
|
|
5
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, Self
|
|
6
7
|
|
|
7
8
|
from apexdevkit.fastapi.name import RestfulName
|
|
8
9
|
from apexdevkit.fastapi.request import HttpRequest
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
[
|
|
1
|
+
[project]
|
|
2
2
|
name = "apexdevkit"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.23.1"
|
|
4
4
|
description = "Apex Development Tools for python."
|
|
5
|
-
authors = ["Apex Dev <dev@apex.ge>"]
|
|
6
5
|
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Apex Dev", email = "dev@apex.ge" }
|
|
8
|
+
]
|
|
9
|
+
dynamic = ["dependencies"]
|
|
10
|
+
requires-python = ">=3.11"
|
|
7
11
|
|
|
8
12
|
[tool.poetry.dependencies]
|
|
9
|
-
python = "^3.11"
|
|
10
13
|
httpx = "*"
|
|
11
14
|
fastapi = "*"
|
|
12
15
|
uvicorn = "*"
|
|
13
|
-
sentry-sdk = {extras = ["fastapi"], version = "*"}
|
|
16
|
+
sentry-sdk = { extras = ["fastapi"], version = "*" }
|
|
14
17
|
python-dotenv = "*"
|
|
15
18
|
pymssql = "2.3.2"
|
|
16
19
|
|
|
@@ -22,14 +25,12 @@ pytest-cov = "*"
|
|
|
22
25
|
pytest-recording = "*"
|
|
23
26
|
coverage = "*"
|
|
24
27
|
faker = "*"
|
|
25
|
-
mongomock = "*"
|
|
26
28
|
|
|
27
29
|
[tool.poetry.group.lint.dependencies]
|
|
28
30
|
mypy = "*"
|
|
29
31
|
ruff = "*"
|
|
30
32
|
|
|
31
33
|
[tool.mypy]
|
|
32
|
-
python_version = "3.11"
|
|
33
34
|
ignore_missing_imports = true
|
|
34
35
|
strict = true
|
|
35
36
|
exclude = [
|
|
@@ -38,7 +39,6 @@ exclude = [
|
|
|
38
39
|
]
|
|
39
40
|
|
|
40
41
|
[tool.ruff]
|
|
41
|
-
target-version = "py311"
|
|
42
42
|
line-length = 88
|
|
43
43
|
|
|
44
44
|
exclude = [
|
|
@@ -48,10 +48,16 @@ exclude = [
|
|
|
48
48
|
"venv",
|
|
49
49
|
]
|
|
50
50
|
|
|
51
|
-
lint
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
select = [
|
|
53
|
+
"B", # flake8-bugbear
|
|
54
|
+
"C4", # flake8-comprehensions
|
|
55
|
+
"E", # pycodestyle errors
|
|
56
|
+
"F", # pyflakes
|
|
57
|
+
"I", # isort
|
|
58
|
+
"UP", # pyupgrade
|
|
59
|
+
"W", # pycodestyle warnings
|
|
60
|
+
]
|
|
55
61
|
|
|
56
62
|
[tool.ruff.lint.mccabe]
|
|
57
63
|
max-complexity = 10
|
|
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
|