muffin-rest 11.0.3__py3-none-any.whl → 12.0.1__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 +8 -12
- muffin_rest/errors.py +3 -2
- muffin_rest/handler.py +26 -31
- muffin_rest/marshmallow.py +2 -2
- muffin_rest/mongo/__init__.py +8 -8
- muffin_rest/mongo/utils.py +3 -3
- muffin_rest/openapi.py +1 -3
- muffin_rest/options.py +1 -1
- muffin_rest/peewee/filters.py +2 -2
- muffin_rest/peewee/handler.py +8 -8
- muffin_rest/peewee/openapi.py +1 -1
- muffin_rest/peewee/sorting.py +2 -2
- muffin_rest/peewee/utils.py +2 -2
- muffin_rest/sqlalchemy/__init__.py +9 -10
- muffin_rest/sqlalchemy/filters.py +2 -2
- muffin_rest/sqlalchemy/sorting.py +2 -2
- muffin_rest/types.py +0 -2
- {muffin_rest-11.0.3.dist-info → muffin_rest-12.0.1.dist-info}/METADATA +1 -1
- muffin_rest-12.0.1.dist-info/RECORD +39 -0
- muffin_rest-11.0.3.dist-info/RECORD +0 -39
- {muffin_rest-11.0.3.dist-info → muffin_rest-12.0.1.dist-info}/LICENSE +0 -0
- {muffin_rest-11.0.3.dist-info → muffin_rest-12.0.1.dist-info}/WHEEL +0 -0
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,
|
|
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:
|
|
30
|
+
app: muffin.Application | None = None,
|
|
31
31
|
prefix: str = "",
|
|
32
32
|
*,
|
|
33
33
|
openapi: bool = True,
|
|
34
|
-
servers:
|
|
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:
|
|
70
|
-
servers:
|
|
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
|
|
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:
|
|
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,
|
|
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:
|
|
59
|
+
_api: API | None = None
|
|
62
60
|
|
|
63
|
-
filters:
|
|
64
|
-
sorting:
|
|
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[
|
|
68
|
+
filters: Sequence[str | tuple[str, str] | Filter] = ()
|
|
71
69
|
|
|
72
70
|
# Define allowed resource sorting params
|
|
73
|
-
sorting: Sequence[
|
|
71
|
+
sorting: Sequence[str | tuple[str, dict] | Sort] = ()
|
|
74
72
|
|
|
75
73
|
# Serialize/Deserialize Schema class
|
|
76
|
-
Schema:
|
|
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:
|
|
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(
|
|
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,
|
|
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:
|
|
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("
|
|
234
|
+
data = cast("Mapping | list", await self.load_data(request))
|
|
241
235
|
return cast(
|
|
242
|
-
"
|
|
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:
|
|
242
|
+
self, request, data: TVResource | Iterable[TVResource], *, many: Literal[True]
|
|
248
243
|
) -> list[TSchemaRes]: ...
|
|
249
244
|
|
|
250
245
|
@overload
|
|
251
|
-
async def dump(
|
|
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:
|
|
253
|
+
data: TVResource | Iterable[TVResource],
|
|
257
254
|
*,
|
|
258
255
|
many: bool = False,
|
|
259
|
-
) ->
|
|
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:
|
|
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:
|
|
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:
|
|
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()
|
muffin_rest/marshmallow.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
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:
|
|
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
|
|
muffin_rest/mongo/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING,
|
|
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:
|
|
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,
|
|
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:
|
|
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) ->
|
|
87
|
+
async def prepare_resource(self, request: Request) -> TVResource | None:
|
|
88
88
|
"""Load a resource."""
|
|
89
|
-
pk = request["path_params"].get(
|
|
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:
|
|
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:
|
|
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 = (
|
muffin_rest/mongo/utils.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING, Awaitable
|
|
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:
|
|
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:
|
|
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
muffin_rest/peewee/filters.py
CHANGED
|
@@ -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,
|
|
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:
|
|
57
|
+
def convert(self, obj: str | Field | PWFilter, **meta):
|
|
58
58
|
"""Convert params to filters."""
|
|
59
59
|
|
|
60
60
|
handler = cast("PWRESTHandler", self.handler)
|
muffin_rest/peewee/handler.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING, Any,
|
|
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:
|
|
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) ->
|
|
59
|
+
async def prepare_resource(self, request: Request) -> TVModel | None:
|
|
60
60
|
"""Load a resource."""
|
|
61
|
-
pk = request["path_params"].get(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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)
|
muffin_rest/peewee/openapi.py
CHANGED
|
@@ -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(
|
|
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"] = {
|
muffin_rest/peewee/sorting.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING,
|
|
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:
|
|
38
|
+
def convert(self, obj: str | Field | PWSort, **meta):
|
|
39
39
|
"""Prepare sorters."""
|
|
40
40
|
|
|
41
41
|
if isinstance(obj, PWSort):
|
muffin_rest/peewee/utils.py
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
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) ->
|
|
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,
|
|
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.
|
|
103
|
+
"dump_only": (self.pk,),
|
|
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,
|
|
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:
|
|
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) ->
|
|
144
|
+
async def prepare_resource(self, request: Request) -> TVResource | None:
|
|
146
145
|
"""Load a resource."""
|
|
147
|
-
pk = request["path_params"].get(
|
|
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:
|
|
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,
|
|
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:
|
|
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,
|
|
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:
|
|
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,
|
|
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:
|
|
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"])
|
|
@@ -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=VO1d7nzKSxPw9QidNiyNSfMDX46ZCK0a-iFbJyzNXEE,2227
|
|
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=pZt_3t8I4uU8WssrksdEn8LXS0ytKT4NHioTpFqKDiw,6312
|
|
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.1.dist-info/LICENSE,sha256=xHPkOZhjyKBMOwXpWn9IB_BVLjrrMxv2M9slKkHj2hM,1082
|
|
37
|
+
muffin_rest-12.0.1.dist-info/METADATA,sha256=dXgMCxDzBPevm7z9iHGm7okdhQCIRX-R8RGCHUwdjWo,4912
|
|
38
|
+
muffin_rest-12.0.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
39
|
+
muffin_rest-12.0.1.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,,
|
|
File without changes
|
|
File without changes
|