muffin-rest 11.0.3__py3-none-any.whl → 12.0.2__py3-none-any.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.
muffin_rest/api.py CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import dataclasses as dc
6
6
  from pathlib import Path
7
- from typing import TYPE_CHECKING, Any, Callable, Optional, Union, overload
7
+ from typing import TYPE_CHECKING, Any, Callable, overload
8
8
 
9
9
  from http_router import Router
10
10
  from muffin.utils import TV, to_awaitable
@@ -27,11 +27,11 @@ class API:
27
27
 
28
28
  def __init__(
29
29
  self,
30
- app: Optional[muffin.Application] = None,
30
+ app: muffin.Application | None = None,
31
31
  prefix: str = "",
32
32
  *,
33
33
  openapi: bool = True,
34
- servers: Optional[list] = None,
34
+ servers: list | None = None,
35
35
  **openapi_info,
36
36
  ):
37
37
  """Post initialize the API if we have an application already."""
@@ -66,8 +66,8 @@ class API:
66
66
  app: muffin.Application,
67
67
  *,
68
68
  prefix: str = "",
69
- openapi: Optional[bool] = None,
70
- servers: Optional[list] = None,
69
+ openapi: bool | None = None,
70
+ servers: list | None = None,
71
71
  **openapi_info,
72
72
  ):
73
73
  """Initialize the API."""
@@ -100,16 +100,12 @@ class API:
100
100
  self.router.route("/openapi.json")(openapi_json)
101
101
 
102
102
  @overload
103
- def route(self, obj: str, *paths: str, **params) -> Callable[[TV], TV]:
104
- ...
103
+ def route(self, obj: str, *paths: str, **params) -> Callable[[TV], TV]: ...
105
104
 
106
105
  @overload
107
- def route(self, obj: TVHandler, *paths: str, **params) -> TVHandler:
108
- ...
106
+ def route(self, obj: TVHandler, *paths: str, **params) -> TVHandler: ...
109
107
 
110
- def route(
111
- self, obj: Union[str, TVHandler], *paths: str, **params
112
- ) -> Union[Callable[[TV], TV], TVHandler]:
108
+ def route(self, obj: str | TVHandler, *paths: str, **params) -> Callable[[TV], TV] | TVHandler:
113
109
  """Route an endpoint by the API."""
114
110
  from .handler import RESTBase
115
111
 
