apexdevkit 1.15.4__tar.gz → 1.15.6__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.15.4 → apexdevkit-1.15.6}/PKG-INFO +2 -1
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/__init__.py +2 -0
- apexdevkit-1.15.6/apexdevkit/repository/connector.py +112 -0
- apexdevkit-1.15.6/apexdevkit/repository/mssql.py +104 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/server.py +6 -2
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/pyproject.toml +2 -1
- apexdevkit-1.15.4/apexdevkit/repository/connector.py +0 -42
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/LICENSE +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/README.md +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/__init__.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/annotation/__init__.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/annotation/deprecate.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/environment.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/error.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/__init__.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/builder.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/dependable.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/docs.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/resource.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/response.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/router.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/schema.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/service.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fluent.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/formatter.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/__init__.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/fake.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/fluent.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/httpx.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/json.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/url.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/key_fn.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/py.typed +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/base.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/database.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/decorator.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/in_memory.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/interface.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/mongo.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/sqlite.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/synchronization.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/testing/__init__.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/testing/database.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/testing/fake.py +0 -0
- {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/testing/rest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: apexdevkit
|
|
3
|
-
Version: 1.15.
|
|
3
|
+
Version: 1.15.6
|
|
4
4
|
Summary: Apex Development Tools for python.
|
|
5
5
|
Author: Apex Dev
|
|
6
6
|
Author-email: dev@apex.ge
|
|
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
12
12
|
Requires-Dist: fastapi
|
|
13
13
|
Requires-Dist: httpx
|
|
14
14
|
Requires-Dist: pymongo
|
|
15
|
+
Requires-Dist: pymssql (>=2.3.1,<3.0.0)
|
|
15
16
|
Requires-Dist: python-dotenv
|
|
16
17
|
Requires-Dist: sentry-sdk[fastapi]
|
|
17
18
|
Requires-Dist: uvicorn
|
|
@@ -12,6 +12,7 @@ from apexdevkit.repository.in_memory import (
|
|
|
12
12
|
KeyValueStore,
|
|
13
13
|
)
|
|
14
14
|
from apexdevkit.repository.interface import Repository
|
|
15
|
+
from apexdevkit.repository.mssql import MsSqlRepository
|
|
15
16
|
|
|
16
17
|
__all__ = [
|
|
17
18
|
"Connection",
|
|
@@ -24,4 +25,5 @@ __all__ = [
|
|
|
24
25
|
"KeyValueStore",
|
|
25
26
|
"Repository",
|
|
26
27
|
"RepositoryBase",
|
|
28
|
+
"MsSqlRepository",
|
|
27
29
|
]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
from contextlib import AbstractContextManager
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from typing import Any, ContextManager
|
|
8
|
+
|
|
9
|
+
import pymssql
|
|
10
|
+
from pymongo import MongoClient
|
|
11
|
+
from pymssql import Connection as _Connection
|
|
12
|
+
from pymssql import Cursor
|
|
13
|
+
|
|
14
|
+
from apexdevkit.repository import Connection
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class SqliteFileConnector:
|
|
19
|
+
dsn: str
|
|
20
|
+
|
|
21
|
+
def connect(self) -> ContextManager[Connection]:
|
|
22
|
+
connection = sqlite3.connect(self.dsn)
|
|
23
|
+
connection.row_factory = sqlite3.Row
|
|
24
|
+
|
|
25
|
+
return connection
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class SqliteInMemoryConnector:
|
|
30
|
+
dsn: str = ":memory:"
|
|
31
|
+
|
|
32
|
+
def connect(self) -> ContextManager[Connection]:
|
|
33
|
+
return self._connection
|
|
34
|
+
|
|
35
|
+
@cached_property
|
|
36
|
+
def _connection(self) -> ContextManager[Connection]:
|
|
37
|
+
connection = sqlite3.connect(self.dsn, check_same_thread=False)
|
|
38
|
+
connection.row_factory = sqlite3.Row
|
|
39
|
+
|
|
40
|
+
return connection
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class PyMongoConnector:
|
|
45
|
+
dsn: str
|
|
46
|
+
|
|
47
|
+
def connect(self) -> ContextManager[MongoClient[Any]]:
|
|
48
|
+
return MongoClient(self.dsn)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class MsSqlConnector:
|
|
53
|
+
db_host: str
|
|
54
|
+
db_user: str
|
|
55
|
+
db_password: str
|
|
56
|
+
db_name: str
|
|
57
|
+
db_tds_version = "7.0"
|
|
58
|
+
|
|
59
|
+
def connect(self) -> ContextManager[Connection]:
|
|
60
|
+
return ConnectionContextManager(self._connection())
|
|
61
|
+
|
|
62
|
+
def _connection(self) -> Connection:
|
|
63
|
+
return MsSqlConnectionAdapter(
|
|
64
|
+
pymssql.connect(
|
|
65
|
+
tds_version=self.db_tds_version,
|
|
66
|
+
server=self.db_host,
|
|
67
|
+
user=self.db_user,
|
|
68
|
+
password=self.db_password,
|
|
69
|
+
database=self.db_name,
|
|
70
|
+
as_dict=True,
|
|
71
|
+
autocommit=True,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ConnectionContextManager(AbstractContextManager[Connection]):
|
|
77
|
+
def __init__(self, connection: Connection) -> None:
|
|
78
|
+
self.connection = connection
|
|
79
|
+
|
|
80
|
+
def __enter__(self) -> Connection:
|
|
81
|
+
return self.connection
|
|
82
|
+
|
|
83
|
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
|
84
|
+
self.connection.cursor().close()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class MsSqlConnectionAdapter:
|
|
89
|
+
connection: _Connection
|
|
90
|
+
|
|
91
|
+
def cursor(self) -> MsSqlCursorAdapter:
|
|
92
|
+
return MsSqlCursorAdapter(self.connection.cursor())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class MsSqlCursorAdapter:
|
|
97
|
+
cursor: Cursor
|
|
98
|
+
|
|
99
|
+
def execute(self, *args: Any, **kwargs: Any) -> Any:
|
|
100
|
+
self.cursor.execute(*args, **kwargs) # type : ignore
|
|
101
|
+
|
|
102
|
+
def executemany(self, *args: Any, **kwargs: Any) -> Any:
|
|
103
|
+
self.cursor.executemany(*args, **kwargs) # type : ignore
|
|
104
|
+
|
|
105
|
+
def fetchone(self, *args: Any, **kwargs: Any) -> Any:
|
|
106
|
+
return self.cursor.fetchone() # type : ignore
|
|
107
|
+
|
|
108
|
+
def fetchall(self, *args: Any, **kwargs: Any) -> Any:
|
|
109
|
+
return self.cursor.fetchall() # type : ignore
|
|
110
|
+
|
|
111
|
+
def close(self) -> None:
|
|
112
|
+
self.cursor.close() # type : ignore
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Iterator, Protocol, TypeVar
|
|
5
|
+
|
|
6
|
+
from pymssql.exceptions import DatabaseError
|
|
7
|
+
|
|
8
|
+
from apexdevkit.error import DoesNotExistError, ExistsError
|
|
9
|
+
from apexdevkit.repository import Database, DatabaseCommand, RepositoryBase
|
|
10
|
+
|
|
11
|
+
ItemT = TypeVar("ItemT")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class MsSqlRepository(RepositoryBase[ItemT]):
|
|
16
|
+
db: Database
|
|
17
|
+
table: SqlTable[ItemT]
|
|
18
|
+
|
|
19
|
+
def __iter__(self) -> Iterator[ItemT]:
|
|
20
|
+
for raw in self.db.execute(self.table.select_all()).fetch_all():
|
|
21
|
+
yield self.table.load(raw)
|
|
22
|
+
|
|
23
|
+
def __len__(self) -> int:
|
|
24
|
+
raw = self.db.execute(self.table.count_all()).fetch_one()
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
return int(raw["n_items"])
|
|
28
|
+
except KeyError:
|
|
29
|
+
raise UnknownError(raw)
|
|
30
|
+
|
|
31
|
+
def delete(self, item_id: str) -> None:
|
|
32
|
+
self.db.execute(self.table.delete(item_id)).fetch_none()
|
|
33
|
+
|
|
34
|
+
def delete_all(self) -> None:
|
|
35
|
+
self.db.execute(self.table.delete_all()).fetch_none()
|
|
36
|
+
|
|
37
|
+
def create(self, item: ItemT) -> ItemT:
|
|
38
|
+
try:
|
|
39
|
+
return self.table.load(self.db.execute(self.table.insert(item)).fetch_one())
|
|
40
|
+
except DatabaseError as e:
|
|
41
|
+
e = MssqlException(e)
|
|
42
|
+
|
|
43
|
+
if e.is_duplication():
|
|
44
|
+
raise self.table.exists(item)
|
|
45
|
+
|
|
46
|
+
raise UnknownError(e.message)
|
|
47
|
+
|
|
48
|
+
def read(self, item_id: str) -> ItemT:
|
|
49
|
+
raw = self.db.execute(self.table.select(item_id)).fetch_one()
|
|
50
|
+
|
|
51
|
+
if not raw:
|
|
52
|
+
raise DoesNotExistError(item_id)
|
|
53
|
+
|
|
54
|
+
return self.table.load(raw)
|
|
55
|
+
|
|
56
|
+
def update(self, item: ItemT) -> None:
|
|
57
|
+
self.db.execute(self.table.update(item)).fetch_none()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class SqlTable(Protocol[ItemT]): # pragma: no cover
|
|
61
|
+
def count_all(self) -> DatabaseCommand:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def insert(self, item: ItemT) -> DatabaseCommand:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
def select(self, item_id: str) -> DatabaseCommand:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
def select_all(self) -> DatabaseCommand:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def delete(self, item_id: str) -> DatabaseCommand:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
def delete_all(self) -> DatabaseCommand:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def update(self, item: ItemT) -> DatabaseCommand:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def load(self, data: dict[str, Any]) -> ItemT:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def exists(self, duplicate: ItemT) -> ExistsError:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class MssqlException:
|
|
91
|
+
code: int
|
|
92
|
+
message: str
|
|
93
|
+
|
|
94
|
+
def __init__(self, e: DatabaseError):
|
|
95
|
+
self.code = e.args[0]
|
|
96
|
+
self.message = e.args[1].decode()
|
|
97
|
+
|
|
98
|
+
def is_duplication(self) -> bool:
|
|
99
|
+
return self.code in [2627, 70003]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class UnknownError(Exception):
|
|
104
|
+
raw: Any
|
|
@@ -24,7 +24,7 @@ class UvicornServer:
|
|
|
24
24
|
port: int = 8000
|
|
25
25
|
path: str = ""
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
startup_handlers: list[Callable[[], None]] = field(default_factory=list)
|
|
28
28
|
|
|
29
29
|
@classmethod
|
|
30
30
|
def from_env(cls, path: str = ".env") -> UvicornServer:
|
|
@@ -49,7 +49,7 @@ class UvicornServer:
|
|
|
49
49
|
return self
|
|
50
50
|
|
|
51
51
|
def before_run(self, execute: Callable[[], None]) -> UvicornServer:
|
|
52
|
-
self.
|
|
52
|
+
self.startup_handlers.append(execute)
|
|
53
53
|
|
|
54
54
|
return self
|
|
55
55
|
|
|
@@ -64,6 +64,10 @@ class UvicornServer:
|
|
|
64
64
|
log_config=self.logging_config,
|
|
65
65
|
)
|
|
66
66
|
|
|
67
|
+
def on_startup(self) -> None:
|
|
68
|
+
for handler in self.startup_handlers:
|
|
69
|
+
handler()
|
|
70
|
+
|
|
67
71
|
|
|
68
72
|
@dataclass
|
|
69
73
|
class Sentry:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "apexdevkit"
|
|
3
|
-
version = "1.15.
|
|
3
|
+
version = "1.15.6"
|
|
4
4
|
description = "Apex Development Tools for python."
|
|
5
5
|
authors = ["Apex Dev <dev@apex.ge>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -13,6 +13,7 @@ pymongo = "*"
|
|
|
13
13
|
uvicorn = "*"
|
|
14
14
|
sentry-sdk = {extras = ["fastapi"], version = "*"}
|
|
15
15
|
python-dotenv = "*"
|
|
16
|
+
pymssql = "^2.3.1"
|
|
16
17
|
|
|
17
18
|
[tool.poetry.group.dev.dependencies]
|
|
18
19
|
pytest = "*"
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import sqlite3
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from functools import cached_property
|
|
4
|
-
from typing import Any, ContextManager
|
|
5
|
-
|
|
6
|
-
from pymongo import MongoClient
|
|
7
|
-
|
|
8
|
-
from apexdevkit.repository import Connection
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass(frozen=True)
|
|
12
|
-
class SqliteFileConnector:
|
|
13
|
-
dsn: str
|
|
14
|
-
|
|
15
|
-
def connect(self) -> ContextManager[Connection]:
|
|
16
|
-
connection = sqlite3.connect(self.dsn)
|
|
17
|
-
connection.row_factory = sqlite3.Row
|
|
18
|
-
|
|
19
|
-
return connection
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@dataclass(frozen=True)
|
|
23
|
-
class SqliteInMemoryConnector:
|
|
24
|
-
dsn: str = ":memory:"
|
|
25
|
-
|
|
26
|
-
def connect(self) -> ContextManager[Connection]:
|
|
27
|
-
return self._connection
|
|
28
|
-
|
|
29
|
-
@cached_property
|
|
30
|
-
def _connection(self) -> ContextManager[Connection]:
|
|
31
|
-
connection = sqlite3.connect(self.dsn, check_same_thread=False)
|
|
32
|
-
connection.row_factory = sqlite3.Row
|
|
33
|
-
|
|
34
|
-
return connection
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@dataclass(frozen=True)
|
|
38
|
-
class PyMongoConnector:
|
|
39
|
-
dsn: str
|
|
40
|
-
|
|
41
|
-
def connect(self) -> ContextManager[MongoClient[Any]]:
|
|
42
|
-
return MongoClient(self.dsn)
|
|
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
|