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.
Files changed (45) hide show
  1. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/PKG-INFO +2 -1
  2. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/__init__.py +2 -0
  3. apexdevkit-1.15.6/apexdevkit/repository/connector.py +112 -0
  4. apexdevkit-1.15.6/apexdevkit/repository/mssql.py +104 -0
  5. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/server.py +6 -2
  6. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/pyproject.toml +2 -1
  7. apexdevkit-1.15.4/apexdevkit/repository/connector.py +0 -42
  8. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/LICENSE +0 -0
  9. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/README.md +0 -0
  10. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/__init__.py +0 -0
  11. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/annotation/__init__.py +0 -0
  12. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/annotation/deprecate.py +0 -0
  13. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/environment.py +0 -0
  14. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/error.py +0 -0
  15. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/__init__.py +0 -0
  16. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/builder.py +0 -0
  17. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/dependable.py +0 -0
  18. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/docs.py +0 -0
  19. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/resource.py +0 -0
  20. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/response.py +0 -0
  21. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/router.py +0 -0
  22. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/schema.py +0 -0
  23. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fastapi/service.py +0 -0
  24. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/fluent.py +0 -0
  25. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/formatter.py +0 -0
  26. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/__init__.py +0 -0
  27. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/fake.py +0 -0
  28. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/fluent.py +0 -0
  29. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/httpx.py +0 -0
  30. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/json.py +0 -0
  31. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/http/url.py +0 -0
  32. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/key_fn.py +0 -0
  33. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/py.typed +0 -0
  34. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/base.py +0 -0
  35. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/database.py +0 -0
  36. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/decorator.py +0 -0
  37. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/in_memory.py +0 -0
  38. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/interface.py +0 -0
  39. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/mongo.py +0 -0
  40. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/repository/sqlite.py +0 -0
  41. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/synchronization.py +0 -0
  42. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/testing/__init__.py +0 -0
  43. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/testing/database.py +0 -0
  44. {apexdevkit-1.15.4 → apexdevkit-1.15.6}/apexdevkit/testing/fake.py +0 -0
  45. {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.4
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
- on_startup: Callable[[], None] = field(init=False, default=_do_nothing)
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.on_startup = execute
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.4"
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