fastapi-extra 0.2.3__tar.gz → 0.2.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 (30) hide show
  1. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/PKG-INFO +1 -1
  2. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/__init__.py +2 -2
  3. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/cache/__init__.py +1 -3
  4. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/cache/redis.py +9 -8
  5. fastapi_extra-0.2.5/fastapi_extra/cursor.pyi +8 -0
  6. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/database/__init__.py +1 -6
  7. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/database/model.py +19 -10
  8. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/database/service.py +14 -11
  9. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/database/session.py +18 -13
  10. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/dependency.py +7 -9
  11. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/form.py +6 -4
  12. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/response.py +15 -7
  13. fastapi_extra-0.2.5/fastapi_extra/routing.pyi +11 -0
  14. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/settings.py +7 -7
  15. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/types.py +6 -5
  16. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra.egg-info/PKG-INFO +1 -1
  17. fastapi_extra-0.2.3/fastapi_extra/cursor.pyi +0 -8
  18. fastapi_extra-0.2.3/fastapi_extra/routing.pyi +0 -16
  19. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/LICENSE +0 -0
  20. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/README.rst +0 -0
  21. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/native/cursor.pyx +0 -0
  22. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/native/routing.pyx +0 -0
  23. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/py.typed +0 -0
  24. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/utils.py +0 -0
  25. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra.egg-info/SOURCES.txt +0 -0
  26. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra.egg-info/dependency_links.txt +0 -0
  27. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra.egg-info/requires.txt +0 -0
  28. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra.egg-info/top_level.txt +0 -0
  29. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/pyproject.toml +0 -0
  30. {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-extra
3
- Version: 0.2.3
3
+ Version: 0.2.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.2.3"
1
+ __version__ = "0.2.5"
2
2
 
3
3
 
4
4
  from fastapi import FastAPI
@@ -7,7 +7,7 @@ from fastapi import FastAPI
7
7
  def setup(app: FastAPI) -> None:
8
8
  try:
9
9
  from fastapi_extra import routing as native_routing # type: ignore
10
-
10
+
11
11
  native_routing.install(app)
12
12
  except ImportError: # pragma: nocover
13
13
  pass
@@ -3,6 +3,4 @@ __date__ = "2025-01-10"
3
3
 
4
4
  from .redis import RedisCli as Redis
5
5
 
6
- __all__ = [
7
- "Redis"
8
- ]
6
+ __all__ = ["Redis"]
@@ -30,18 +30,20 @@ class RedisPool(AbstractComponent):
30
30
 
31
31
  def __init__(self):
32
32
  self._pool: ConnectionPool | None = None
33
-
33
+
34
34
  @classmethod
35
35
  def setup(cls, **options) -> Self:
36
36
  redis = cls()
37
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
38
+ str(cls.default_config.url),
39
+ **cls.default_config.model_dump(
40
+ exclude_defaults=True, exclude={"url", "connection_kwargs"}
41
+ ),
42
+ **cls.default_config.connection_kwargs,
43
+ **options,
42
44
  )
43
45
  return redis
44
-
46
+
45
47
  def get_client(self) -> Redis:
46
48
  return Redis(connection_pool=self._pool)
47
49
 
@@ -50,9 +52,8 @@ class RedisPool(AbstractComponent):
50
52
  await self._pool.aclose()
51
53
 
52
54
 
53
-
54
55
  async def get_redis(pool: RedisPool) -> AsyncGenerator[Redis, None]:
55
- async with RedisPool.get_client as client:
56
+ async with pool.get_client() as client:
56
57
  yield client
57
58
 
58
59
 
@@ -0,0 +1,8 @@
1
+ __author__ = "ziyan.yin"
2
+ __describe__ = ""
3
+
4
+ class Cursor:
5
+
6
+ def __init__(self, seed: int) -> None: ...
7
+
8
+ def next_val(self) -> int: ...
@@ -7,9 +7,4 @@ from fastapi_extra.database.service import ModelService
7
7
  from fastapi_extra.database.session import DefaultSession as Session
8
8
  from fastapi_extra.database.session import SessionFactory
9
9
 
