fsrest 0.1.0__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.
@@ -0,0 +1,9 @@
1
+ /.venv/
2
+ /dist/
3
+ /build/
4
+ /.pytest_cache/
5
+ /.ruff_cache/
6
+ __pycache__/
7
+ *.py[cod]
8
+ *.egg-info/
9
+
fsrest-0.1.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 huoyinghui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
fsrest-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.5
2
+ Name: fsrest
3
+ Version: 0.1.0
4
+ Summary: Reusable, framework-agnostic REST CRUD logic for Pydantic applications
5
+ Project-URL: Homepage, https://github.com/pydtools/fsrest
6
+ Project-URL: Repository, https://github.com/pydtools/fsrest
7
+ Project-URL: Issues, https://github.com/pydtools/fsrest/issues
8
+ Author-email: huoyinghui <hyhlinux@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 huoyinghui
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+
31
+ License-File: LICENSE
32
+ Keywords: crud,dao,fastapi,pydantic,rest
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.9
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Typing :: Typed
42
+ Requires-Python: >=3.9
43
+ Requires-Dist: pydantic<3,>=1.10
44
+ Description-Content-Type: text/markdown
45
+
46
+ # fsrest
47
+
48
+ `fsrest` extracts reusable single-resource REST CRUD orchestration from application code. It is framework-independent: request/response objects are Pydantic models, while persistence is supplied through a small DAO protocol.
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install fsrest
54
+ ```
55
+
56
+ Python 3.9+ and Pydantic 1.10/2.x are supported.
57
+
58
+ ## Usage
59
+
60
+ Bind a DAO to `RestCrudLogicBase`. Request schemas provide the small conversion methods needed to turn HTTP-facing data into DAO fields.
61
+
62
+ ```python
63
+ from pydantic import BaseModel
64
+ from fsrest import RestCrudLogicBase, RestPageReqSchema, RestPageRespSchema
65
+
66
+ class Item(BaseModel):
67
+ id: str
68
+ name: str
69
+
70
+ class Filters(BaseModel):
71
+ name: str | None = None
72
+
73
+ class Ordering(BaseModel):
74
+ field: str = "id"
75
+
76
+ class PageData(BaseModel):
77
+ items: list[Item]
78
+ total: int
79
+
80
+ class ListQuery(RestPageReqSchema):
81
+ name: str | None = None
82
+
83
+ def build_filters(self) -> Filters:
84
+ return Filters(name=self.name)
85
+
86
+ def build_ordering(self) -> Ordering:
87
+ return Ordering()
88
+
89
+ def build_response(self, *, page_data: PageData) -> RestPageRespSchema[Item]:
90
+ return RestPageRespSchema[Item](
91
+ items=page_data.items,
92
+ total=page_data.total,
93
+ page=self.page,
94
+ page_size=self.page_size,
95
+ )
96
+
97
+ class ItemDao:
98
+ @classmethod
99
+ def list_schema_page(cls, *, filters, ordering, page, page_size) -> PageData:
100
+ ...
101
+
102
+ # Also implement get_schema_by_id, create_schema,
103
+ # update_schema_by_id, and delete_by_id.
104
+
105
+ class ItemLogic(RestCrudLogicBase):
106
+ dao_rest_crud = ItemDao
107
+ ```
108
+
109
+ The library raises `RestApiError` for missing records and failed deletes. To integrate with an application's existing exception middleware, subclass it and bind `error_class`:
110
+
111
+ ```python
112
+ class ApplicationApiError(RestApiError):
113
+ error_code = 400455
114
+
115
+ class ItemLogic(RestCrudLogicBase):
116
+ dao_rest_crud = ItemDao
117
+ error_class = ApplicationApiError
118
+ ```
119
+
120
+ ## Development and publishing
121
+
122
+ From the `pytools` repository root, use the unified release script:
123
+
124
+ ```bash
125
+ python make.py fsrest test
126
+ python make.py fsrest build
127
+ python make.py fsrest publish
128
+ ```
129
+
130
+ `publish` uploads the artifacts under `fsrest/dist/` using the PyPI credentials
131
+ configured in `~/.pypirc`. Before publishing a new release, update the version
132
+ in `pyproject.toml`, run tests, and build fresh artifacts.
133
+
fsrest-0.1.0/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # fsrest
2
+
3
+ `fsrest` extracts reusable single-resource REST CRUD orchestration from application code. It is framework-independent: request/response objects are Pydantic models, while persistence is supplied through a small DAO protocol.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install fsrest
9
+ ```
10
+
11
+ Python 3.9+ and Pydantic 1.10/2.x are supported.
12
+
13
+ ## Usage
14
+
15
+ Bind a DAO to `RestCrudLogicBase`. Request schemas provide the small conversion methods needed to turn HTTP-facing data into DAO fields.
16
+
17
+ ```python
18
+ from pydantic import BaseModel
19
+ from fsrest import RestCrudLogicBase, RestPageReqSchema, RestPageRespSchema
20
+
21
+ class Item(BaseModel):
22
+ id: str
23
+ name: str
24
+
25
+ class Filters(BaseModel):
26
+ name: str | None = None
27
+
28
+ class Ordering(BaseModel):
29
+ field: str = "id"
30
+
31
+ class PageData(BaseModel):
32
+ items: list[Item]
33
+ total: int
34
+
35
+ class ListQuery(RestPageReqSchema):
36
+ name: str | None = None
37
+
38
+ def build_filters(self) -> Filters:
39
+ return Filters(name=self.name)
40
+
41
+ def build_ordering(self) -> Ordering:
42
+ return Ordering()
43
+
44
+ def build_response(self, *, page_data: PageData) -> RestPageRespSchema[Item]:
45
+ return RestPageRespSchema[Item](
46
+ items=page_data.items,
47
+ total=page_data.total,
48
+ page=self.page,
49
+ page_size=self.page_size,
50
+ )
51
+
52
+ class ItemDao:
53
+ @classmethod
54
+ def list_schema_page(cls, *, filters, ordering, page, page_size) -> PageData:
55
+ ...
56
+
57
+ # Also implement get_schema_by_id, create_schema,
58
+ # update_schema_by_id, and delete_by_id.
59
+
60
+ class ItemLogic(RestCrudLogicBase):
61
+ dao_rest_crud = ItemDao
62
+ ```
63
+
64
+ The library raises `RestApiError` for missing records and failed deletes. To integrate with an application's existing exception middleware, subclass it and bind `error_class`:
65
+
66
+ ```python
67
+ class ApplicationApiError(RestApiError):
68
+ error_code = 400455
69
+
70
+ class ItemLogic(RestCrudLogicBase):
71
+ dao_rest_crud = ItemDao
72
+ error_class = ApplicationApiError
73
+ ```
74
+
75
+ ## Development and publishing
76
+
77
+ From the `pytools` repository root, use the unified release script:
78
+
79
+ ```bash
80
+ python make.py fsrest test
81
+ python make.py fsrest build
82
+ python make.py fsrest publish
83
+ ```
84
+
85
+ `publish` uploads the artifacts under `fsrest/dist/` using the PyPI credentials
86
+ configured in `~/.pypirc`. Before publishing a new release, update the version
87
+ in `pyproject.toml`, run tests, and build fresh artifacts.
88
+
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "fsrest"
7
+ version = "0.1.0"
8
+ description = "Reusable, framework-agnostic REST CRUD logic for Pydantic applications"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [
12
+ { name = "huoyinghui", email = "hyhlinux@gmail.com" },
13
+ ]
14
+ requires-python = ">=3.9"
15
+ dependencies = [
16
+ "pydantic>=1.10,<3",
17
+ ]
18
+ keywords = ["rest", "crud", "pydantic", "fastapi", "dao"]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Typing :: Typed",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/pydtools/fsrest"
33
+ Repository = "https://github.com/pydtools/fsrest"
34
+ Issues = "https://github.com/pydtools/fsrest/issues"
35
+
36
+ [dependency-groups]
37
+ dev = [
38
+ "build>=1.2",
39
+ "pytest>=7",
40
+ "ruff>=0.9",
41
+ ]
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/fsrest"]
45
+
46
+ [tool.pytest.ini_options]
47
+ pythonpath = ["src"]
48
+ testpaths = ["tests"]
49
+
50
+ [tool.ruff]
51
+ line-length = 120
52
+ target-version = "py39"
53
+
54
+ [tool.ruff.lint]
55
+ select = ["E", "F", "I", "UP"]
@@ -0,0 +1,28 @@
1
+ """Public API for fsrest."""
2
+
3
+ from .base import (
4
+ RestCreateReqProtocol,
5
+ RestCrudDaoProtocol,
6
+ RestCrudLogicBase,
7
+ RestIdReqProtocol,
8
+ RestListReqProtocol,
9
+ RestUpdateReqProtocol,
10
+ )
11
+ from .exceptions import APIError, RestApiError
12
+ from .schemas import RestCrudSchemaMapSchema, RestDeleteRespSchema, RestPageReqSchema, RestPageRespSchema
13
+
14
+ __all__ = [
15
+ "APIError",
16
+ "RestApiError",
17
+ "RestCreateReqProtocol",
18
+ "RestCrudDaoProtocol",
19
+ "RestCrudLogicBase",
20
+ "RestCrudSchemaMapSchema",
21
+ "RestDeleteRespSchema",
22
+ "RestIdReqProtocol",
23
+ "RestListReqProtocol",
24
+ "RestPageReqSchema",
25
+ "RestPageRespSchema",
26
+ "RestUpdateReqProtocol",
27
+ ]
28
+
@@ -0,0 +1,163 @@
1
+ """Reusable single-resource REST CRUD logic."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Generic, Protocol, TypeVar, cast
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from .exceptions import RestApiError
10
+ from .schemas import RestCrudSchemaMapSchema, RestDeleteRespSchema
11
+
12
+ RestItemSchemaT = TypeVar("RestItemSchemaT", bound=BaseModel)
13
+ RestListReqSchemaT = TypeVar("RestListReqSchemaT", bound=BaseModel)
14
+ RestListRespSchemaT = TypeVar("RestListRespSchemaT", bound=BaseModel)
15
+ RestListFilterSchemaT = TypeVar("RestListFilterSchemaT", bound=BaseModel)
16
+ RestOrderingSchemaT = TypeVar("RestOrderingSchemaT", bound=BaseModel)
17
+ RestPageDataSchemaT = TypeVar("RestPageDataSchemaT", bound=BaseModel)
18
+ RestGetReqSchemaT = TypeVar("RestGetReqSchemaT", bound=BaseModel)
19
+ RestCreateReqSchemaT = TypeVar("RestCreateReqSchemaT", bound=BaseModel)
20
+ RestCreateFieldsSchemaT = TypeVar("RestCreateFieldsSchemaT", bound=BaseModel)
21
+ RestUpdateReqSchemaT = TypeVar("RestUpdateReqSchemaT", bound=BaseModel)
22
+ RestUpdateFieldsSchemaT = TypeVar("RestUpdateFieldsSchemaT", bound=BaseModel)
23
+ RestDeleteReqSchemaT = TypeVar("RestDeleteReqSchemaT", bound=BaseModel)
24
+
25
+ ItemT_co = TypeVar("ItemT_co", bound=BaseModel, covariant=True)
26
+ FilterT_co = TypeVar("FilterT_co", bound=BaseModel, covariant=True)
27
+ FilterT_contra = TypeVar("FilterT_contra", bound=BaseModel, contravariant=True)
28
+ OrderingT_co = TypeVar("OrderingT_co", bound=BaseModel, covariant=True)
29
+ OrderingT_contra = TypeVar("OrderingT_contra", bound=BaseModel, contravariant=True)
30
+ PageDataT_co = TypeVar("PageDataT_co", bound=BaseModel, covariant=True)
31
+ PageDataT_contra = TypeVar("PageDataT_contra", bound=BaseModel, contravariant=True)
32
+ ListRespT_co = TypeVar("ListRespT_co", bound=BaseModel, covariant=True)
33
+ CreateFieldsT_co = TypeVar("CreateFieldsT_co", bound=BaseModel, covariant=True)
34
+ CreateFieldsT_contra = TypeVar("CreateFieldsT_contra", bound=BaseModel, contravariant=True)
35
+ UpdateFieldsT_co = TypeVar("UpdateFieldsT_co", bound=BaseModel, covariant=True)
36
+ UpdateFieldsT_contra = TypeVar("UpdateFieldsT_contra", bound=BaseModel, contravariant=True)
37
+
38
+
39
+ class RestListReqProtocol(Protocol[FilterT_co, OrderingT_co, PageDataT_contra, ListRespT_co]):
40
+ page: int
41
+ page_size: int
42
+
43
+ def build_filters(self) -> FilterT_co: ...
44
+
45
+ def build_ordering(self) -> OrderingT_co: ...
46
+
47
+ def build_response(self, *, page_data: PageDataT_contra) -> ListRespT_co: ...
48
+
49
+
50
+ class RestIdReqProtocol(Protocol):
51
+ id: str
52
+
53
+
54
+ class RestCreateReqProtocol(Protocol[CreateFieldsT_co]):
55
+ def build_create_fields(self) -> CreateFieldsT_co: ...
56
+
57
+
58
+ class RestUpdateReqProtocol(Protocol[UpdateFieldsT_co]):
59
+ id: str
60
+
61
+ def build_update_fields(self) -> UpdateFieldsT_co: ...
62
+
63
+
64
+ class RestCrudDaoProtocol(
65
+ Protocol[ItemT_co, FilterT_contra, OrderingT_contra, PageDataT_co, CreateFieldsT_contra, UpdateFieldsT_contra]
66
+ ):
67
+ @classmethod
68
+ def list_schema_page(
69
+ cls, *, filters: FilterT_contra, ordering: OrderingT_contra, page: int, page_size: int
70
+ ) -> PageDataT_co: ...
71
+
72
+ @classmethod
73
+ def get_schema_by_id(cls, item_id: str) -> ItemT_co | None: ...
74
+
75
+ @classmethod
76
+ def create_schema(cls, *, fields: CreateFieldsT_contra) -> ItemT_co: ...
77
+
78
+ @classmethod
79
+ def update_schema_by_id(cls, *, item_id: str, fields: UpdateFieldsT_contra) -> ItemT_co | None: ...
80
+
81
+ @classmethod
82
+ def delete_by_id(cls, item_id: str) -> bool: ...
83
+
84
+
85
+ class RestCrudLogicBase(
86
+ Generic[
87
+ RestItemSchemaT,
88
+ RestListReqSchemaT,
89
+ RestListRespSchemaT,
90
+ RestListFilterSchemaT,
91
+ RestOrderingSchemaT,
92
+ RestPageDataSchemaT,
93
+ RestGetReqSchemaT,
94
+ RestCreateReqSchemaT,
95
+ RestCreateFieldsSchemaT,
96
+ RestUpdateReqSchemaT,
97
+ RestUpdateFieldsSchemaT,
98
+ RestDeleteReqSchemaT,
99
+ ]
100
+ ):
101
+ """Base logic for a single-resource REST CRUD API.
102
+
103
+ Subclasses bind ``dao_rest_crud``. ``schema_map`` remains available for
104
+ endpoint generators and introspection, but the CRUD methods do not require it.
105
+ """
106
+
107
+ dao_rest_crud: type[
108
+ RestCrudDaoProtocol[
109
+ RestItemSchemaT,
110
+ RestListFilterSchemaT,
111
+ RestOrderingSchemaT,
112
+ RestPageDataSchemaT,
113
+ RestCreateFieldsSchemaT,
114
+ RestUpdateFieldsSchemaT,
115
+ ]
116
+ ]
117
+ schema_map: RestCrudSchemaMapSchema
118
+ error_class: type[RestApiError] = RestApiError
119
+
120
+ @classmethod
121
+ def list_items(cls, *, query: RestListReqSchemaT) -> RestListRespSchemaT:
122
+ contract = cast(
123
+ RestListReqProtocol[RestListFilterSchemaT, RestOrderingSchemaT, RestPageDataSchemaT, RestListRespSchemaT],
124
+ query,
125
+ )
126
+ page_data = cls.dao_rest_crud.list_schema_page(
127
+ filters=contract.build_filters(),
128
+ ordering=contract.build_ordering(),
129
+ page=contract.page,
130
+ page_size=contract.page_size,
131
+ )
132
+ return contract.build_response(page_data=page_data)
133
+
134
+ @classmethod
135
+ def get_item(cls, *, query: RestGetReqSchemaT) -> RestItemSchemaT:
136
+ contract = cast(RestIdReqProtocol, query)
137
+ item = cls.dao_rest_crud.get_schema_by_id(contract.id)
138
+ if item is None:
139
+ raise cls.error_class(f"Record not found: {contract.id}")
140
+ return item
141
+
142
+ @classmethod
143
+ def create_item(cls, *, payload: RestCreateReqSchemaT) -> RestItemSchemaT:
144
+ contract = cast(RestCreateReqProtocol[RestCreateFieldsSchemaT], payload)
145
+ return cls.dao_rest_crud.create_schema(fields=contract.build_create_fields())
146
+
147
+ @classmethod
148
+ def update_item(cls, *, payload: RestUpdateReqSchemaT) -> RestItemSchemaT:
149
+ contract = cast(RestUpdateReqProtocol[RestUpdateFieldsSchemaT], payload)
150
+ item = cls.dao_rest_crud.update_schema_by_id(item_id=contract.id, fields=contract.build_update_fields())
151
+ if item is None:
152
+ raise cls.error_class(f"Record not found: {contract.id}")
153
+ return item
154
+
155
+ @classmethod
156
+ def delete_item(cls, *, payload: RestDeleteReqSchemaT) -> RestDeleteRespSchema:
157
+ contract = cast(RestIdReqProtocol, payload)
158
+ if cls.dao_rest_crud.get_schema_by_id(contract.id) is None:
159
+ raise cls.error_class(f"Record not found: {contract.id}")
160
+ if not cls.dao_rest_crud.delete_by_id(contract.id):
161
+ raise cls.error_class(f"Failed to delete record: {contract.id}")
162
+ return RestDeleteRespSchema(id=contract.id, deleted=True)
163
+
@@ -0,0 +1,46 @@
1
+ """Exceptions raised by :mod:`fsrest`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class RestApiError(Exception):
9
+ """A framework-independent REST business error.
10
+
11
+ ``code`` and ``error_code`` are intentionally plain integers so an
12
+ application can translate the exception into its own HTTP response type.
13
+ """
14
+
15
+ code = 400
16
+ error_code = 400455
17
+ message = "REST operation failed"
18
+
19
+ def __init__(
20
+ self,
21
+ message: str | None = None,
22
+ *,
23
+ code: int | None = None,
24
+ error_code: int | None = None,
25
+ data: Any = None,
26
+ ) -> None:
27
+ self.message = message if message is not None else type(self).message
28
+ self.code = code if code is not None else type(self).code
29
+ self.error_code = error_code if error_code is not None else type(self).error_code
30
+ self.data = data
31
+ super().__init__(self.message)
32
+
33
+ @property
34
+ def msg(self) -> str:
35
+ """Compatibility alias used by applications that call the field ``msg``."""
36
+
37
+ return self.message
38
+
39
+ @msg.setter
40
+ def msg(self, value: str) -> None:
41
+ self.message = value
42
+ self.args = (value,)
43
+
44
+
45
+ # Compatibility with the original internal package.
46
+ APIError = RestApiError
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,49 @@
1
+ """Common Pydantic schemas used by REST CRUD logic."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Generic, TypeVar
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ RestItemSchemaT = TypeVar("RestItemSchemaT", bound=BaseModel)
10
+
11
+
12
+ class RestPageReqSchema(BaseModel):
13
+ """Page-number pagination input."""
14
+
15
+ page: int = Field(default=1, ge=1, le=100000, description="Page number, starting at 1")
16
+ page_size: int = Field(default=20, ge=1, le=200, description="Number of records per page")
17
+
18
+
19
+ class RestPageRespSchema(BaseModel, Generic[RestItemSchemaT]):
20
+ """Page-number pagination response."""
21
+
22
+ items: list[RestItemSchemaT] = Field(default_factory=list, description="Records on the current page")
23
+ total: int = Field(default=0, ge=0, description="Total number of records")
24
+ page: int = Field(default=1, ge=1, description="Current page number")
25
+ page_size: int = Field(default=20, ge=1, description="Number of records per page")
26
+
27
+
28
+ class RestDeleteRespSchema(BaseModel):
29
+ """Deletion response."""
30
+
31
+ id: str = Field(..., description="ID of the deleted record")
32
+ deleted: bool = Field(default=True, description="Whether the record was deleted")
33
+
34
+
35
+ class RestCrudSchemaMapSchema(BaseModel):
36
+ """Concrete schema types bound to a :class:`RestCrudLogicBase` subclass."""
37
+
38
+ item_schema: type[BaseModel]
39
+ list_req_schema: type[BaseModel]
40
+ list_resp_schema: type[BaseModel]
41
+ list_filter_schema: type[BaseModel]
42
+ ordering_schema: type[BaseModel]
43
+ page_data_schema: type[BaseModel]
44
+ get_req_schema: type[BaseModel]
45
+ create_req_schema: type[BaseModel]
46
+ create_fields_schema: type[BaseModel]
47
+ update_req_schema: type[BaseModel]
48
+ update_fields_schema: type[BaseModel]
49
+ delete_req_schema: type[BaseModel]