fastapi-extra 0.1.4__cp312-cp312-win_amd64.whl → 0.1.6__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 +1 -1
- fastapi_extra/cache/__init__.py +8 -0
- fastapi_extra/cache/redis.py +59 -0
- fastapi_extra/cursor.cp312-win_amd64.pyd +0 -0
- fastapi_extra/database/__init__.py +14 -0
- fastapi_extra/database/driver.py +81 -0
- fastapi_extra/database/model.py +80 -0
- fastapi_extra/database/service.py +40 -0
- fastapi_extra/database/session.py +24 -0
- fastapi_extra/routing.cp312-win_amd64.pyd +0 -0
- {fastapi_extra-0.1.4.dist-info → fastapi_extra-0.1.6.dist-info}/METADATA +1 -1
- fastapi_extra-0.1.6.dist-info/RECORD +23 -0
- fastapi_extra-0.1.4.dist-info/RECORD +0 -16
- {fastapi_extra-0.1.4.dist-info → fastapi_extra-0.1.6.dist-info}/LICENSE +0 -0
- {fastapi_extra-0.1.4.dist-info → fastapi_extra-0.1.6.dist-info}/WHEEL +0 -0
- {fastapi_extra-0.1.4.dist-info → fastapi_extra-0.1.6.dist-info}/top_level.txt +0 -0
fastapi_extra/__init__.py
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
__author__ = "ziyan.yin"
|
|
2
|
+
__date__ = "2025-01-17"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from typing import Annotated, AsyncGenerator
|
|
6
|
+
|
|
7
|
+
from fastapi.params import Depends
|
|
8
|
+
from pydantic import BaseModel, Field, RedisDsn
|
|
9
|
+
from redis.asyncio import ConnectionPool, Redis
|
|
10
|
+
|
|
11
|
+
from fastapi_extra.dependency import AbstractWidget
|
|
12
|
+
from fastapi_extra.settings import Settings
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RedisConfig(BaseModel):
|
|
16
|
+
url: RedisDsn = RedisDsn("redis://localhost:6379/0")
|
|
17
|
+
max_connections: int | None = None
|
|
18
|
+
connection_kwargs: dict = Field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DefaultRedisSettings(Settings):
|
|
22
|
+
redis: RedisConfig
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_settings = DefaultRedisSettings() # type: ignore
|
|
26
|
+
_loaded_pools: list[ConnectionPool] = []
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RedisCli(AbstractWidget):
|
|
30
|
+
default_config = _settings.redis
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self._pool = None
|
|
35
|
+
|
|
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
|
+
|
|
47
|
+
|
|
48
|
+
async def dispose() -> None:
|
|
49
|
+
for redis_pool in _loaded_pools:
|
|
50
|
+
redis_pool.aclose()
|
|
51
|
+
|
|
52
|
+
|
|
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
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
DefaultRedis = Annotated[Redis, Depends(get_redis)]
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
__author__ = "ziyan.yin"
|
|
2
|
+
__date__ = "2025-01-05"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from fastapi_extra.database.model import SQLBase
|
|
6
|
+
from fastapi_extra.database.service import ModelService
|
|
7
|
+
from fastapi_extra.database.session import DefaultSession as Session
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"AsyncSessionMaker",
|
|
11
|
+
"ModelService",
|
|
12
|
+
"Session",
|
|
13
|
+
"SQLBase"
|
|
14
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
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 AbstractWidget
|
|
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(AbstractWidget):
|
|
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
|
+
return AsyncEngine(super().engine)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def session(self) -> AsyncSession:
|
|
72
|
+
return AsyncSession(self.engine)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if _settings.mode == "test":
|
|
76
|
+
DB.default_options = {"poolclass": NullPool}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def dispose() -> None:
|
|
80
|
+
for engine in _loaded_engines:
|
|
81
|
+
await _concurrency_py3k.greenlet_spawn(engine.dispose)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
__author__ = "ziyan.yin"
|
|
2
|
+
__date__ = "2024-12-25"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
|
|
7
|
+
from sqlalchemy import BigInteger, DateTime, Integer, SmallInteger, func
|
|
8
|
+
from sqlalchemy.ext.declarative import declared_attr
|
|
9
|
+
from sqlmodel import Field, SQLModel
|
|
10
|
+
|
|
11
|
+
from fastapi_extra.cursor import Cursor as _Cursor # type: ignore
|
|
12
|
+
from fastapi_extra.types import Cursor, LocalDateTime
|
|
13
|
+
from fastapi_extra.utils import get_machine_seed
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AutoPK(SQLModel):
|
|
17
|
+
id: int | None = Field(
|
|
18
|
+
default_factory=lambda: None,
|
|
19
|
+
title="ID",
|
|
20
|
+
primary_key=True,
|
|
21
|
+
sa_type=BigInteger,
|
|
22
|
+
sa_column_kwargs={"autoincrement": True},
|
|
23
|
+
schema_extra={"json_schema_extra": {"readOnly": True}},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LocalPK(SQLModel):
|
|
28
|
+
id: Cursor | None = Field(
|
|
29
|
+
default_factory=_Cursor(get_machine_seed()).next_val,
|
|
30
|
+
title="ID",
|
|
31
|
+
primary_key=True,
|
|
32
|
+
sa_type=BigInteger,
|
|
33
|
+
sa_column_kwargs={"autoincrement": False},
|
|
34
|
+
schema_extra={"json_schema_extra": {"readOnly": True}},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Deleted(SQLModel):
|
|
39
|
+
deleted: int = Field(
|
|
40
|
+
default=0,
|
|
41
|
+
title="DELETED",
|
|
42
|
+
sa_type=SmallInteger,
|
|
43
|
+
sa_column_kwargs={"nullable": False, "comment": "DELETED"},
|
|
44
|
+
schema_extra={"json_schema_extra": {"readOnly": True}},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Versioned(SQLModel):
|
|
49
|
+
version_id: int = Field(
|
|
50
|
+
default=0,
|
|
51
|
+
title="VERSION_ID",
|
|
52
|
+
sa_type=Integer,
|
|
53
|
+
sa_column_kwargs={"nullable": False, "comment": "VERSION_ID"},
|
|
54
|
+
schema_extra={"json_schema_extra": {"readOnly": True}},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@declared_attr # type: ignore
|
|
58
|
+
def __mapper_args__(cls) -> dict:
|
|
59
|
+
return {"version_id_col": "version_id"}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Optime(SQLModel):
|
|
63
|
+
create_at: LocalDateTime = Field(
|
|
64
|
+
default_factory=datetime.datetime.now,
|
|
65
|
+
title="CREATE_AT",
|
|
66
|
+
sa_type=DateTime,
|
|
67
|
+
sa_column_kwargs={"default": func.now(), "nullable": False, "comment": "CREATE_AT"},
|
|
68
|
+
schema_extra={"json_schema_extra": {"readOnly": True}},
|
|
69
|
+
)
|
|
70
|
+
update_at: LocalDateTime = Field(
|
|
71
|
+
default_factory=datetime.datetime.now,
|
|
72
|
+
title="UPDATE_AT",
|
|
73
|
+
sa_type=DateTime,
|
|
74
|
+
sa_column_kwargs={"default": func.now(), "onupdate": func.now(), "nullable": False, "comment": "UPDATE_AT"},
|
|
75
|
+
schema_extra={"json_schema_extra": {"readOnly": True}},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class SQLBase(LocalPK, Versioned, Deleted, Optime):
|
|
80
|
+
pass
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
__author__ = "ziyan.yin"
|
|
2
|
+
__date__ = "2025-01-12"
|
|
3
|
+
|
|
4
|
+
from typing import Any, Generic, Self, TypeVar
|
|
5
|
+
|
|
6
|
+
from fastapi_extra.database.model import SQLModel
|
|
7
|
+
from fastapi_extra.database.session import DefaultSession
|
|
8
|
+
from fastapi_extra.dependency import AbstractDependency
|
|
9
|
+
|
|
10
|
+
Model = TypeVar("Model", bound=SQLModel)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ModelService(AbstractDependency, Generic[Model], annotated=False):
|
|
14
|
+
__slot__ = ("session", )
|
|
15
|
+
__model__: SQLModel
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def __class_getitem__(cls, item: type[SQLModel]) -> Self:
|
|
19
|
+
if not issubclass(item, SQLModel):
|
|
20
|
+
raise TypeError(f"type[SQLModel] expected, got {item}")
|
|
21
|
+
if not (table_arg := item.model_config.get("table", None)):
|
|
22
|
+
raise AttributeError(f"True expected for argument {item.__name__}.model_config.table, got {table_arg}")
|
|
23
|
+
cls.__model__ = item
|
|
24
|
+
return cls
|
|
25
|
+
|
|
26
|
+
def __init__(self, session: DefaultSession):
|
|
27
|
+
self.session = session
|
|
28
|
+
|
|
29
|
+
async def get(self, ident: int | str, **kwargs: Any) -> Model | None:
|
|
30
|
+
return await self.session.get(self.__model__, ident, **kwargs)
|
|
31
|
+
|
|
32
|
+
async def create(self, model: Model) -> Model:
|
|
33
|
+
return await self.session.add(model)
|
|
34
|
+
|
|
35
|
+
async def create_model(self, **kwargs: Any) -> Model:
|
|
36
|
+
model = self.__model__.model_validate(kwargs)
|
|
37
|
+
return self.create(model)
|
|
38
|
+
|
|
39
|
+
async def delete(self, model: Model) -> Model:
|
|
40
|
+
return await self.session.delete(model)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__author__ = "ziyan.yin"
|
|
2
|
+
__date__ = "2025-01-05"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from typing import Annotated, AsyncGenerator, Generator
|
|
6
|
+
|
|
7
|
+
from fastapi.params import Depends
|
|
8
|
+
from sqlmodel import Session
|
|
9
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
10
|
+
|
|
11
|
+
from fastapi_extra.database.driver import DB, AsyncDB
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def get_async_session(db: AsyncDB) -> AsyncGenerator[AsyncSession, None]:
|
|
15
|
+
async with db.session as session:
|
|
16
|
+
yield session
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_session(db: DB) -> Generator[Session, None, None]:
|
|
20
|
+
with db.session as session:
|
|
21
|
+
yield session
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
DefaultSession = Annotated[AsyncSession, Depends(get_async_session)]
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
fastapi_extra/__init__.py,sha256=s2AZTyatkiSeY9OmuW6p45fFgX9Dz_nVH0jV8ut52q0,286
|
|
2
|
+
fastapi_extra/cursor.cp312-win_amd64.pyd,sha256=IWj0owGMJBYMGeuU-MXPSVRwUCt1hwrlJoqnp97OBNk,57344
|
|
3
|
+
fastapi_extra/dependency.py,sha256=Pgl4Y9Mm6JY5pw19w_xJEmnnLkQ9p_K1h1w9lLVHUJQ,907
|
|
4
|
+
fastapi_extra/form.py,sha256=LikaJkA16dSRGCqX9K7z2S8-e3SJcMiVXX5nRZU_kVY,957
|
|
5
|
+
fastapi_extra/response.py,sha256=DHvhOSgwot5eBNKuI_jPYxZ5rshZ55Xkg-FNBJlHD1E,9609
|
|
6
|
+
fastapi_extra/routing.cp312-win_amd64.pyd,sha256=to_HPQbAav7fTQeOYYaFT9zb1b4qdABNkWqQb1uDZdo,94720
|
|
7
|
+
fastapi_extra/settings.py,sha256=cCcwaper5GiNNoT4gNKqf-iloSOTNnMsiUR0knJx4Mw,1461
|
|
8
|
+
fastapi_extra/types.py,sha256=EUjT9jFryzlazHvWs4m-IfUezmSEvyxwaOGe_vTTBnY,763
|
|
9
|
+
fastapi_extra/utils.py,sha256=tsPX3kpF_P5D9Bd3gnlG6rkVsLkv5gbxjml-s6ZL_6I,346
|
|
10
|
+
fastapi_extra/cache/__init__.py,sha256=2bwWFRf6giDo0QiFWEvekQwga9kGTK_9BJdxe32Nru8,126
|
|
11
|
+
fastapi_extra/cache/redis.py,sha256=N4ntWFNjW1w3OJB64AYSrXbLumPUPK9j2iQcfXxJTC8,1586
|
|
12
|
+
fastapi_extra/database/__init__.py,sha256=B59umaoNjDuXyoNh7EYWYEk4xr9tfgVjXsSaOPz3y_Q,328
|
|
13
|
+
fastapi_extra/database/driver.py,sha256=VOJxRtXusuRyOJHMHLu18ZYbPQtoIbrkQyyADpHs9cA,2146
|
|
14
|
+
fastapi_extra/database/model.py,sha256=icHh6tnVKYVGl0hNX6pYypTiyGQt3g41geOkjBZTTv4,2467
|
|
15
|
+
fastapi_extra/database/service.py,sha256=efDZuz__RqEom-UZHfKQ3rHkxnEYmQocWduVbm3rcy0,1478
|
|
16
|
+
fastapi_extra/database/session.py,sha256=XZ5DfkDn4rHHqdJwhRDP87VPDcycxrq6-j_mu4w9Ou0,633
|
|
17
|
+
fastapi_extra/native/cursor.pyx,sha256=bESprFDgk9gGjyPQ4YCSg51dov2WB6s60XrOs3r5-r0,1146
|
|
18
|
+
fastapi_extra/native/routing.pyx,sha256=GrdGAoBospwCpxMHBon5cuRYcz9ifAFSSYa2Ytf49lg,3841
|
|
19
|
+
fastapi_extra-0.1.6.dist-info/LICENSE,sha256=0vTjHDa3VDsxTT-R-sH6SpYcA2F1hKtbX9ZFZQm-EcU,1516
|
|
20
|
+
fastapi_extra-0.1.6.dist-info/METADATA,sha256=U2HYhcc5OTpOGcN5-hOdLXJ4pEIbzqMk5FC5C62lAX0,1348
|
|
21
|
+
fastapi_extra-0.1.6.dist-info/WHEEL,sha256=cRmSBGD-cl98KkuHMNqv9Ac9L9_VqTvcBYwpIvxN0cg,101
|
|
22
|
+
fastapi_extra-0.1.6.dist-info/top_level.txt,sha256=B7D80bEftE2E-eSd1be2r9BWkLLMZN21dRTWpb4y4Ig,14
|
|
23
|
+
fastapi_extra-0.1.6.dist-info/RECORD,,
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
fastapi_extra/__init__.py,sha256=wZGS0_cF9HspqetdvcDTIxjV9lp-0yTS7toLQQnJI0Q,286
|
|
2
|
-
fastapi_extra/cursor.cp312-win_amd64.pyd,sha256=Zj_hwMRczIkzNkXjW3dUeNqrpBXW-hcTyNPlHSYxW9s,57344
|
|
3
|
-
fastapi_extra/dependency.py,sha256=Pgl4Y9Mm6JY5pw19w_xJEmnnLkQ9p_K1h1w9lLVHUJQ,907
|
|
4
|
-
fastapi_extra/form.py,sha256=LikaJkA16dSRGCqX9K7z2S8-e3SJcMiVXX5nRZU_kVY,957
|
|
5
|
-
fastapi_extra/response.py,sha256=DHvhOSgwot5eBNKuI_jPYxZ5rshZ55Xkg-FNBJlHD1E,9609
|
|
6
|
-
fastapi_extra/routing.cp312-win_amd64.pyd,sha256=4Vlhy3oYaYhUzj9jxQuONzlLYjfo03OmTsCg8jUUbcQ,94720
|
|
7
|
-
fastapi_extra/settings.py,sha256=cCcwaper5GiNNoT4gNKqf-iloSOTNnMsiUR0knJx4Mw,1461
|
|
8
|
-
fastapi_extra/types.py,sha256=EUjT9jFryzlazHvWs4m-IfUezmSEvyxwaOGe_vTTBnY,763
|
|
9
|
-
fastapi_extra/utils.py,sha256=tsPX3kpF_P5D9Bd3gnlG6rkVsLkv5gbxjml-s6ZL_6I,346
|
|
10
|
-
fastapi_extra/native/cursor.pyx,sha256=bESprFDgk9gGjyPQ4YCSg51dov2WB6s60XrOs3r5-r0,1146
|
|
11
|
-
fastapi_extra/native/routing.pyx,sha256=GrdGAoBospwCpxMHBon5cuRYcz9ifAFSSYa2Ytf49lg,3841
|
|
12
|
-
fastapi_extra-0.1.4.dist-info/LICENSE,sha256=0vTjHDa3VDsxTT-R-sH6SpYcA2F1hKtbX9ZFZQm-EcU,1516
|
|
13
|
-
fastapi_extra-0.1.4.dist-info/METADATA,sha256=2YlZPZ_9Y8rbBVG2th19mmT12zzXuBTt7MQDsRtgIjo,1348
|
|
14
|
-
fastapi_extra-0.1.4.dist-info/WHEEL,sha256=cRmSBGD-cl98KkuHMNqv9Ac9L9_VqTvcBYwpIvxN0cg,101
|
|
15
|
-
fastapi_extra-0.1.4.dist-info/top_level.txt,sha256=B7D80bEftE2E-eSd1be2r9BWkLLMZN21dRTWpb4y4Ig,14
|
|
16
|
-
fastapi_extra-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|