muffin_rest/errors.py CHANGED
@@ -1,9 +1,10 @@
1
1
  """Helpers to raise API errors as JSON responses."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import json
5
6
  from http import HTTPStatus
6
- from typing import TYPE_CHECKING, Optional
7
+ from typing import TYPE_CHECKING
7
8
 
8
9
  from muffin import ResponseError
9
10
 
@@ -16,7 +17,7 @@ class APIError(ResponseError):
16
17
 
17
18
  def __init__(
18
19
  self,
19
- content: Optional[TJSON] = None,
20
+ content: TJSON | None = None,
20
21
  *,
21
22
  status_code: int = HTTPStatus.BAD_REQUEST.value,
22
23
  **json_data,
muffin_rest/handler.py CHANGED
@@ -9,9 +9,7 @@ from typing import (
9
9
  Iterable,
10
10
  Literal,
11
11
  Mapping,
12
- Optional,
13
12
  Sequence,
14
- Union,
15
13
  cast,
16
14
  overload,
17
15
  )
@@ -31,7 +29,7 @@ from muffin_rest.types import TSchemaRes
31
29
 
32
30
  from .errors import HandlerNotBindedError
33
31
  from .options import RESTOptions
34
- from .types import TVCollection, TVData, TVResource
32
+ from .types import TVCollection, TVResource
35
33
 
36
34
 
37
35
  class RESTHandlerMeta(HandlerMeta):
@@ -58,22 +56,22 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
58
56
 
59
57
  meta: RESTOptions
60
58
  meta_class: type[RESTOptions] = RESTOptions
61
- _api: Optional[API] = None
59
+ _api: API | None = None
62
60
 
63
- filters: Optional[dict[str, Any]] = None
64
- sorting: Optional[dict[str, Any]] = None
61
+ filters: dict[str, Any] | None = None
62
+ sorting: dict[str, Any] | None = None
65
63
 
66
64
  class Meta:
67
65
  """Tune the handler."""
68
66
 
69
67
  # Resource filters
70
- filters: Sequence[Union[str, tuple[str, str], Filter]] = ()
68
+ filters: Sequence[str | tuple[str, str] | Filter] = ()
71
69
 
72
70
  # Define allowed resource sorting params
73
- sorting: Sequence[Union[str, tuple[str, dict], Sort]] = ()
71
+ sorting: Sequence[str | tuple[str, dict] | Sort] = ()
74
72
 
75
73
  # Serialize/Deserialize Schema class
76
- Schema: Optional[type[ma.Schema]] = None
74
+ Schema: type[ma.Schema] | None = None
77
75
 
78
76
  @classmethod
79
77
  def __route__(cls, router, *paths, **params):
@@ -84,9 +82,7 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
84
82
 
85
83
  else:
86
84
  router.bind(cls, f"/{ cls.meta.name }", methods=methods, **params)
87
- router.bind(
88
- cls, f"/{ cls.meta.name }/{{{ cls.meta.name_id }}}", methods=methods, **params
89
- )
85
+ router.bind(cls, f"/{ cls.meta.name }/{{pk}}", methods=methods, **params)
90
86
 
91
87
  for _, method in inspect.getmembers(cls, lambda m: hasattr(m, "__route__")):
92
88
  paths, methods = method.__route__
@@ -94,7 +90,7 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
94
90
 
95
91
  return cls
96
92
 
97
- async def __call__(self, request: Request, *, method_name: Optional[str] = None, **_) -> Any:
93
+ async def __call__(self, request: Request, *, method_name: str | None = None, **_) -> Any:
98
94
  """Dispatch the given request by HTTP method."""
99
95
  self.auth = await self.authorize(request)
100
96
 
@@ -151,7 +147,7 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
151
147
 
152
148
  async def prepare_resource(self, request: Request) -> Any:
153
149
  """Load a resource."""
154
- return request["path_params"].get(self.meta.name_id)
150
+ return request["path_params"].get("pk")
155
151
 
156
152
  async def filter(self, request: Request, collection: TVCollection) -> tuple[TVCollection, Any]:
157
153
  """Filter the collection."""
@@ -190,7 +186,7 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
190
186
  @abc.abstractmethod
191
187
  async def paginate(
192
188
  self, request: Request, *, limit: int = 0, offset: int = 0
193
- ) -> tuple[Any, Optional[int]]:
189
+ ) -> tuple[Any, int | None]:
194
190
  """Paginate the results."""
195
191
  raise NotImplementedError
196
192
 
@@ -215,7 +211,7 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
215
211
  # Parse data
216
212
  # -----------
217
213
  def get_schema(
218
- self, request: Request, *, resource: Optional[TVResource] = None, **schema_options
214
+ self, request: Request, *, resource: TVResource | None = None, **schema_options
219
215
  ) -> ma.Schema:
220
216
  """Initialize marshmallow schema for serialization/deserialization."""
221
217
  query = request.url.query
@@ -232,36 +228,37 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
232
228
 
233
229
  return data
234
230
 
235
- async def load(
236
- self, request: Request, resource: Optional[TVResource] = None, **schema_options
237
- ) -> TVData[TVResource]:
231
+ async def load(self, request: Request, resource: TVResource | None = None, **schema_options):
238
232
  """Load data from request and create/update a resource."""
239
233
  schema = self.get_schema(request, resource=resource, **schema_options)
240
- data = cast("Union[Mapping, list]", await self.load_data(request))
234
+ data = cast("Mapping | list", await self.load_data(request))
241
235
  return cast(
242
- "TVData[TVResource]", await load_data(data, schema, partial=resource is not None)
236
+ "TVResource | list[TVResource]",
237
+ await load_data(data, schema, partial=resource is not None),
243
238
  )
244
239
 
245
240
  @overload
246
241
  async def dump( # type: ignore[misc]
247
- self, request, data: TVData, *, many: Literal[True]
242
+ self, request, data: TVResource | Iterable[TVResource], *, many: Literal[True]
248
243
  ) -> list[TSchemaRes]: ...
249
244
 
250
245
  @overload
251
- async def dump(self, request, data: TVData, *, many: bool = False) -> TSchemaRes: ...
246
+ async def dump(
247
+ self, request, data: TVResource | Iterable[TVResource], *, many: bool = False
248
+ ) -> TSchemaRes: ...
252
249
 
253
250
  async def dump(
254
251
  self,
255
252
  request: Request,
256
- data: Union[TVResource, Iterable[TVResource]],
253
+ data: TVResource | Iterable[TVResource],
257
254
  *,
258
255
  many: bool = False,
259
- ) -> Union[TSchemaRes, list[TSchemaRes]]:
256
+ ) -> TSchemaRes | list[TSchemaRes]:
260
257
  """Serialize the given response."""
261
258
  schema = self.get_schema(request)
262
259
  return schema.dump(data, many=many)
263
260
 
264
- async def get(self, request: Request, *, resource: Optional[TVResource] = None) -> ResponseJSON:
261
+ async def get(self, request: Request, *, resource: TVResource | None = None) -> ResponseJSON:
265
262
  """Get a resource or a collection of resources.
266
263
 
267
264
  Specify a path param to load a resource.
@@ -273,9 +270,7 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
273
270
  )
274
271
  return ResponseJSON(res) # type: ignore[type-var]
275
272
 
