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.
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/PKG-INFO +1 -1
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/__init__.py +2 -2
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/cache/__init__.py +1 -3
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/cache/redis.py +9 -8
- fastapi_extra-0.2.5/fastapi_extra/cursor.pyi +8 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/database/__init__.py +1 -6
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/database/model.py +19 -10
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/database/service.py +14 -11
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/database/session.py +18 -13
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/dependency.py +7 -9
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/form.py +6 -4
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/response.py +15 -7
- fastapi_extra-0.2.5/fastapi_extra/routing.pyi +11 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/settings.py +7 -7
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/types.py +6 -5
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra.egg-info/PKG-INFO +1 -1
- fastapi_extra-0.2.3/fastapi_extra/cursor.pyi +0 -8
- fastapi_extra-0.2.3/fastapi_extra/routing.pyi +0 -16
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/LICENSE +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/README.rst +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/native/cursor.pyx +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/native/routing.pyx +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/py.typed +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra/utils.py +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra.egg-info/SOURCES.txt +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra.egg-info/dependency_links.txt +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra.egg-info/requires.txt +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/fastapi_extra.egg-info/top_level.txt +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/pyproject.toml +0 -0
- {fastapi_extra-0.2.3 → fastapi_extra-0.2.5}/setup.cfg +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "0.2.
|
|
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
|
|
@@ -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(
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
56
|
+
async with pool.get_client() as client:
|
|
56
57
|
yield client
|
|
57
58
|
|
|
58
59
|
|
|
@@ -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={
|
|
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={
|
|
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,
|
|
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]) ->
|
|
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(
|
|
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) ->
|
|
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:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 |
|
|
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
|
-
|
|
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
|
|
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__(
|
|
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
|
|
|
@@ -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[
|
|
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,
|
|
27
|
+
datetime.datetime,
|
|
28
|
+
PlainSerializer(lambda x: x.strftime("%Y-%m-%d %H:%M:%S"), return_type=str),
|
|
28
29
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|