10
- __all__ = [
11
- "SessionFactory",
12
- "Session",
13
- "SQLBase",
14
- "ModelService"
15
- ]
10
+ __all__ = ["SessionFactory", "Session", "SQLBase", "ModelService"]
@@ -15,8 +15,8 @@ from fastapi_extra.utils import get_machine_seed
15
15
 
16
16
  class AutoPK(SQLModel):
17
17
  id: int | None = Field(
18
- default=None,
19
- title="ID",
18
+ default=None,
19
+ title="ID",
20
20
  primary_key=True,
21
21
  sa_type=BigInteger,
22
22
  sa_column_kwargs={"autoincrement": True},
@@ -26,9 +26,9 @@ class AutoPK(SQLModel):
26
26
 
27
27
  class LocalPK(SQLModel):
28
28
  id: Cursor | None = Field(
29
- default_factory=_Cursor(get_machine_seed()).next_val,
30
- title="ID",
31
- primary_key=True,
29
+ default_factory=_Cursor(get_machine_seed()).next_val,
30
+ title="ID",
31
+ primary_key=True,
32
32
  sa_type=BigInteger,
33
33
  sa_column_kwargs={"autoincrement": False},
34
34
  schema_extra={"json_schema_extra": {"readOnly": True}},
@@ -53,7 +53,7 @@ class Versioned(SQLModel):
53
53
  sa_column_kwargs={"nullable": False, "comment": "VERSION_ID"},
54
54
  schema_extra={"json_schema_extra": {"readOnly": True}},
55
55
  )
56
-
56
+
57
57
  @declared_attr # type: ignore
58
58
  def __mapper_args__(cls) -> dict:
59
59
  return {"version_id_col": cls.version_id}
@@ -61,17 +61,26 @@ class Versioned(SQLModel):
61
61
 
62
62
  class Optime(SQLModel):
63
63
  create_at: LocalDateTime = Field(
64
- default_factory=datetime.datetime.now,
64
+ default_factory=datetime.datetime.now,
65
65
  title="CREATE_AT",
66
66
  sa_type=DateTime,
67
- sa_column_kwargs={"default": func.now(), "nullable": False, "comment": "CREATE_AT"},
67
+ sa_column_kwargs={
68
+ "default": func.now(),
69
+ "nullable": False,
70
+ "comment": "CREATE_AT",
71
+ },
68
72
  schema_extra={"json_schema_extra": {"readOnly": True}},
69
73
  )
70
74
  update_at: LocalDateTime = Field(
71
- default_factory=datetime.datetime.now,
75
+ default_factory=datetime.datetime.now,
72
76
  title="UPDATE_AT",
73
77
  sa_type=DateTime,
74
- sa_column_kwargs={"default": func.now(), "onupdate": func.now(), "nullable": False, "comment": "UPDATE_AT"},
78
+ sa_column_kwargs={
79
+ "default": func.now(),
80
+ "onupdate": func.now(),
81
+ "nullable": False,
82
+ "comment": "UPDATE_AT",
83
+ },
75
84
  schema_extra={"json_schema_extra": {"readOnly": True}},
76
85
  )
77
86
 
@@ -2,7 +2,7 @@ __author__ = "ziyan.yin"
2
2
  __date__ = "2025-01-12"
3
3
 
4
4
  from contextvars import ContextVar
5
- from typing import Any, Generic, Self, TypeVar
5
+ from typing import Any, Generic, TypeVar
6
6
 
7
7
  from fastapi_extra.database.model import SQLModel
8
8
  from fastapi_extra.database.session import AsyncSession, DefaultSession
@@ -13,24 +13,27 @@ Model = TypeVar("Model", bound=SQLModel)
13
13
 
14
14
  class ModelService(AbstractService, Generic[Model], abstract=True):
15
15
  __slot__ = ()
16
- __model__: Model
17
- __session_container__ = ContextVar("__session_container__", default=None)
18
-
16
+ __model__: type[Model]
17
+ __session_container__ = ContextVar[AsyncSession | None]("__session_container__", default=None)
18
+
19
19
  @classmethod
20
- def __class_getitem__(cls, item: type[SQLModel]) -> Self:
20
+ def __class_getitem__(cls, item: type[SQLModel]) -> type["ModelService"]:
21
21
  if not issubclass(item, SQLModel):
22
22
  raise TypeError(f"type[SQLModel] expected, got {item}")
23
23
  if not (table_arg := item.model_config.get("table", None)):
24
- raise AttributeError(f"True expected for argument {item.__name__}.model_config.table, got {table_arg}")
25
-
24
+ raise AttributeError(
25
+ f"True expected for argument {item.__name__}.model_config.table, got {table_arg}"
26
+ )
27
+
26
28
  class SubService(ModelService):
29
+ __slot__ = ()
27
30
  __model__ = item
28
-
31
+
29
32
  return SubService
30
33
 
31
34
  def __init__(self, session: DefaultSession):
32
35
  self.__session_container__.set(session)
33
-
36
+
34
37
  @property
35
38
  def session(self) -> AsyncSession:
36
39
  _session = self.__session_container__.get()
@@ -45,6 +48,6 @@ class ModelService(AbstractService, Generic[Model], abstract=True):
45
48
  self.session.add(model)
46
49
  await self.session.flush()
47
50
  return model
48
-
49
- async def delete(self, model: Model) -> Model:
51
+
52
+ async def delete(self, model: Model) -> None:
50
53
  return await self.session.delete(model)
@@ -5,7 +5,7 @@ from typing import Annotated, AsyncGenerator, Literal, Self
5
5
 
6
6
  from fastapi.params import Depends
7
7
  from pydantic import AnyUrl, BaseModel, Field
8
- from sqlalchemy.ext.asyncio import create_async_engine
8
+ from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
9
9
  from sqlmodel.ext.asyncio.session import AsyncSession
10
10
 
11
11
  from fastapi_extra.dependency import AbstractComponent
@@ -16,13 +16,16 @@ class DatabaseConfig(BaseModel):
16
16
  url: AnyUrl
17
17
  echo: bool = False
18
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
19
+ isolation_level: (
20
+ Literal[
21
+ "SERIALIZABLE",
22
+ "REPEATABLE READ",
23
+ "READ COMMITTED",
24
+ "READ UNCOMMITTED",
25
+ "AUTOCOMMIT",
26
+ ]
27
+ | None
28
+ ) = None
26
29
  options: dict = Field(default_factory=dict)
27
30
 
28
31
 
@@ -36,18 +39,20 @@ _settings = DefaultDatabaseSettings() # type: ignore
36
39
  class SessionFactory(AbstractComponent):
37
40
  __slot__ = ("_engine",)
38
41
  default_config = _settings.datasource
39
-
42
+
40
43
  def __init__(self):
41
- self._engine = None
42
-
44
+ self._engine: AsyncEngine | None = None
45
+
43
46
  @classmethod
44
47
  def setup(cls, **options) -> Self:
45
48
  db = cls()
46
49
  db._engine = create_async_engine(
47
50
  url=str(cls.default_config.url),
48
- **cls.default_config.model_dump(exclude_defaults=True, exclude={"url", "options"}),
51
+ **cls.default_config.model_dump(
52
+ exclude_defaults=True, exclude={"url", "options"}
53
+ ),
49
54
  **cls.default_config.options,
50
- **options
55
+ **options,
51
56
  )
52
57
  return db
53
58
 
@@ -9,23 +9,19 @@ from fastapi import Depends, FastAPI, Request
9
9
 
10
10
 
11
11
  def async_wrapper(func: Callable):
12
-
12
+
13
13
  async def func_wrapper(*args, **kwds):
14
14
  return func(*args, **kwds)
15
-
15
+
16
16
  return update_wrapper(func_wrapper, func)
17
17
 
18
18
 
19
19
  class DependencyMetaClass(ABCMeta):
20
20
  __load__ = None
21
21
  __token__ = None
22
-
22
+
23
23
  def __new__(
24
- mcs,
25
- name: str,
26
- bases: tuple[type, ...],
27
- attrs: dict,
28
- abstract: bool = False
24
+ mcs, name: str, bases: tuple[type, ...], attrs: dict, abstract: bool = False
29
25
  ):
30
26
  new_cls = super().__new__(mcs, name, bases, attrs)
31
27
  new_cls.__token__ = f"{new_cls.__module__}.{new_cls.__name__}"
@@ -54,7 +50,9 @@ class AbstractComponent(metaclass=DependencyMetaClass, abstract=True):
54
50
  @final
55
51
  @classmethod
56
52
  async def __load__(cls, request: Request) -> Self:
57
- assert hasattr(request.app.state, cls.__token__), f"{cls.__name__} must be installed in lifespan"
53
+ assert hasattr(
54
+ request.app.state, cls.__token__
55
+ ), f"{cls.__name__} must be installed in lifespan"
58
56
  return getattr(request.app.state, cls.__token__)
59
57
 
60
58
  async def dispose(self) -> None:
@@ -16,10 +16,12 @@ class DataRange(BaseModel, Generic[C]):
16
16
 
17
17
  class ColumnExpression(BaseModel, Generic[S]):
18
18
  column_name: str = Field(title="列名")
19
- option: Literal["eq", "ne", "gt", "lt", "ge", "le"] = Field(default="eq", title="逻辑值")
19
+ option: Literal["eq", "ne", "gt", "lt", "ge", "le"] = Field(
20
+ default="eq", title="逻辑值"
21
+ )
20
22
  value: S = Field(title="参考值")
21
-
22
- @model_validator(mode="after")
23
+
24
+ @model_validator(mode="after") # type: ignore
23
25
  def validate_value(self):
24
26
  if self.value is None and self.option not in ("eq", "ne"):
25
27
  raise ValueError("NoneType is not comparable")
@@ -27,7 +29,7 @@ class ColumnExpression(BaseModel, Generic[S]):
27
29
 
28
30
  class WhereClause(BaseModel):
29
31
  option: Literal["and", "or"] = Field(default="and", title="关系")
30
- column_clauses: list[ColumnExpression | "WhereClause"]
32
+ column_clauses: list["ColumnExpression | WhereClause"]
31
33
 
32
34
 
33
35
  class Page(BaseModel, Generic[Schema]):
@@ -195,31 +195,39 @@ class APIResult(BaseModel, Generic[T]):
195
195
  data: T | None = Field(default=None, title="返回数据")
196
196
 
197
197
  if TYPE_CHECKING:
198
+
198
199
  @classmethod
199
200
  def ok(cls, data: T | None = None) -> "APIResult[T]":
200
201
  return APIResult(data=data)
202
+
201
203
  else:
204
+
202
205
  @classmethod
203
206
  def ok(cls, data: T | None = None) -> "APIResponse":
204
- return APIResponse(APIResult(data=data))
207
+ model = cls.__new__(cls)
208
+ model.__dict__["data"] = data
209
+ return APIResponse(model)
205
210
 
206
211
 
207
212
  class APIResponse(JSONResponse):
208
-
213
+
209
214
  def render(self, content: APIResult) -> bytes:
210
- return content.__pydantic_serializer__.to_json(
215
+ return APIResult.__pydantic_serializer__.to_json(
216
+ content,
211
217
  indent=None,
212
218
  by_alias=True,
213
219
  exclude_defaults=False,
214
220
  exclude_none=False,
215
- exclude_unset=False
221
+ exclude_unset=False,
216
222
  )
217
223
 
218
224
 
219
225
  class APIError(Exception):
220
226
  __slots__ = ("code", "message")
221
-
222
- def __init__(self, result: ResultEnum | None = None, code: str = "00000", message: str = "") -> None:
227
+
228
+ def __init__(
229
+ self, result: ResultEnum | None = None, code: str = "00000", message: str = ""
230
+ ) -> None:
223
231
  if result:
224
232
  self.code = result.value[0]
225
233
  self.message = message or result.value[1]
@@ -227,7 +235,7 @@ class APIError(Exception):
227
235
  self.code = code
228
236
  self.message = message
229
237
  super().__init__(self)
230
-
238
+
231
239
  def __str__(self) -> str:
232
240
  return self.message
233
241
 
@@ -0,0 +1,11 @@
1
+ __author__ = "ziyan.yin"
2
+ __describe__ = ""
3
+
4
+ from typing import Any
5
+
6
+ from fastapi import FastAPI
7
+
8
+ class RouteNode:
9
+ def add_route(self, fullpath: str, handler: Any): ...
10
+
11
+ def install(app: FastAPI) -> None: ...
@@ -11,18 +11,18 @@ from pydantic_settings import (BaseSettings, PydanticBaseSettingsSource,
11
11
 
12
12
  class Settings(BaseSettings):
13
13
  model_config = SettingsConfigDict(
14
- toml_file=["config.default.toml", "config.custom.toml"],
14
+ toml_file=["config.default.toml", "config.custom.toml"],
15
15
  validate_default=False,
16
- extra="ignore"
16
+ extra="ignore",
17
17
  )
18
-
18
+
19
19
  title: str = "FastAPI"
20
20
  version: str = "0.1.0"
21
21
  debug: bool = False
22
22
  root_path: str = ""
23
23
  include_in_schema: bool = True
24
24
  mode: Literal["dev", "test", "prod"] = "dev"
25
-
25
+
26
26
  @classmethod
27
27
  def settings_customise_sources(
28
28
  cls,
@@ -33,10 +33,10 @@ class Settings(BaseSettings):
33
33
  file_secret_settings: PydanticBaseSettingsSource,
34
34
  ) -> Tuple[PydanticBaseSettingsSource, ...]:
35
35
  return (
36
- TomlConfigSettingsSource(settings_cls),
37
- env_settings,
36
+ TomlConfigSettingsSource(settings_cls),
37
+ env_settings,
38
38
  init_settings,
39
- file_secret_settings
39
+ file_secret_settings,
40
40
  )
41
41
 
42
42
  @model_validator(mode="after")
@@ -9,7 +9,9 @@ from typing import Annotated, Any, TypeVar, Union
9
9
  from pydantic import BaseModel, PlainSerializer
10
10
  from sqlmodel import SQLModel
11
11
 
12
- Comparable = Union[int, float, decimal.Decimal, datetime.datetime, datetime.date, datetime.time]
12
+ Comparable = Union[
13
+ int, float, decimal.Decimal, datetime.datetime, datetime.date, datetime.time
14
+ ]
13
15
  Serializable = Union[Comparable, bool, str, None]
14
16
 
15
17
 
@@ -20,9 +22,8 @@ S = TypeVar("S", bound=Serializable)
20
22
  Schema = TypeVar("Schema", bound=BaseModel)
21
23
  Model = TypeVar("Model", bound=SQLModel)
22
24
 
23
- Cursor = Annotated[
24
- int, PlainSerializer(lambda x: str(x), return_type=str)
25
- ]
25
+ Cursor = Annotated[int, PlainSerializer(lambda x: str(x), return_type=str)]
26
26
  LocalDateTime = Annotated[
27
- datetime.datetime, PlainSerializer(lambda x: x.strftime("%Y-%m-%d %H:%M:%S"), return_type=str)
27
+ datetime.datetime,
28
+ PlainSerializer(lambda x: x.strftime("%Y-%m-%d %H:%M:%S"), return_type=str),
28
29
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-extra
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: extra package for fastapi.
5
5
  Author-email: Ziyan Yin <408856732@qq.com>
6
6
  License: BSD-3-Clause
@@ -1,8 +0,0 @@
1
- __author__ = "ziyan.yin"
2
- __describe__ = ""
3
-
4
-
5
- class Cursor:
6
-
7
- def next_val(self) -> int:
8
- ...
@@ -1,16 +0,0 @@
1
- __author__ = "ziyan.yin"
2
- __describe__ = ""
3
-
4
-
5
- from typing import Any
6
-
7
- from fastapi import FastAPI
8
-
9
- class RouteNode:
10
-
11
- def add_route(self, fullpath: str, handler: Any):
12
- ...
13
-
14
-
15
- def install(app: FastAPI) -> None:
16
- ...
File without changes
File without changes
File without changes