276
- async def post(
277
- self, request: Request, *, resource: Optional[TVResource] = None
278
- ) -> ResponseJSON:
273
+ async def post(self, request: Request, *, resource: TVResource | None = None) -> ResponseJSON:
279
274
  """Create a resource.
280
275
 
281
276
  The method accepts a single resource's data or a list of resources to create.
@@ -290,14 +285,14 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
290
285
  res = await self.dump(request, data, many=many)
291
286
  return ResponseJSON(res)
292
287
 
293
- async def put(self, request: Request, *, resource: Optional[TVResource] = None) -> ResponseJSON:
288
+ async def put(self, request: Request, *, resource: TVResource | None = None) -> ResponseJSON:
294
289
  """Update a resource."""
295
290
  if resource is None:
296
291
  raise APIError.NOT_FOUND()
297
292
 
298
293
  return await self.post(request, resource=resource)
299
294
 
300
- async def delete(self, request: Request, resource: Optional[TVResource] = None):
295
+ async def delete(self, request: Request, resource: TVResource | None = None):
301
296
  """Delete a resource."""
302
297
  if resource is None:
303
298
  raise APIError.NOT_FOUND()
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Optional, Union
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  from marshmallow import Schema, ValidationError
6
6
 
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
10
10
  from collections.abc import Mapping
11
11
 
12
12
 
13
- async def load_data(data: Union[Mapping, list], schema: Optional[Schema] = None, **params):
13
+ async def load_data(data: Mapping | list, schema: Schema | None = None, **params):
14
14
  if schema is None:
15
15
  return data
16
16
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Optional, cast
5
+ from typing import TYPE_CHECKING, cast
6
6
 
7
7
  import bson
8
8
  from bson.errors import InvalidId
@@ -29,7 +29,7 @@ class MongoRESTOptions(RESTOptions):
29
29
  sorting_cls: type[MongoSorting] = MongoSorting
30
30
  schema_base: type[MongoSchema] = MongoSchema
31
31
 
32
- aggregate: Optional[list] = None # Support aggregation. Set to pipeline.
32
+ aggregate: list | None = None # Support aggregation. Set to pipeline.
33
33
  collection_id: str = "_id"
34
34
  collection: motor.AsyncIOMotorCollection
35
35
 
@@ -58,7 +58,7 @@ class MongoRESTHandler(RESTHandler[TVResource]):
58
58
 
59
59
  async def paginate(
60
60
  self, _: Request, *, limit: int = 0, offset: int = 0
61
- ) -> tuple[motor.AsyncIOMotorCursor, Optional[int]]:
61
+ ) -> tuple[motor.AsyncIOMotorCursor, int | None]:
62
62
  """Paginate collection."""
63
63
  if self.meta.aggregate:
64
64
  pipeline_all = [*self.meta.aggregate, {"$skip": offset}, {"$limit": limit}]
@@ -76,7 +76,7 @@ class MongoRESTHandler(RESTHandler[TVResource]):
76
76
  total = await self.collection.count()
77
77
  return self.collection.skip(offset).limit(limit), total
78
78
 
79
- async def get(self, request, *, resource: Optional[TVResource] = None):
79
+ async def get(self, request, *, resource: TVResource | None = None):
80
80
  """Get resource or collection of resources."""
81
81
  if resource:
82
82
  return await self.dump(request, resource)
@@ -84,9 +84,9 @@ class MongoRESTHandler(RESTHandler[TVResource]):
84
84
  docs = await self.collection.to_list(None)
85
85
  return await self.dump(request, docs, many=True)
86
86
 
87
- async def prepare_resource(self, request: Request) -> Optional[TVResource]:
87
+ async def prepare_resource(self, request: Request) -> TVResource | None:
88
88
  """Load a resource."""
89
- pk = request["path_params"].get(self.meta.name_id)
89
+ pk = request["path_params"].get("pk")
90
90
  if not pk:
91
91
  return None
92
92
 
@@ -98,7 +98,7 @@ class MongoRESTHandler(RESTHandler[TVResource]):
98
98
  raise APIError.NOT_FOUND() from exc
99
99
 
100
100
  def get_schema(
101
- self, request: Request, resource: Optional[TVResource] = None, **schema_options
101
+ self, request: Request, resource: TVResource | None = None, **schema_options
102
102
  ) -> ma.Schema:
103
103
  """Initialize marshmallow schema for serialization/deserialization."""
104
104
  return super().get_schema(request, instance=resource, **schema_options)
@@ -116,7 +116,7 @@ class MongoRESTHandler(RESTHandler[TVResource]):
116
116
 
117
117
  return resource
118
118
 
119
- async def delete(self, request: Request, resource: Optional[TVResource] = None):
119
+ async def delete(self, request: Request, resource: TVResource | None = None):
120
120
  """Remove the given resource(s)."""
121
121
  meta = self.meta
122
122
  oids = (
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Awaitable, Union
5
+ from typing import TYPE_CHECKING, Awaitable
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from motor import motor_asyncio as motor
@@ -58,7 +58,7 @@ class MongoChain:
58
58
 
59
59
  def find(
60
60
  self,
61
- query: Union[list, dict, None] = None,
61
+ query: list | dict | None = None,
62
62
  projection=None,
63
63
  ) -> MongoChain:
64
64
  """Store filters in self."""
@@ -68,7 +68,7 @@ class MongoChain:
68
68
 
69
69
  def find_one(
70
70
  self,
71
- query: Union[list, dict, None] = None,
71
+ query: list | dict | None = None,
72
72
  projection=None,
73
73
  ) -> Awaitable:
74
74
  """Apply filters and return cursor."""
muffin_rest/openapi.py CHANGED
@@ -199,9 +199,7 @@ class OpenAPIMixin:
199
199
  schema_ref = {"$ref": f"#/components/schemas/{ meta.Schema.__name__ }"}
200
200
  for method in route_to_methods(route):
201
201
  operations[method] = {"tags": [tags[cls]]}
202
- is_resource_route = isinstance(route, DynamicRoute) and route.params.get(
203
- meta.name_id,
204
- )
202
+ is_resource_route = isinstance(route, DynamicRoute) and route.params.get("pk")
205
203
 
206
204
  if method == "get" and not is_resource_route:
207
205
  operations[method]["parameters"] = []
muffin_rest/options.py CHANGED
@@ -12,7 +12,6 @@ class RESTOptions:
12
12
  """Handler Options."""
13
13
 
14
14
  name: str = ""
15
- name_id: str = "id"
16
15
  base_property: str = "name"
17
16
 
18
17
  # Pagination
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import operator
6
6
  from functools import reduce
7
- from typing import TYPE_CHECKING, ClassVar, Union, cast
7
+ from typing import TYPE_CHECKING, ClassVar, cast
8
8
 
9
9
  from peewee import ColumnBase, Field, ModelSelect
10
10
 
@@ -54,7 +54,7 @@ class PWFilters(Filters):
54
54
 
55
55
  MUTATE_CLASS: type[PWFilter] = PWFilter
56
56
 
57
- def convert(self, obj: Union[str, Field, PWFilter], **meta):
57
+ def convert(self, obj: str | Field | PWFilter, **meta):
58
58
  """Convert params to filters."""
59
59
 
60
60
  handler = cast("PWRESTHandler", self.handler)
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any, Optional, Union, cast, overload
5
+ from typing import TYPE_CHECKING, Any, cast, overload
6
6
 
7
7
  import marshmallow as ma
8
8
  from apispec.ext.marshmallow import MarshmallowPlugin
@@ -34,7 +34,7 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
34
34
 
35
35
  if TYPE_CHECKING:
36
36
  resource: TVModel
37
- collection: Union[AIOModelSelect, pw.ModelSelect]
37
+ collection: AIOModelSelect | pw.ModelSelect
38
38
 
39
39
  meta: PWRESTOptions
40
40
  meta_class: type[PWRESTOptions] = PWRESTOptions
@@ -56,9 +56,9 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
56
56
  """Initialize Peeewee QuerySet for a binded to the resource model."""
57
57
  return self.meta.model.select()
58
58
 
59
- async def prepare_resource(self, request: Request) -> Optional[TVModel]:
59
+ async def prepare_resource(self, request: Request) -> TVModel | None:
60
60
  """Load a resource."""
61
- pk = request["path_params"].get(self.meta.name_id)
61
+ pk = request["path_params"].get("pk")
62
62
  if not pk:
63
63
  return None
64
64
 
@@ -101,7 +101,7 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
101
101
 
102
102
  return self.collection.offset(offset).limit(limit), count
103
103
 
104
- async def get(self, request, *, resource: Optional[TVModel] = None) -> Any:
104
+ async def get(self, request, *, resource: TVModel | None = None) -> Any:
105
105
  """Get resource or collection of resources."""
106
106
  if resource:
107
107
  return await self.dump(request, resource)
@@ -120,7 +120,7 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
120
120
 
121
121
  return resource
122
122
 
123
- async def remove(self, request: Request, resource: Optional[TVModel] = None):
123
+ async def remove(self, request: Request, resource: TVModel | None = None):
124
124
  """Remove the given resource."""
125
125
  meta = self.meta
126
126
  if resource:
@@ -147,11 +147,11 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
147
147
 
148
148
  return resource.get_id() if resource else [r.get_id() for r in resources]
149
149
 
150
- async def delete(self, request: Request, resource: Optional[TVModel] = None): # type: ignore[override]
150
+ async def delete(self, request: Request, resource: TVModel | None = None): # type: ignore[override]
151
151
  return await self.remove(request, resource)
152
152
 
153
153
  def get_schema(
154
- self, request: Request, *, resource: Optional[TVModel] = None, **schema_options
154
+ self, request: Request, *, resource: TVModel | None = None, **schema_options
155
155
  ) -> ma.Schema:
156
156
  """Initialize marshmallow schema for serialization/deserialization."""
157
157
  return super().get_schema(request, instance=resource, **schema_options)
@@ -22,7 +22,7 @@ class PeeweeOpenAPIMixin(OpenAPIMixin):
22
22
  def openapi(cls, route: Route, spec: APISpec, tags: dict) -> dict:
23
23
  """Get openapi specs for the endpoint."""
24
24
  operations = super(PeeweeOpenAPIMixin, cls).openapi(route, spec, tags)
25
- is_resource_route = getattr(route, "params", {}).get(cls.meta.name_id)
25
+ is_resource_route = getattr(route, "params", {}).get("pk")
26
26
  if not is_resource_route and "delete" in operations:
27
27
  operations["delete"].setdefault("parameters", [])
28
28
  operations["delete"]["requestBody"] = {
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Union, cast
5
+ from typing import TYPE_CHECKING, cast
6
6
 
7
7
  from peewee import Field
8
8
 
@@ -35,7 +35,7 @@ class PWSorting(Sorting):
35
35
  """Prepare collection for sorting."""
36
36
  return collection.order_by()
37
37
 
38
- def convert(self, obj: Union[str, Field, PWSort], **meta):
38
+ def convert(self, obj: str | Field | PWSort, **meta):
39
39
  """Prepare sorters."""
40
40
 
41
41
  if isinstance(obj, PWSort):
@@ -2,14 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Optional
5
+ from typing import TYPE_CHECKING
6
6
  from warnings import warn
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from peewee import Field
10
10
 
11
11
 
12
- def get_model_field_by_name(handler, name: str, *, stacklevel=5) -> Optional[Field]:
12
+ def get_model_field_by_name(handler, name: str, *, stacklevel=5) -> Field | None:
13
13
  """Get model field by name."""
14
14
  fields = handler.meta.model._meta.fields
15
15
  candidate = fields.get(name)
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any, Optional, cast
5
+ from typing import TYPE_CHECKING, Any, cast
6
6
 
7
7
  import marshmallow as ma
8
8
  import sqlalchemy as sa
@@ -18,7 +18,6 @@ if TYPE_CHECKING:
18
18
  from muffin import Request
19
19
  from muffin_databases import Plugin as Database
20
20
 
21
- from muffin_rest.types import TVData
22
21
 
23
22
  from .types import TVResource
24
23
 
@@ -101,7 +100,7 @@ class SARESTOptions(RESTOptions):
101
100
  "unknown": self.schema_unknown,
102
101
  "table": self.table,
103
102
  "include_fk": True,
104
- "dump_only": (self.name_id,),
103
+ "dump_only": (self.table_pk.name,),
105
104
  },
106
105
  **self.schema_meta,
107
106
  ),
@@ -125,7 +124,7 @@ class SARESTHandler(RESTHandler[TVResource]):
125
124
  *,
126
125
  limit: int = 0,
127
126
  offset: int = 0,
128
- ) -> tuple[sa.sql.Select, Optional[int]]:
127
+ ) -> tuple[sa.sql.Select, int | None]:
129
128
  """Paginate the collection."""
130
129
  sqs = self.collection.order_by(None).subquery()
131
130
  qs = sa.select(sa.func.count()).select_from(sqs)
@@ -134,7 +133,7 @@ class SARESTHandler(RESTHandler[TVResource]):
134
133
  total = await self.meta.database.fetch_val(qs)
135
134
  return self.collection.offset(offset).limit(limit), total
136
135
 
137
- async def get(self, request, *, resource: Optional[TVResource] = None) -> Any:
136
+ async def get(self, request, *, resource: TVResource | None = None) -> Any:
138
137
  """Get resource or collection of resources."""
139
138
  if resource:
140
139
  return await self.dump(request, resource)
@@ -142,9 +141,9 @@ class SARESTHandler(RESTHandler[TVResource]):
142
141
  rows = await self.meta.database.fetch_all(self.collection)
143
142
  return await self.dump(request, rows, many=True)
144
143
 
145
- async def prepare_resource(self, request: Request) -> Optional[TVResource]:
144
+ async def prepare_resource(self, request: Request) -> TVResource | None:
146
145
  """Load a resource."""
147
- pk = request["path_params"].get(self.meta.name_id)
146
+ pk = request["path_params"].get("pk")
148
147
  if not pk:
149
148
  return None
150
149
 
@@ -155,12 +154,12 @@ class SARESTHandler(RESTHandler[TVResource]):
155
154
  return cast("TVResource", dict(resource))
156
155
 
157
156
  def get_schema(
158
- self, request: Request, *, resource: Optional[TVResource] = None, **schema_options
157
+ self, request: Request, *, resource: TVResource | None = None, **schema_options
159
158
  ) -> ma.Schema:
160
159
  """Initialize marshmallow schema for serialization/deserialization."""
161
160
  return super().get_schema(request, instance=resource, **schema_options)
162
161
 
163
- async def save(self, _: Request, resource: TVData[TVResource], *, update=False):
162
+ async def save(self, request: Request, resource: TVResource, *, update=False):
164
163
  """Save the given resource."""
165
164
  meta = self.meta
166
165
  insert_query = meta.table.insert()
@@ -174,7 +173,7 @@ class SARESTHandler(RESTHandler[TVResource]):
174
173
 
175
174
  return resource
176
175
 
177
- async def remove(self, request: Request, resource: Optional[TVResource] = None):
176
+ async def remove(self, request: Request, resource: TVResource | None = None):
178
177
  """Remove the given resource."""
179
178
  table_pk = cast("sa.Column", self.meta.table_pk)
180
179
  pks = [resource[table_pk.name]] if resource else await request.data()
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any, ClassVar, Union, cast
5
+ from typing import TYPE_CHECKING, Any, ClassVar, cast
6
6
 
7
7
  from sqlalchemy import Column
8
8
 
@@ -50,7 +50,7 @@ class SAFilters(Filters):
50
50
 
51
51
  MUTATE_CLASS = SAFilter
52
52
 
53
- def convert(self, obj: Union[str, Column, SAFilter], **meta):
53
+ def convert(self, obj: str | Column | SAFilter, **meta):
54
54
  """Convert params to filters."""
55
55
 
56
56
  handler = cast("SARESTHandler", self.handler)
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Union, cast
5
+ from typing import TYPE_CHECKING, cast
6
6
 
7
7
  from sqlalchemy import Column
8
8
 
@@ -36,7 +36,7 @@ class SASorting(Sorting):
36
36
 
37
37
  MUTATE_CLASS = SASort
38
38
 
39
- def convert(self, obj: Union[str, Column, SASort], **meta):
39
+ def convert(self, obj: str | Column | SASort, **meta):
40
40
  """Prepare sorters."""
41
41
 
42
42
  if isinstance(obj, SASort):
muffin_rest/types.py CHANGED
@@ -4,7 +4,6 @@ from typing import (
4
4
  Awaitable,
5
5
  Callable,
6
6
  TypeVar,
7
- Union,
8
7
  )
9
8
 
10
9
  if TYPE_CHECKING:
@@ -14,7 +13,6 @@ from muffin import Request
14
13
 
15
14
  TVCollection = TypeVar("TVCollection", bound=Any)
16
15
  TVResource = TypeVar("TVResource", bound=Any)
17
- TVData = Union[TVResource, list[TVResource]]
18
16
  TAuth = Callable[[Request], Awaitable]
19
17
  TVAuth = TypeVar("TVAuth", bound=TAuth)
20
18
  TVHandler = TypeVar("TVHandler", bound=type["RESTBase"])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: muffin-rest
3
- Version: 11.0.3
3
+ Version: 12.0.2
4
4
  Summary: The package provides enhanced support for writing REST APIs with Muffin framework
5
5
  License: MIT
6
6
  Keywords: rest,api,muffin,asgi,asyncio,trio
@@ -0,0 +1,39 @@
1
+ muffin_rest/__init__.py,sha256=JnwsHQNDmJpOqETeIjjPheG891T5WQ0JBaiEV72Wcmg,1242
2
+ muffin_rest/api.py,sha256=bdbXMPiZi5yXf7_u3XB_gaGDHZKzDPqnnFnrwHoO1Rs,3799
3
+ muffin_rest/errors.py,sha256=Todg7CrCE1ufOsyyARI2Bv-RqNPH4Rl1p-Tkpq7VYrA,1151
4
+ muffin_rest/filters.py,sha256=nWlwDLuId-mmP3tgZjp1joYCVx0k9ekV42fPIMMKphM,5920
5
+ muffin_rest/handler.py,sha256=qpG4ioIagdYqGFOrlT7pBx-0czgS2ZI3AvqKXz9EzcA,10762
6
+ muffin_rest/limits.py,sha256=Fnlu4Wj3B-BzpahLK-rDbd1GEd7CEQ3zxyOD0vee7GE,2007
7
+ muffin_rest/marshmallow.py,sha256=-MyMKiaMTfiWw4y-4adpfoQd05HV1vEKqSXJh8eD3xg,548
8
+ muffin_rest/mongo/__init__.py,sha256=oPfTF_cG4LD1TDm-RoyxhSS1pVDXConCKQf9rz2QlZM,4644
9
+ muffin_rest/mongo/filters.py,sha256=yIxIDVqMn6SoDgVhCqiTxYetw0hoaf_3jIvX2Vnizok,964
10
+ muffin_rest/mongo/schema.py,sha256=y4OEPQnlV_COTIIQ3cKmpqDpD2r18eAWn0rijQldWm0,1205
11
+ muffin_rest/mongo/sorting.py,sha256=iJBnaFwE7g_JMwpGpQkoqSqbQK9XULx1K3skiRRgLgY,870
12
+ muffin_rest/mongo/types.py,sha256=jaODScgwwYbzHis3DY4bPzU1ahiMJMSwquH5_Thi-Gg,200
13
+ muffin_rest/mongo/utils.py,sha256=jirD2Hxk28Wadpq_F08t40dYBygq6W1_0cD3261kCgo,3949
14
+ muffin_rest/openapi.py,sha256=VrkyZr_T0ykAQsvkFQ_yarZ-Zs55AmAGf4aNLsUdjGA,8715
15
+ muffin_rest/options.py,sha256=Mzv0mq6YylL6cNtad9JhEhkmA4RgmDsu-On2hn5VEiw,2208
16
+ muffin_rest/peewee/__init__.py,sha256=94DSj_ftT6fbPksHlBv40AH2HWaiZommUFOMN2jd9a4,129
17
+ muffin_rest/peewee/filters.py,sha256=8CqleDEn4vVQvBLXEaGOjM2N7sY117EQ4GThPhlDPE8,2494
18
+ muffin_rest/peewee/handler.py,sha256=qu-ma1zhMGvzVbqpBEdSkify2UbffT58SLjWUoT_1ws,5418
19
+ muffin_rest/peewee/openapi.py,sha256=ktgsmB9wFNvcTbTaDkMQFLdQxk3osDFW5LkUSFVjcQo,1093
20
+ muffin_rest/peewee/options.py,sha256=TimJtErC9e8B7BRiEkHiBZd71_bZbYr-FE2PIlQvfH0,1455
21
+ muffin_rest/peewee/schemas.py,sha256=hQG2UKR7gEMsz0n75L3tvkVzUX3baXMJ94emgIc1Scw,439
22
+ muffin_rest/peewee/sorting.py,sha256=aBtQkj3iSo6XjBZ8evJuprIkhv1nHFi7wFb0an6Dn34,1963
23
+ muffin_rest/peewee/types.py,sha256=cgCXhpGHkImKwudA1lulZHz5oJswHH168AiW5MhZRCM,155
24
+ muffin_rest/peewee/utils.py,sha256=8wTSKmjeD3qtF5NFHX1wkBn4jiXFvCzNEK9WW7r_JmI,693
25
+ muffin_rest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
+ muffin_rest/redoc.html,sha256=GtuHIMvTuSi8Ro6bgI-G8VB94AljMyfjcZseqtBmGCY,559
27
+ muffin_rest/schemas.py,sha256=BW3dF82C6Q6STs4tZjej1x8Ii1rI3EZUJZR4mNNKmu4,875
28
+ muffin_rest/sorting.py,sha256=nu4SGW5uxLETJIywE-1UTCCya9QAvYWcq-P8dPWQixI,2822
29
+ muffin_rest/sqlalchemy/__init__.py,sha256=yTDmK93rCxNUeJKuKkr9JvklUXFO0BIj2uc532NZvBM,6323
30
+ muffin_rest/sqlalchemy/filters.py,sha256=Lf1nPOQjDT7Nv1HAwowfWYTHWCrIMpw-4aWwoaNVlVU,2475
31
+ muffin_rest/sqlalchemy/sorting.py,sha256=qmzP18ongriCGs2Ok_OllEybqWgppU-Kakg0JL1DM_M,1690
32
+ muffin_rest/sqlalchemy/types.py,sha256=Exm-zAQCtPAwXvYcCTtPRqSa-wTEWRcH_v2YSsJkB6s,198
33
+ muffin_rest/swagger.html,sha256=2uGLu_KpkYf925KnDKHBJmV9pm6OHn5C3BWScESsUS8,1736
34
+ muffin_rest/types.py,sha256=5NY9gPTIptv6U2Qab2xMXlS_jZuTuR0bquFFwWRURcA,510
35
+ muffin_rest/utils.py,sha256=c08E4HJ4SLYC-91GKPEbsyKTZ4sZbTN4qDqJbNg_HTE,2076
36
+ muffin_rest-12.0.2.dist-info/LICENSE,sha256=xHPkOZhjyKBMOwXpWn9IB_BVLjrrMxv2M9slKkHj2hM,1082
37
+ muffin_rest-12.0.2.dist-info/METADATA,sha256=IgPneGVrSh5ADttBAGlNenSsgHmwITIIWeZmzRnLgDE,4912
38
+ muffin_rest-12.0.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
39
+ muffin_rest-12.0.2.dist-info/RECORD,,
@@ -1,39 +0,0 @@
1
- muffin_rest/__init__.py,sha256=JnwsHQNDmJpOqETeIjjPheG891T5WQ0JBaiEV72Wcmg,1242
2
- muffin_rest/api.py,sha256=zssoHjqTsa8UCAyxj6TxQVluYPX9Sigyiph6SRxBY6I,3870
3
- muffin_rest/errors.py,sha256=mxEBhNPo3pwpG2em6zaQonbfRgHFBJ3I8WunVYWDjvM,1163
4
- muffin_rest/filters.py,sha256=nWlwDLuId-mmP3tgZjp1joYCVx0k9ekV42fPIMMKphM,5920
5
- muffin_rest/handler.py,sha256=lgZ0KVnzVye8eMZ4qPuo-cSCvg8Vh_fFfTw5g7pJNio,10878
6
- muffin_rest/limits.py,sha256=Fnlu4Wj3B-BzpahLK-rDbd1GEd7CEQ3zxyOD0vee7GE,2007
7
- muffin_rest/marshmallow.py,sha256=UeHxZLtETlrheDAlMZn7Xv_7vWPxvJQo-sj05nql9gM,574
8
- muffin_rest/mongo/__init__.py,sha256=AVOM6ydkEZ4CAOefOlpNnDx0dlD65LdhAkLh_VR8Qf4,4685
9
- muffin_rest/mongo/filters.py,sha256=yIxIDVqMn6SoDgVhCqiTxYetw0hoaf_3jIvX2Vnizok,964
10
- muffin_rest/mongo/schema.py,sha256=y4OEPQnlV_COTIIQ3cKmpqDpD2r18eAWn0rijQldWm0,1205
11
- muffin_rest/mongo/sorting.py,sha256=iJBnaFwE7g_JMwpGpQkoqSqbQK9XULx1K3skiRRgLgY,870
12
- muffin_rest/mongo/types.py,sha256=jaODScgwwYbzHis3DY4bPzU1ahiMJMSwquH5_Thi-Gg,200
13
- muffin_rest/mongo/utils.py,sha256=_6J-FYfU2OIjgFLF9JHRr98vHLzW4ACeL62pBXiuF_M,3966
14
- muffin_rest/openapi.py,sha256=kpynUbNmK78L-2xTbn13FBgmPWLPGMKF_yejVGfHLwg,8754
15
- muffin_rest/options.py,sha256=38y7AasyAghXNffxX-3xgqLbWDQ1RxAhg77ze7c0Mu0,2232
16
- muffin_rest/peewee/__init__.py,sha256=94DSj_ftT6fbPksHlBv40AH2HWaiZommUFOMN2jd9a4,129
17
- muffin_rest/peewee/filters.py,sha256=BL6Zd8TI7wc7ZacZJTPVmTPfsKk6v8Z634HA5t798c0,2506
18
- muffin_rest/peewee/handler.py,sha256=qiJNZRj-7wHO9Fdkt8uV_lPmWJtBvjZQL-I6z5_BvXQ,5469
19
- muffin_rest/peewee/openapi.py,sha256=lDnLnoXi33p0YeFVwRgaVrndyrG2XL93RH-BzbxinOY,1105
20
- muffin_rest/peewee/options.py,sha256=TimJtErC9e8B7BRiEkHiBZd71_bZbYr-FE2PIlQvfH0,1455
21
- muffin_rest/peewee/schemas.py,sha256=hQG2UKR7gEMsz0n75L3tvkVzUX3baXMJ94emgIc1Scw,439
22
- muffin_rest/peewee/sorting.py,sha256=3IQzA1UfMRXl6NAp1yUyTv9-pNSkObtwI1-oZdbetQc,1975
23
- muffin_rest/peewee/types.py,sha256=cgCXhpGHkImKwudA1lulZHz5oJswHH168AiW5MhZRCM,155
24
- muffin_rest/peewee/utils.py,sha256=wrDcEEeCMzK4GdMSBi7BX0XpBPRZ6pF_EHwDsZPgL1s,706
25
- muffin_rest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- muffin_rest/redoc.html,sha256=GtuHIMvTuSi8Ro6bgI-G8VB94AljMyfjcZseqtBmGCY,559
27
- muffin_rest/schemas.py,sha256=BW3dF82C6Q6STs4tZjej1x8Ii1rI3EZUJZR4mNNKmu4,875
28
- muffin_rest/sorting.py,sha256=nu4SGW5uxLETJIywE-1UTCCya9QAvYWcq-P8dPWQixI,2822
29
- muffin_rest/sqlalchemy/__init__.py,sha256=pO6flB9vg3neaEZLp2hFBte2M0ImrssG1gQLjuuqg_4,6398
30
- muffin_rest/sqlalchemy/filters.py,sha256=hmjvxsJF0ddePgrLZ1DPmQ8vd5QwPTTlza3xOZZHquk,2487
31
- muffin_rest/sqlalchemy/sorting.py,sha256=OcuNdgd2__AWXJhlfykqIyTOqAKpyZ3WrK_acNT_t6c,1702
32
- muffin_rest/sqlalchemy/types.py,sha256=Exm-zAQCtPAwXvYcCTtPRqSa-wTEWRcH_v2YSsJkB6s,198
33
- muffin_rest/swagger.html,sha256=2uGLu_KpkYf925KnDKHBJmV9pm6OHn5C3BWScESsUS8,1736
34
- muffin_rest/types.py,sha256=m27-g6BI7qdSWGym4fWALBJa2ZpWR0_m0nlrDx7iTCo,566
35
- muffin_rest/utils.py,sha256=c08E4HJ4SLYC-91GKPEbsyKTZ4sZbTN4qDqJbNg_HTE,2076
36
- muffin_rest-11.0.3.dist-info/LICENSE,sha256=xHPkOZhjyKBMOwXpWn9IB_BVLjrrMxv2M9slKkHj2hM,1082
37
- muffin_rest-11.0.3.dist-info/METADATA,sha256=AOTpoLl7eDiaxX86rrwNlfeEFP6t6tDsAW02FBxzgMU,4912
38
- muffin_rest-11.0.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
39
- muffin_rest-11.0.3.dist-info/RECORD,,