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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.1.4"
1
+ __version__ = "0.1.6"
2
2
 
3
3
 
4
4
  from fastapi import FastAPI
@@ -0,0 +1,8 @@
1
+ __author__ = "ziyan.yin"
2
+ __date__ = "2025-01-10"
3
+
4
+ from .redis import DefaultRedis as Redis
5
+
6
+ __all__ = [
7
+ "Redis"
8
+ ]
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fastapi-extra
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: extra package for fastapi.
5
5
  Author-email: Ziyan Yin <408856732@qq.com>
6
6
  License: BSD-3-Clause
@@ -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,,