fastapi-extra 0.1.9__cp312-cp312-win_amd64.whl → 0.2.1__cp312-cp312-win_amd64.whl

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.
fastapi_extra/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.1.9"
1
+ __version__ = "0.2.1"
2
2
 
3
3
 
4
4
  from fastapi import FastAPI
@@ -1,7 +1,7 @@
1
1
  __author__ = "ziyan.yin"
2
2
  __date__ = "2025-01-10"
3
3
 
4
- from .redis import DefaultRedis as Redis
4
+ from .redis import RedisCli as Redis
5
5
 
6
6
  __all__ = [
7
7
  "Redis"
@@ -2,7 +2,7 @@ __author__ = "ziyan.yin"
2
2
  __date__ = "2025-01-17"
3
3
 
4
4
 
5
- from typing import Annotated, AsyncGenerator
5
+ from typing import Annotated, AsyncGenerator, Self
6
6
 
7
7
  from fastapi.params import Depends
8
8
  from pydantic import BaseModel, Field, RedisDsn
@@ -23,37 +23,37 @@ class DefaultRedisSettings(Settings):
23
23
 
24
24
 
25
25
  _settings = DefaultRedisSettings() # type: ignore
26
- _loaded_pools: list[ConnectionPool] = []
27
26
 
28
27
 
29
- class RedisCli(AbstractComponent):
28
+ class RedisPool(AbstractComponent):
30
29
  default_config = _settings.redis
31
30
 
32
-
33
31
  def __init__(self):
34
- self._pool = None
32
+ self._pool: ConnectionPool | None = None
35
33
 
36
- @property
37
- def pool(self) -> ConnectionPool:
38
- if not self._pool:
39
- self._pool = ConnectionPool.from_url(
40
- self.default_config.url,
41
- **self.default_config.model_dump(exclude_defaults=True, exclude={"url", "connection_kwargs"}),
42
- **self.default_config.connection_kwargs
43
- )
44
- _loaded_pools.append(self)
45
- return self._pool
46
-
34
+ @classmethod
35
+ def setup(cls, **options) -> Self:
36
+ redis = cls()
37
+ redis._pool = ConnectionPool.from_url(
38
+ cls.default_config.url,
39
+ **cls.default_config.model_dump(exclude_defaults=True, exclude={"url", "connection_kwargs"}),
40
+ **cls.default_config.connection_kwargs
41
+ **options
42
+ )
43
+ return redis
44
+
45
+ def get_client(self) -> Redis:
46
+ return Redis(connection_pool=self._pool)
47
47
 
48
- async def dispose() -> None:
49
- for redis_pool in _loaded_pools:
50
- redis_pool.aclose()
48
+ async def dispose(self) -> None:
49
+ if self._pool:
50
+ await self._pool.aclose()
51
51
 
52
52
 
53
53
 
54
- async def get_redis(redis_cli: RedisCli) -> AsyncGenerator[Redis, None]:
55
- async with Redis(connection_pool=redis_cli.pool) as redis:
56
- yield redis
54
+ async def get_redis(pool: RedisPool) -> AsyncGenerator[Redis, None]:
55
+ async with RedisPool.get_client as client:
56
+ yield client
57
57
 
58
58
 
59
- DefaultRedis = Annotated[Redis, Depends(get_redis)]
59
+ RedisCli = Annotated[Redis, Depends(get_redis)]
Binary file
@@ -0,0 +1,8 @@
1
+ __author__ = "ziyan.yin"
2
+ __describe__ = ""
3
+
4
+
5
+ class Cursor:
6
+
7
+ def next_val(self) -> int:
8
+ ...
@@ -5,10 +5,11 @@ __date__ = "2025-01-05"
5
5
  from fastapi_extra.database.model import SQLBase
6
6
  from fastapi_extra.database.service import ModelService
7
7
  from fastapi_extra.database.session import DefaultSession as Session
8
+ from fastapi_extra.database.session import SessionFactory
8
9
 
9
10
  __all__ = [
10
- "AsyncSessionMaker",
11
- "ModelService",
11
+ "SessionFactory",
12
12
  "Session",
13
- "SQLBase"
13
+ "SQLBase",
14
+ "ModelService"
14
15
  ]
@@ -1,18 +1,20 @@
1
1
  __author__ = "ziyan.yin"
2
2
  __date__ = "2025-01-12"
3
3
 
4
+ from contextvars import ContextVar
4
5
  from typing import Any, Generic, Self, TypeVar
5
6
 
6
7
  from fastapi_extra.database.model import SQLModel
7
8
  from fastapi_extra.database.session import DefaultSession
8
- from fastapi_extra.dependency import AbstractDependency
9
+ from fastapi_extra.dependency import AbstractService
9
10
 
10
11
  Model = TypeVar("Model", bound=SQLModel)
11
12
 
12
13
 
13
- class ModelService(AbstractDependency, Generic[Model], annotated=False):
14
- __slot__ = ("session", )
14
+ class ModelService(AbstractService, Generic[Model], abstract=True):
15
+ __slot__ = ()
15
16
  __model__: Model
17
+ __session_container__ = ContextVar("__session_container__", default=None)
16
18
 
17
19
  @classmethod
18
20
  def __class_getitem__(cls, item: type[SQLModel]) -> Self:
@@ -27,7 +29,13 @@ class ModelService(AbstractDependency, Generic[Model], annotated=False):
27
29
  return SubService
28
30
 
29
31
  def __init__(self, session: DefaultSession):
30
- self.session = session
32
+ self.__session_container__.set(session)
33
+
34
+ @property
35
+ def session(self):
36
+ _session = self.__session_container__.get()
37
+ assert _session is not None, "Session is not initialized"
38
+ return _session
31
39
 
32
40
  async def get(self, ident: int | str, **kwargs: Any) -> Model | None:
33
41
  return await self.session.get(self.__model__, ident, **kwargs)
@@ -1,26 +1,68 @@
1
1
  __author__ = "ziyan.yin"
2
- __date__ = "2025-01-05"
2
+ __date__ = "2024-12-26"
3
3
 
4
-
5
- from typing import Annotated, AsyncGenerator, Generator
4
+ from typing import Annotated, AsyncGenerator, Literal, Self
6
5
 
7
6
  from fastapi.params import Depends
8
- from sqlmodel import Session
7
+ from pydantic import AnyUrl, BaseModel, Field
8
+ from sqlalchemy.ext.asyncio import create_async_engine
9
9
  from sqlmodel.ext.asyncio.session import AsyncSession
10
10
 
11
- from fastapi_extra.database.driver import DB, AsyncDB
11
+ from fastapi_extra.dependency import AbstractComponent
12
+ from fastapi_extra.settings import Settings
12
13
 
13
14
 
14
- async def get_async_session(db: AsyncDB) -> AsyncGenerator[AsyncSession, None]:
15
- async with db.session as session:
16
- yield session
17
- await session.commit()
15
+ class DatabaseConfig(BaseModel):
16
+ url: AnyUrl
17
+ echo: bool = False
18
+ echo_pool: bool = False
19
+ isolation_level: Literal[
20
+ "SERIALIZABLE",
21
+ "REPEATABLE READ",
22
+ "READ COMMITTED",
23
+ "READ UNCOMMITTED",
24
+ "AUTOCOMMIT",
25
+ ] | None = None
26
+ options: dict = Field(default_factory=dict)
27
+
28
+
29
+ class DefaultDatabaseSettings(Settings):
30
+ datasource: DatabaseConfig
31
+
18
32
 
33
+ _settings = DefaultDatabaseSettings() # type: ignore
19
34
 
20
- def get_session(db: DB) -> Generator[Session, None, None]:
21
- with db.session as session:
35
+
36
+ class SessionFactory(AbstractComponent):
37
+ __slot__ = ("_engine",)
38
+ default_config = _settings.datasource
39
+
40
+ def __init__(self):
41
+ self._engine = None
42
+
43
+ @classmethod
44
+ def setup(cls, **options) -> Self:
45
+ db = cls()
46
+ db._engine = create_async_engine(
47
+ url=str(cls.default_config.url),
48
+ **cls.default_config.model_dump(exclude_defaults=True, exclude={"url", "options"}),
49
+ **cls.default_config.options,
50
+ **options
51
+ )
52
+ return db
53
+
54
+ def create_session(self) -> AsyncSession:
55
+ return AsyncSession(self._engine)
56
+
57
+ async def dispose(self) -> None:
58
+ if self._engine:
59
+ await self._engine.dispose()
60
+
61
+
62
+ async def get_session(factory: SessionFactory) -> AsyncGenerator[AsyncSession, None]:
63
+ async with factory.create_session() as session:
22
64
  yield session
23
- session.commit()
65
+ await session.commit()
24
66
 
25
67
 
26
- DefaultSession = Annotated[AsyncSession, Depends(get_async_session)]
68
+ DefaultSession = Annotated[AsyncSession, Depends(get_session)]
@@ -1,36 +1,72 @@
1
1
  __author__ = "ziyan.yin"
2
2
  __date__ = "2025-01-05"
3
3
 
4
-
5
4
  from abc import ABCMeta
6
- from typing import Annotated, Any, Self
5
+ from functools import update_wrapper
6
+ from typing import Annotated, Any, Callable, ClassVar, Self, final
7
+
8
+ from fastapi import Depends, FastAPI, Request
9
+
7
10
 
8
- from fastapi.params import Depends
11
+ def async_wrapper(func: Callable):
12
+
13
+ async def func_wrapper(*args, **kwds):
14
+ return func(*args, **kwds)
15
+
16
+ return update_wrapper(func_wrapper, func)
9
17
 
10
18
 
11
19
  class DependencyMetaClass(ABCMeta):
20
+ __load__ = None
21
+ __token__ = None
12
22
 
13
23
  def __new__(
14
24
  mcs,
15
25
  name: str,
16
26
  bases: tuple[type, ...],
17
27
  attrs: dict,
18
- annotated: bool = True
28
+ abstract: bool = False
19
29
  ):
20
30
  new_cls = super().__new__(mcs, name, bases, attrs)
21
- if annotated:
22
- return Annotated[new_cls, Depends(new_cls)]
31
+ new_cls.__token__ = f"{new_cls.__module__}.{new_cls.__name__}"
32
+ if not abstract:
33
+ if not new_cls.__load__:
34
+ return Annotated[new_cls, Depends(async_wrapper(new_cls))]
35
+ return Annotated[new_cls, Depends(new_cls.__load__)]
23
36
  return new_cls
24
37
 
25
38
 
26
- class AbstractDependency(metaclass=DependencyMetaClass, annotated=False):
39
+ class AbstractComponent(metaclass=DependencyMetaClass, abstract=True):
27
40
  __slot__ = ()
41
+ __token__: ClassVar[str]
42
+
43
+ @classmethod
44
+ def setup(cls, *args: Any, **kwargs: Any) -> Self:
45
+ raise NotImplementedError
28
46
 
47
+ @final
48
+ @classmethod
49
+ def install(cls, app: FastAPI, *args: Any, **kwargs: Any) -> Self:
50
+ component = cls.setup(*args, **kwargs)
51
+ setattr(app.state, cls.__token__, component)
52
+ return component
29
53
 
30
- class AbstractComponent(AbstractDependency, annotated=False):
54
+ @final
55
+ @classmethod
56
+ async def __load__(cls, request: Request) -> Self:
57
+ assert hasattr(request.app.state, cls.__token__), f"{cls.__name__} must be installed in lifespan"
58
+ return getattr(request.app.state, cls.__token__)
59
+
60
+ async def dispose(self) -> None:
61
+ pass
62
+
63
+
64
+ class AbstractService(metaclass=DependencyMetaClass, abstract=True):
31
65
  __slot__ = ()
32
- __instance__: Any = None
33
-
66
+ __load__ = None
67
+ __instance__ = None
68
+ __token__: ClassVar[str]
69
+
34
70
  def __new__(cls, *args, **kwargs) -> Self:
35
71
  if cls.__instance__ is None:
36
72
  cls.__instance__ = super().__new__(cls)
@@ -37,8 +37,8 @@ cdef class Cursor:
37
37
  self.cursor = count
38
38
  return (point << (_sequence_length + 4)) + (self.seed << _sequence_length) + count
39
39
 
40
- def next_val(self) -> str:
40
+ def next_val(self) -> int:
41
41
  index = self.fetch()
42
42
  while index == 0:
43
43
  index = self.fetch()
44
- return str(index)
44
+ return index
@@ -3,80 +3,113 @@ __describe__ = ""
3
3
 
4
4
  cimport cython
5
5
 
6
- from typing import MutableMapping
7
-
8
6
  from starlette import _utils as starlette_utils
9
7
  from starlette.datastructures import URL
10
8
  from starlette.responses import RedirectResponse
11
9
 
12
10
 
11
+ cdef int find_params(unicode path):
12
+ for i, ch in enumerate(path):
13
+ if ch == "{":
14
+ return i
15
+ return -1
16
+
17
+
18
+ cdef int get_longest_common_prefix(unicode path, unicode node_path):
19
+ cdef int i
20
+ cdef int max_len = min(len(path), len(node_path))
21
+ for i in range(max_len):
22
+ if path[i] != node_path[i]:
23
+ return i
24
+ return max_len
25
+
26
+
13
27
  @cython.no_gc
14
28
  cdef class RouteNode:
29
+
15
30
  cdef readonly:
16
- list routes
17
- dict leaves
18
31
  unicode prefix
32
+ list params_routes
33
+ list static_routes
34
+ dict children
35
+
36
+ cdef public object parent
19
37
 
20
- def __cinit__(self, prefix):
38
+ def __cinit__(self, prefix: str):
21
39
  self.prefix = prefix
22
- self.routes = []
23
- self.leaves = {}
40
+ self.params_routes = []
41
+ self.static_routes = []
42
+ self.children = {}
43
+ self.parent = None
44
+
45
+ def add_route(self, fullpath: str, handler: object):
46
+ wild_child = False
47
+ if (index := find_params(fullpath)) >= 0:
48
+ wild_child = True
49
+ path = fullpath[:index]
50
+ else:
51
+ path = fullpath
52
+ insert_route(self, path, wild_child, handler)
24
53
 
25
- def add_route(self, route):
26
- self.routes.append(route)
27
54
 
28
- def add_leaf(self, node):
29
- if node.prefix in self.leaves:
30
- raise KeyError(node.prefix)
31
- else:
32
- self.leaves[node.prefix] = node
55
+ cdef void insert_route(RouteNode node, unicode path, bint wild_child, object handler):
56
+ if node.prefix == path:
57
+ add_node(node, wild_child, handler)
58
+ return
33
59
 
60
+ cdef Py_UCS4 key = path.removeprefix(node.prefix)[0]
61
+ if key not in node.children:
62
+ add_child_node(node, key, path, wild_child, handler)
63
+ return
34
64
 
35
- cdef list change_path_to_ranks(unicode path):
36
- ranks = path.lstrip('/').split('/')
37
- return ranks
65
+ child_node = node.children[key]
66
+ i = get_longest_common_prefix(child_node.prefix, path)
67
+ longest_prefix = child_node.prefix[0: i]
68
+ if i == len(child_node.prefix):
69
+ insert_route(node.children[key], path, wild_child, handler)
70
+ return
71
+ next_node = RouteNode.__new__(RouteNode, longest_prefix)
72
+ next_node.parent = node
73
+ node.children[key] = next_node
74
+ next_node.children[child_node.prefix[i]] = child_node
75
+ child_node.parent = next_node
76
+ insert_route(next_node, path, wild_child, handler)
38
77
 
39
78
 
40
- cdef void add_route(unicode path, RouteNode root, object route):
41
- current_node = root
42
- ranks = change_path_to_ranks(path)
43
- for r in ranks:
44
- if r.find('{') >= 0 and r.find('}') > 0:
45
- break
46
- if not r:
47
- continue
48
- if r in current_node.leaves:
49
- current_node = current_node.leaves[r]
50
- else:
51
- next_node = RouteNode.__new__(RouteNode, r)
52
- current_node.add_leaf(next_node)
53
- current_node = next_node
54
- current_node.add_route(route)
55
-
56
-
57
- cdef list find_routes(unicode path, RouteNode root):
58
- current_node = root
59
- ranks = change_path_to_ranks(path)
60
-
61
- routes = []
62
- if current_node.routes:
63
- routes += current_node.routes
64
- for r in ranks:
65
- if not r:
66
- continue
67
- if r in current_node.leaves:
68
- current_node = current_node.leaves[r]
69
- if current_node.routes:
70
- routes += current_node.routes
71
- continue
72
- break
73
- return routes
79
+ cdef inline void add_child_node(RouteNode node, Py_UCS4 key, unicode path, bint wild_child, object handler):
80
+ child = RouteNode.__new__(RouteNode, path)
81
+ child.parent = node
82
+ add_node(child, wild_child, handler)
83
+ node.children[key] = child
84
+
85
+
86
+ cdef inline void add_node(RouteNode node, bint wild_child, object handler):
87
+ if wild_child:
88
+ node.params_routes.append(handler)
89
+ else:
90
+ node.static_routes.append(handler)
74
91
 
75
92
 
76
93
  root_node = RouteNode.__new__(RouteNode, "")
77
94
 
78
95
 
79
- async def handle(router, scope, receive, send):
96
+ cdef RouteNode search_node(unicode url):
97
+ cdef RouteNode current_node = root_node
98
+ cdef int n = len(url)
99
+ cdef int i = get_longest_common_prefix(url, current_node.prefix)
100
+
101
+ while i < n:
102
+ key = url[i]
103
+ if key not in current_node.children:
104
+ break
105
+ current_node = current_node.children[key]
106
+ i = get_longest_common_prefix(url, current_node.prefix)
107
+
108
+ return current_node
109
+
110
+
111
+ async def handle(scope, receive, send):
112
+ router = scope["app"].router
80
113
  assert scope["type"] in ("http", "websocket", "lifespan")
81
114
 
82
115
  if "router" not in scope:
@@ -90,46 +123,70 @@ async def handle(router, scope, receive, send):
90
123
 
91
124
  scope["path"] = route_path = starlette_utils.get_route_path(scope)
92
125
  scope["root_path"] = ""
93
- matched_routes = find_routes(route_path, root_node)
94
- n = len(matched_routes)
95
-
96
- for i in range(n):
97
- route = matched_routes[n - i - 1]
98
- match, child_scope = route.matches(scope)
99
- if match.value == 2:
100
- scope.update(child_scope)
101
- await route.handle(scope, receive, send)
102
- return
103
- elif match.value == 1 and partial is None:
104
- partial = route
105
- partial_scope = child_scope
126
+ leaf_node = search_node(route_path)
127
+
128
+ if leaf_node.prefix == route_path:
129
+ for route in leaf_node.static_routes:
130
+ match, child_scope = route.matches(scope)
131
+ if match.value == 2:
132
+ scope.update(child_scope)
133
+ await route.handle(scope, receive, send)
134
+ return
135
+ elif match.value == 1 and partial is None:
136
+ partial = route
137
+ partial_scope = child_scope
138
+ else:
139
+ current_node = leaf_node
140
+ routes = current_node.params_routes
141
+ while current_node.parent:
142
+ for route in routes:
143
+ match, child_scope = route.matches(scope)
144
+ if match.value == 2:
145
+ scope.update(child_scope)
146
+ await route.handle(scope, receive, send)
147
+ return
148
+ elif match.value == 1 and partial is None:
149
+ partial = route
150
+ partial_scope = child_scope
151
+ current_node = current_node.parent
106
152
 
107
153
  if partial is not None:
108
154
  scope.update(partial_scope)
109
155
  await partial.handle(scope, receive, send)
110
156
  return
111
157
 
112
-
113
158
  if scope["type"] == "http" and router.redirect_slashes and route_path != "/":
114
159
  redirect_scope = dict(scope)
115
160
  if route_path.endswith("/"):
116
161
  redirect_scope["path"] = redirect_scope["path"].rstrip("/")
117
162
  else:
118
163
  redirect_scope["path"] = redirect_scope["path"] + "/"
119
-
120
- for i in range(n):
121
- route = matched_routes[n - i - 1]
122
- match, child_scope = route.matches(redirect_scope)
123
- if match.value != 0:
124
- redirect_url = URL(scope=redirect_scope)
125
- response = RedirectResponse(url=str(redirect_url))
126
- await response(scope, receive, send)
127
- return
164
+
165
+ if leaf_node.prefix == redirect_scope["path"]:
166
+ for route in leaf_node.static_routes:
167
+ match, child_scope = route.matches(redirect_scope)
168
+ if match.value != 0:
169
+ redirect_url = URL(scope=redirect_scope)
170
+ response = RedirectResponse(url=str(redirect_url))
171
+ await response(scope, receive, send)
172
+ return
173
+ else:
174
+ current_node = leaf_node
175
+ routes = current_node.params_routes
176
+ while current_node.parent:
177
+ for route in routes:
178
+ if match.value != 0:
179
+ redirect_url = URL(scope=redirect_scope)
180
+ response = RedirectResponse(url=str(redirect_url))
181
+ await response(scope, receive, send)
182
+ return
183
+ current_node = current_node.parent
128
184
 
129
185
  await router.default(scope, receive, send)
130
186
 
131
187
 
132
188
  def install(app):
133
189
  for route in app.routes:
134
- add_route(route.path, root_node, route)
135
- app.router.app = handle
190
+ root_node.add_route(route.path, route)
191
+
192
+ app.router.middleware_stack = handle
Binary file
@@ -0,0 +1,16 @@
1
+ __author__ = "ziyan.yin"
2
+ __describe__ = ""
3
+
4
+
5
+ from typing import Any
6
+ from fastapi import FastAPI
7
+
8
+
9
+ class RouteNode:
10
+
11
+ def add_route(self, fullpath: str, handler: Any):
12
+ ...
13
+
14
+
15
+ def install(app: FastAPI) -> None:
16
+ ...
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: fastapi-extra
3
- Version: 0.1.9
3
+ Version: 0.2.1
4
4
  Summary: extra package for fastapi.
5
5
  Author-email: Ziyan Yin <408856732@qq.com>
6
6
  License: BSD-3-Clause
@@ -32,3 +32,4 @@ Provides-Extra: pgsql
32
32
  Requires-Dist: asyncpg; extra == "pgsql"
33
33
  Provides-Extra: aiomysql
34
34
  Requires-Dist: aiomysql; extra == "aiomysql"
35
+ Dynamic: license-file
@@ -0,0 +1,25 @@
1
+ fastapi_extra/__init__.py,sha256=nZY4R9s4b2NNPoO6UZnpGbs_Vm2cXhT_Infu-VyNvWM,286
2
+ fastapi_extra/cursor.cp312-win_amd64.pyd,sha256=nKZAYOxCrwr51-YZnXjKFTvpL6RKBMeQd55elvgiqhk,53760
3
+ fastapi_extra/cursor.pyi,sha256=t1_ub-dRhu3OcXB_dIw5MdBG9r1n7853Vtgzc6bk6dw,115
4
+ fastapi_extra/dependency.py,sha256=LtYnOTMyhOQUFSbNEViw7lxJvAFpPWbyuY4e2goSG-8,2130
5
+ fastapi_extra/form.py,sha256=Fs9uEDOQThjFroDVTrjWnIGJ107BgXCppIVTymwQLzg,1247
6
+ fastapi_extra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ fastapi_extra/response.py,sha256=DHvhOSgwot5eBNKuI_jPYxZ5rshZ55Xkg-FNBJlHD1E,9609
8
+ fastapi_extra/routing.cp312-win_amd64.pyd,sha256=kOfgWarbRkQDTYZAh4LYvyEB5d6DoxnpfzOYbRl6qJk,91136
9
+ fastapi_extra/routing.pyi,sha256=QslcOUnVJqFCqt14VJQwjUYQ7Qd3bQBTo34Yx1xS7TA,243
10
+ fastapi_extra/settings.py,sha256=cCcwaper5GiNNoT4gNKqf-iloSOTNnMsiUR0knJx4Mw,1461
11
+ fastapi_extra/types.py,sha256=3z6gUnao6WZL76asZYmex20xfY9mvYA-RbnsxUcui30,819
12
+ fastapi_extra/utils.py,sha256=tsPX3kpF_P5D9Bd3gnlG6rkVsLkv5gbxjml-s6ZL_6I,346
13
+ fastapi_extra/cache/__init__.py,sha256=kq4b_AYKCSJ0fEp4rqpeaoNJilko4XbtfC81xzUaYGI,122
14
+ fastapi_extra/cache/redis.py,sha256=-hr2DRkmruzH4cIArdjsytqRiiWWtDJt9GIDy38wmtQ,1600
15
+ fastapi_extra/database/__init__.py,sha256=pCYUoEylTponWqpR0AXJHcRo_cs4fnEXVVV9B5J9rt0,384
16
+ fastapi_extra/database/model.py,sha256=2lDi9PQ5F0PSM7BGZozZf1wSefpXnTWqAVzEyGPaXRI,2453
17
+ fastapi_extra/database/service.py,sha256=-tBC17jEbGZhnxdtWyftP2BUXfCwcOtxzNZ53qLp518,1798
18
+ fastapi_extra/database/session.py,sha256=PALArHhXNS2gCgeMkiKjyatvINV_VW1LjgEIwXDKyfQ,1903
19
+ fastapi_extra/native/cursor.pyx,sha256=GchJIPY6e5scuyl1BbA-Et0CdRcHOI3xgsTvDem0B70,1141
20
+ fastapi_extra/native/routing.pyx,sha256=DfK83nSwI2l8KATZ9YXLXjMXSy42YxjxZvwhQEpa0fI,6262
21
+ fastapi_extra-0.2.1.dist-info/licenses/LICENSE,sha256=0vTjHDa3VDsxTT-R-sH6SpYcA2F1hKtbX9ZFZQm-EcU,1516
22
+ fastapi_extra-0.2.1.dist-info/METADATA,sha256=ii3OEMR1gHqVOIQzzDTQiGNVXQXqmT_NtRthARtu_is,1371
23
+ fastapi_extra-0.2.1.dist-info/WHEEL,sha256=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
24
+ fastapi_extra-0.2.1.dist-info/top_level.txt,sha256=B7D80bEftE2E-eSd1be2r9BWkLLMZN21dRTWpb4y4Ig,14
25
+ fastapi_extra-0.2.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp312-cp312-win_amd64
5
5
 
@@ -1,83 +0,0 @@
1
- __author__ = "ziyan.yin"
2
- __date__ = "2024-12-26"
3
-
4
-
5
- from typing import Literal
6
-
7
- from pydantic import AnyUrl, BaseModel, Field
8
- from sqlalchemy import Engine, NullPool
9
- from sqlalchemy.ext.asyncio import AsyncEngine
10
- from sqlalchemy.util import _concurrency_py3k
11
- from sqlmodel import Session, create_engine
12
- from sqlmodel.ext.asyncio.session import AsyncSession
13
-
14
- from fastapi_extra.dependency import AbstractComponent
15
- from fastapi_extra.settings import Settings
16
-
17
-
18
- class DatabaseConfig(BaseModel):
19
- url: AnyUrl
20
- echo: bool = False
21
- echo_pool: bool = False
22
- isolation_level: Literal[
23
- "SERIALIZABLE",
24
- "REPEATABLE READ",
25
- "READ COMMITTED",
26
- "READ UNCOMMITTED",
27
- "AUTOCOMMIT",
28
- ] | None = None
29
- options: dict = Field(default_factory=dict)
30
-
31
-
32
- class DefaultDatabaseSettings(Settings):
33
- datasource: DatabaseConfig
34
-
35
-
36
- _settings = DefaultDatabaseSettings() # type: ignore
37
- _loaded_engines: list[Engine] = []
38
-
39
-
40
- class DB(AbstractComponent):
41
- default_config = _settings.datasource
42
- default_options = {}
43
-
44
- def __init__(self):
45
- self._engine = None
46
-
47
- @property
48
- def engine(self) -> Engine:
49
- if not self._engine:
50
- self._engine = create_engine(
51
- url=str(self.default_config.url),
52
- **self.default_config.model_dump(exclude_defaults=True, exclude={"url", "options"}),
53
- **self.default_config.options,
54
- **self.default_options
55
- )
56
- _loaded_engines.append(self._engine)
57
- return self._engine
58
-
59
- @property
60
- def session(self) -> Session:
61
- return Session(self.engine)
62
-
63
-
64
- class AsyncDB(DB):
65
-
66
- @property
67
- def engine(self) -> AsyncEngine:
68
- if not self._engine:
69
- self._engine = AsyncEngine(super().engine)
70
- return self._engine
71
-
72
- @property
73
- def session(self) -> AsyncSession:
74
- return AsyncSession(self.engine)
75
-
76
-
77
- if _settings.mode == "test":
78
- DB.default_options = {"poolclass": NullPool}
79
-
80
-
81
- async def dispose() -> None:
82
- for engine in _loaded_engines:
83
- await _concurrency_py3k.greenlet_spawn(engine.dispose)
@@ -1,24 +0,0 @@
1
- fastapi_extra/__init__.py,sha256=lpfuCAfLTXtsdNSd8ouWB-l-VLdZ-FfaAo7-vTJeh9k,286
2
- fastapi_extra/cursor.cp312-win_amd64.pyd,sha256=PobPLIMw1W_RCM4S6tWkLsywbir5wdz7dVBbMvlNiPs,57344
3
- fastapi_extra/dependency.py,sha256=8SgAky63hMa9pnttqRRZMyPAa3BwBMUvFA3ZLH86h44,910
4
- fastapi_extra/form.py,sha256=Fs9uEDOQThjFroDVTrjWnIGJ107BgXCppIVTymwQLzg,1247
5
- fastapi_extra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- fastapi_extra/response.py,sha256=DHvhOSgwot5eBNKuI_jPYxZ5rshZ55Xkg-FNBJlHD1E,9609
7
- fastapi_extra/routing.cp312-win_amd64.pyd,sha256=HqWjCP-jfZPw3l95pH8Im-NQT7d70TaywE_05CGXbl8,94720
8
- fastapi_extra/settings.py,sha256=cCcwaper5GiNNoT4gNKqf-iloSOTNnMsiUR0knJx4Mw,1461
9
- fastapi_extra/types.py,sha256=3z6gUnao6WZL76asZYmex20xfY9mvYA-RbnsxUcui30,819
10
- fastapi_extra/utils.py,sha256=tsPX3kpF_P5D9Bd3gnlG6rkVsLkv5gbxjml-s6ZL_6I,346
11
- fastapi_extra/cache/__init__.py,sha256=2bwWFRf6giDo0QiFWEvekQwga9kGTK_9BJdxe32Nru8,126
12
- fastapi_extra/cache/redis.py,sha256=fLNJfL8V-HYek38WVNwxvW6cnd7rJpcd62vy4O07C44,1592
13
- fastapi_extra/database/__init__.py,sha256=B59umaoNjDuXyoNh7EYWYEk4xr9tfgVjXsSaOPz3y_Q,328
14
- fastapi_extra/database/driver.py,sha256=ar_vnfqjpoWjiW5eEdrisyi9CrhfdHtbhAGFxGMKC5E,2223
15
- fastapi_extra/database/model.py,sha256=2lDi9PQ5F0PSM7BGZozZf1wSefpXnTWqAVzEyGPaXRI,2453
16
- fastapi_extra/database/service.py,sha256=TdKlcTQ_WoMqy-kmlYUcFKF7XpyFL8O4vkQ2glZc7P4,1495
17
- fastapi_extra/database/session.py,sha256=cpKj_NDBSATRCBbmfyYa4v-TKGrMMgRJQCEnkCR153s,691
18
- fastapi_extra/native/cursor.pyx,sha256=bESprFDgk9gGjyPQ4YCSg51dov2WB6s60XrOs3r5-r0,1146
19
- fastapi_extra/native/routing.pyx,sha256=GrdGAoBospwCpxMHBon5cuRYcz9ifAFSSYa2Ytf49lg,3841
20
- fastapi_extra-0.1.9.dist-info/LICENSE,sha256=0vTjHDa3VDsxTT-R-sH6SpYcA2F1hKtbX9ZFZQm-EcU,1516
21
- fastapi_extra-0.1.9.dist-info/METADATA,sha256=_ZjrmeVVPTOQZEPykPyaFVnr3odZ917x8N2xuEk4aW4,1348
22
- fastapi_extra-0.1.9.dist-info/WHEEL,sha256=A8mRFNvJcDL8dRPZ6k2ICKEOXwE8pzYFXYxEla0rR0g,101
23
- fastapi_extra-0.1.9.dist-info/top_level.txt,sha256=B7D80bEftE2E-eSd1be2r9BWkLLMZN21dRTWpb4y4Ig,14
24
- fastapi_extra-0.1.9.dist-info/RECORD,,