fastapi-extra 0.1.3__tar.gz → 0.1.5__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 (26) hide show
  1. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/PKG-INFO +1 -1
  2. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra/__init__.py +1 -1
  3. fastapi_extra-0.1.5/fastapi_extra/cache/__init__.py +10 -0
  4. fastapi_extra-0.1.5/fastapi_extra/cache/redis.py +52 -0
  5. fastapi_extra-0.1.5/fastapi_extra/database/__init__.py +17 -0
  6. fastapi_extra-0.1.5/fastapi_extra/database/engine.py +50 -0
  7. fastapi_extra-0.1.5/fastapi_extra/database/model.py +80 -0
  8. fastapi_extra-0.1.5/fastapi_extra/database/service.py +40 -0
  9. fastapi_extra-0.1.5/fastapi_extra/database/session.py +39 -0
  10. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra.egg-info/PKG-INFO +1 -1
  11. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra.egg-info/SOURCES.txt +7 -0
  12. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/pyproject.toml +1 -1
  13. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/LICENSE +0 -0
  14. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/README.rst +0 -0
  15. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra/dependency.py +0 -0
  16. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra/form.py +0 -0
  17. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra/native/cursor.pyx +0 -0
  18. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra/native/routing.pyx +0 -0
  19. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra/response.py +0 -0
  20. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra/settings.py +0 -0
  21. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra/types.py +0 -0
  22. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra/utils.py +0 -0
  23. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra.egg-info/dependency_links.txt +0 -0
  24. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra.egg-info/requires.txt +0 -0
  25. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/fastapi_extra.egg-info/top_level.txt +0 -0
  26. {fastapi_extra-0.1.3 → fastapi_extra-0.1.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fastapi-extra
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: extra package for fastapi.
5
5
  Author-email: Ziyan Yin <408856732@qq.com>
6
6
  License: BSD-3-Clause
@@ -1,4 +1,4 @@
1
- __version__ = "0.1.3"
1
+ __version__ = "0.1.5"
2
2
 
3
3
 
4
4
  from fastapi import FastAPI
@@ -0,0 +1,10 @@
1
+ __author__ = "ziyan.yin"
2
+ __date__ = "2025-01-10"
3
+
4
+ from .redis import Redis, RedisCli, RedisMaker
5
+
6
+ __all__ = [
7
+ "Redis",
8
+ "RedisMaker",
9
+ "RedisCli"
10
+ ]
@@ -0,0 +1,52 @@
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.settings import Settings
12
+
13
+
14
+ class RedisConfig(BaseModel):
15
+ url: RedisDsn = RedisDsn("redis://localhost:6379/0")
16
+ max_connections: int | None = None
17
+ connection_kwargs: dict = Field(default_factory=dict)
18
+
19
+
20
+ class DefaultRedisSettings(Settings):
21
+ redis: RedisConfig
22
+
23
+
24
+ def load_pool(config: RedisConfig) -> ConnectionPool:
25
+ return ConnectionPool.from_url(
26
+ config.url,
27
+ **config.model_dump(exclude_defaults=True, exclude={"url", "connection_kwargs"}),
28
+ **config.connection_kwargs
29
+ )
30
+
31
+
32
+ _settings = DefaultRedisSettings() # type: ignore
33
+ DEFAULT_POOL = load_pool(_settings.redis)
34
+
35
+
36
+ async def dispose() -> None:
37
+ await DEFAULT_POOL.aclose()
38
+
39
+
40
+ class RedisMaker(Depends):
41
+
42
+ def __init__(self, pool: ConnectionPool):
43
+ super().__init__()
44
+ self.dependency = self
45
+ self.pool = pool
46
+
47
+ async def __call__(self) -> AsyncGenerator[Redis, None]:
48
+ async with Redis(connection_pool=self.pool) as session:
49
+ yield session
50
+
51
+
52
+ RedisCli = Annotated[Redis, RedisMaker(DEFAULT_POOL)]
@@ -0,0 +1,17 @@
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 AsyncSessionMaker
8
+ from fastapi_extra.database.session import DefaultSession as Session
9
+ from fastapi_extra.database.session import SessionMaker
10
+
11
+ __all__ = [
12
+ "AsyncSessionMaker",
13
+ "ModelService",
14
+ "Session",
15
+ "SessionMaker",
16
+ "SQLBase"
17
+ ]
@@ -0,0 +1,50 @@
1
+ __author__ = "ziyan.yin"
2
+ __date__ = "2024-12-26"
3
+
4
+
5
+ from typing import Any, Literal
6
+
7
+ from pydantic import AnyUrl, BaseModel, Field
8
+ from sqlalchemy import Engine, NullPool
9
+ from sqlalchemy.util import _concurrency_py3k
10
+ from sqlmodel import create_engine
11
+
12
+ from fastapi_extra.settings import Settings
13
+
14
+
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
+
32
+
33
+ def load_engine(config: DatabaseConfig, **kw: Any) -> Engine:
34
+ return create_engine(
35
+ url=str(config.url),
36
+ **config.model_dump(exclude_defaults=True, exclude={"url", "options"}),
37
+ **config.options,
38
+ **kw
39
+ )
40
+
41
+
42
+ _settings = DefaultDatabaseSettings() # type: ignore
43
+ if _settings.mode == "test":
44
+ DEFAULT_ENGINE: Engine = load_engine(_settings.datasource, poolclass=NullPool)
45
+ else:
46
+ DEFAULT_ENGINE: Engine = load_engine(_settings.datasource)
47
+
48
+
49
+ async def dispose() -> None:
50
+ await _concurrency_py3k.greenlet_spawn(DEFAULT_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,39 @@
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 sqlalchemy.ext.asyncio import AsyncEngine
9
+ from sqlmodel import Session as _Session
10
+ from sqlmodel.ext.asyncio.session import AsyncSession
11
+
12
+ from fastapi_extra.database.engine import DEFAULT_ENGINE, Engine
13
+
14
+
15
+ class SessionMaker(Depends):
16
+ __slots__ = ("engine", )
17
+
18
+ def __init__(self, engine: Engine):
19
+ super().__init__()
20
+ self.engine = engine
21
+ self.dependency = self
22
+
23
+ def __call__(self) -> Generator[_Session, None, None]:
24
+ with _Session(self.engine) as session:
25
+ yield session
26
+
27
+
28
+ class AsyncSessionMaker(SessionMaker):
29
+
30
+ def __init__(self, engine: Engine):
31
+ super().__init__(engine)
32
+ self.async_engine: AsyncEngine = AsyncEngine(self.engine)
33
+
34
+ async def __call__(self) -> AsyncGenerator[AsyncSession, None]:
35
+ async with AsyncSession(self.async_engine) as session:
36
+ yield session
37
+
38
+
39
+ DefaultSession = Annotated[AsyncSession, AsyncSessionMaker(DEFAULT_ENGINE)]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fastapi-extra
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: extra package for fastapi.
5
5
  Author-email: Ziyan Yin <408856732@qq.com>
6
6
  License: BSD-3-Clause
@@ -15,5 +15,12 @@ fastapi_extra.egg-info/requires.txt
15
15
  fastapi_extra.egg-info/top_level.txt
16
16
  fastapi_extra/native/cursor.pyx
17
17
  fastapi_extra/native/routing.pyx
18
+ fastapi_extra/cache/__init__.py
19
+ fastapi_extra/cache/redis.py
20
+ fastapi_extra/database/__init__.py
21
+ fastapi_extra/database/engine.py
22
+ fastapi_extra/database/model.py
23
+ fastapi_extra/database/service.py
24
+ fastapi_extra/database/session.py
18
25
  fastapi_extra/native/cursor.pyx
19
26
  fastapi_extra/native/routing.pyx
@@ -44,7 +44,7 @@ pgsql = ["asyncpg"]
44
44
  aiomysql = ["aiomysql"]
45
45
 
46
46
  [tool.setuptools]
47
- packages = ["fastapi_extra"]
47
+ packages = ["fastapi_extra", "fastapi_extra.database", "fastapi_extra.cache"]
48
48
  ext-modules = [
49
49
  { name = "fastapi_extra.cursor", sources = ["fastapi_extra/native/cursor.pyx"] },
50
50
  { name = "fastapi_extra.routing", sources = ["fastapi_extra/native/routing.pyx"] }
File without changes
File without changes
File without changes