muffin-rest 13.0.3__py3-none-any.whl → 13.1.0__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/filters.py +1 -1
- muffin_rest/handler.py +10 -10
- muffin_rest/mongo/__init__.py +7 -9
- muffin_rest/mongo/schema.py +2 -4
- muffin_rest/mongo/utils.py +1 -1
- muffin_rest/openapi.py +7 -9
- muffin_rest/peewee/filters.py +2 -4
- muffin_rest/peewee/handler.py +3 -4
- muffin_rest/peewee/openapi.py +1 -1
- muffin_rest/peewee/options.py +4 -4
- muffin_rest/peewee/sorting.py +1 -1
- muffin_rest/sorting.py +7 -6
- muffin_rest/sqlalchemy/__init__.py +9 -9
- muffin_rest/utils.py +16 -11
- {muffin_rest-13.0.3.dist-info → muffin_rest-13.1.0.dist-info}/METADATA +17 -19
- {muffin_rest-13.0.3.dist-info → muffin_rest-13.1.0.dist-info}/RECORD +17 -18
- {muffin_rest-13.0.3.dist-info → muffin_rest-13.1.0.dist-info}/WHEEL +1 -1
- muffin_rest-13.0.3.dist-info/licenses/LICENSE +0 -22
muffin_rest/filters.py
CHANGED
muffin_rest/handler.py
CHANGED
|
@@ -47,7 +47,7 @@ class RESTHandlerMeta(HandlerMeta):
|
|
|
47
47
|
return kls
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
class RESTBase(Generic[TVResource],
|
|
50
|
+
class RESTBase(Handler, Generic[TVResource], metaclass=RESTHandlerMeta):
|
|
51
51
|
"""Load/save resources."""
|
|
52
52
|
|
|
53
53
|
auth: Any
|
|
@@ -81,8 +81,8 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
|
|
|
81
81
|
router.bind(cls, *paths, methods=methods, **params)
|
|
82
82
|
|
|
83
83
|
else:
|
|
84
|
-
router.bind(cls, f"/{
|
|
85
|
-
router.bind(cls, f"/{
|
|
84
|
+
router.bind(cls, f"/{cls.meta.name}", methods=methods, **params)
|
|
85
|
+
router.bind(cls, f"/{cls.meta.name}/{{id}}", methods=methods, **params)
|
|
86
86
|
|
|
87
87
|
for _, method in inspect.getmembers(cls, lambda m: hasattr(m, "__route__")):
|
|
88
88
|
paths, methods = method.__route__
|
|
@@ -204,9 +204,9 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
|
|
|
204
204
|
return [await self.save(request, item, update=update) for item in data]
|
|
205
205
|
|
|
206
206
|
@abc.abstractmethod
|
|
207
|
-
async def remove(self, request: Request, resource):
|
|
207
|
+
async def remove(self, request: Request, resource: TVResource | None = None) -> Any:
|
|
208
208
|
"""Remove the given resource."""
|
|
209
|
-
|
|
209
|
+
...
|
|
210
210
|
|
|
211
211
|
# Parse data
|
|
212
212
|
# -----------
|
|
@@ -258,7 +258,7 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
|
|
|
258
258
|
schema = self.get_schema(request)
|
|
259
259
|
return schema.dump(data, many=many)
|
|
260
260
|
|
|
261
|
-
async def get(self, request: Request, *, resource: TVResource | None = None) ->
|
|
261
|
+
async def get(self, request: Request, *, resource: TVResource | None = None) -> Any:
|
|
262
262
|
"""Get a resource or a collection of resources.
|
|
263
263
|
|
|
264
264
|
Specify a path param to load a resource.
|
|
@@ -268,9 +268,9 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
|
|
|
268
268
|
if resource
|
|
269
269
|
else self.dump(request, data=self.collection, many=True)
|
|
270
270
|
)
|
|
271
|
-
return ResponseJSON(res)
|
|
271
|
+
return ResponseJSON(res)
|
|
272
272
|
|
|
273
|
-
async def post(self, request: Request, *, resource: TVResource | None = None) ->
|
|
273
|
+
async def post(self, request: Request, *, resource: TVResource | None = None) -> Any:
|
|
274
274
|
"""Create a resource.
|
|
275
275
|
|
|
276
276
|
The method accepts a single resource's data or a list of resources to create.
|
|
@@ -285,14 +285,14 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
|
|
|
285
285
|
res = await self.dump(request, data, many=many)
|
|
286
286
|
return ResponseJSON(res)
|
|
287
287
|
|
|
288
|
-
async def put(self, request: Request, *, resource: TVResource | None = None) ->
|
|
288
|
+
async def put(self, request: Request, *, resource: TVResource | None = None) -> Any:
|
|
289
289
|
"""Update a resource."""
|
|
290
290
|
if resource is None:
|
|
291
291
|
raise APIError.NOT_FOUND()
|
|
292
292
|
|
|
293
293
|
return await self.post(request, resource=resource)
|
|
294
294
|
|
|
295
|
-
async def delete(self, request: Request, resource: TVResource | None = None):
|
|
295
|
+
async def delete(self, request: Request, resource: TVResource | None = None) -> Any:
|
|
296
296
|
"""Delete a resource."""
|
|
297
297
|
if resource is None:
|
|
298
298
|
raise APIError.NOT_FOUND()
|
muffin_rest/mongo/__init__.py
CHANGED
|
@@ -25,9 +25,9 @@ if TYPE_CHECKING:
|
|
|
25
25
|
class MongoRESTOptions(RESTOptions):
|
|
26
26
|
"""Support Mongo DB."""
|
|
27
27
|
|
|
28
|
-
filters_cls
|
|
29
|
-
sorting_cls
|
|
30
|
-
schema_base
|
|
28
|
+
filters_cls = MongoFilters
|
|
29
|
+
sorting_cls = MongoSorting
|
|
30
|
+
schema_base = MongoSchema
|
|
31
31
|
|
|
32
32
|
aggregate: list | None = None # Support aggregation. Set to pipeline.
|
|
33
33
|
collection_id: str = "_id"
|
|
@@ -35,7 +35,7 @@ class MongoRESTOptions(RESTOptions):
|
|
|
35
35
|
|
|
36
36
|
base_property: str = "collection"
|
|
37
37
|
|
|
38
|
-
Schema: type[MongoSchema]
|
|
38
|
+
Schema: type[MongoSchema] # type: ignore[override]
|
|
39
39
|
|
|
40
40
|
def setup(self, cls):
|
|
41
41
|
"""Prepare meta options."""
|
|
@@ -49,16 +49,14 @@ class MongoRESTOptions(RESTOptions):
|
|
|
49
49
|
class MongoRESTHandler(RESTHandler[TVResource]):
|
|
50
50
|
"""Support Mongo DB."""
|
|
51
51
|
|
|
52
|
-
meta: MongoRESTOptions
|
|
53
|
-
meta_class
|
|
52
|
+
meta: MongoRESTOptions # type: ignore[bad-override]
|
|
53
|
+
meta_class = MongoRESTOptions
|
|
54
54
|
|
|
55
55
|
async def prepare_collection(self, request: Request) -> MongoChain:
|
|
56
56
|
"""Initialize Peeewee QuerySet for a binded to the resource model."""
|
|
57
57
|
return MongoChain(self.meta.collection)
|
|
58
58
|
|
|
59
|
-
async def paginate(
|
|
60
|
-
self, request: Request, *, limit: int = 0, offset: int = 0
|
|
61
|
-
) -> tuple[motor.AsyncIOMotorCursor, int | None]:
|
|
59
|
+
async def paginate(self, request: Request, *, limit: int = 0, offset: int = 0): # type: ignore[override]
|
|
62
60
|
"""Paginate collection."""
|
|
63
61
|
if self.meta.aggregate:
|
|
64
62
|
pipeline_all = [*self.meta.aggregate, {"$skip": offset}, {"$limit": limit}]
|
muffin_rest/mongo/schema.py
CHANGED
|
@@ -5,23 +5,21 @@ import marshmallow as ma
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class ObjectId(ma.fields.Field):
|
|
8
|
-
|
|
9
8
|
"""ObjectID Marshmallow Field."""
|
|
10
9
|
|
|
11
|
-
def _deserialize(self, value,
|
|
10
|
+
def _deserialize(self, value, attr, data, **kwargs):
|
|
12
11
|
try:
|
|
13
12
|
return bson.ObjectId(value)
|
|
14
13
|
except ValueError as exc:
|
|
15
14
|
raise ma.ValidationError("invalid ObjectId `%s`" % value) from exc
|
|
16
15
|
|
|
17
|
-
def _serialize(self, value,
|
|
16
|
+
def _serialize(self, value, attr, obj, **kwargs):
|
|
18
17
|
if value is None:
|
|
19
18
|
return ma.missing
|
|
20
19
|
return str(value)
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
class MongoSchema(ma.Schema):
|
|
24
|
-
|
|
25
23
|
"""Serialize/deserialize results from mongo."""
|
|
26
24
|
|
|
27
25
|
_id = ObjectId()
|
muffin_rest/mongo/utils.py
CHANGED
|
@@ -79,7 +79,7 @@ class MongoChain:
|
|
|
79
79
|
def count(self) -> Awaitable[int]:
|
|
80
80
|
"""Count documents."""
|
|
81
81
|
query = (self.query and {"$and": self.query}) or {}
|
|
82
|
-
return self.collection.count_documents(query)
|
|
82
|
+
return self.collection.count_documents(query) # type: ignore[]
|
|
83
83
|
|
|
84
84
|
def aggregate(self, pipeline, **kwargs):
|
|
85
85
|
"""Aggregate collection."""
|
muffin_rest/openapi.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
|
-
import re
|
|
7
6
|
from contextlib import suppress
|
|
8
7
|
from functools import partial
|
|
9
8
|
from http import HTTPStatus
|
|
@@ -40,7 +39,6 @@ HTTP_METHODS = [
|
|
|
40
39
|
"TRACE",
|
|
41
40
|
"CONNECT",
|
|
42
41
|
]
|
|
43
|
-
RE_URL = re.compile(r"<(?:[^:<>]+:)?([^<>]+)>") # TODO: Not used
|
|
44
42
|
SKIP_PATH = {"/openapi.json", "/swagger", "/redoc"}
|
|
45
43
|
|
|
46
44
|
|
|
@@ -50,14 +48,14 @@ def render_openapi(api, request=None):
|
|
|
50
48
|
options = dict(api.openapi_options)
|
|
51
49
|
if request:
|
|
52
50
|
options.setdefault(
|
|
53
|
-
"servers",
|
|
54
|
-
[{"url": str(request.url.with_query("").with_path(api.prefix))}],
|
|
51
|
+
"servers", [{"url": str(request.url.with_query("").with_path(api.prefix))}]
|
|
55
52
|
)
|
|
56
53
|
|
|
54
|
+
info = cast("dict", options["info"])
|
|
57
55
|
spec = APISpec(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
options.pop("openapi_version", "3.0.3"),
|
|
56
|
+
info.pop("title", f"{api.app.cfg.name.title()} API"),
|
|
57
|
+
info.pop("version", "1.0.0"),
|
|
58
|
+
options.pop("openapi_version", "3.0.3"), # type: ignore[]
|
|
61
59
|
**options,
|
|
62
60
|
plugins=[MarshmallowPlugin()],
|
|
63
61
|
)
|
|
@@ -188,7 +186,7 @@ class OpenAPIMixin:
|
|
|
188
186
|
return {}
|
|
189
187
|
|
|
190
188
|
operations: dict = {}
|
|
191
|
-
summary,
|
|
189
|
+
summary, _, schema = parse_docs(cls)
|
|
192
190
|
if cls not in tags:
|
|
193
191
|
tags[cls] = meta.name
|
|
194
192
|
spec.tag({"name": meta.name, "description": summary})
|
|
@@ -196,7 +194,7 @@ class OpenAPIMixin:
|
|
|
196
194
|
if schema_cls.__name__ not in spec.components.schemas:
|
|
197
195
|
spec.components.schema(meta.Schema.__name__, schema=meta.Schema)
|
|
198
196
|
|
|
199
|
-
schema_ref = {"$ref": f"#/components/schemas/{
|
|
197
|
+
schema_ref = {"$ref": f"#/components/schemas/{meta.Schema.__name__}"}
|
|
200
198
|
for method in route_to_methods(route):
|
|
201
199
|
operations[method] = {"tags": [tags[cls]]}
|
|
202
200
|
is_resource_route = isinstance(route, DynamicRoute) and route.params.get("id")
|
muffin_rest/peewee/filters.py
CHANGED
|
@@ -43,16 +43,14 @@ class PWFilter(Filter):
|
|
|
43
43
|
"""Apply the filters to Peewee QuerySet.."""
|
|
44
44
|
column = self.field
|
|
45
45
|
if isinstance(column, ColumnBase):
|
|
46
|
-
collection =
|
|
47
|
-
"ModelSelect", collection.where(*[op(column, val) for op, val in ops])
|
|
48
|
-
)
|
|
46
|
+
collection = collection.where(*[op(column, val) for op, val in ops])
|
|
49
47
|
return collection
|
|
50
48
|
|
|
51
49
|
|
|
52
50
|
class PWFilters(Filters):
|
|
53
51
|
"""Bind Peewee filter class."""
|
|
54
52
|
|
|
55
|
-
MUTATE_CLASS
|
|
53
|
+
MUTATE_CLASS = PWFilter
|
|
56
54
|
|
|
57
55
|
def convert(self, obj: str | Field | PWFilter, **meta):
|
|
58
56
|
"""Convert params to filters."""
|
muffin_rest/peewee/handler.py
CHANGED
|
@@ -36,8 +36,8 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
|
|
|
36
36
|
resource: TVModel
|
|
37
37
|
collection: AIOModelSelect | pw.ModelSelect
|
|
38
38
|
|
|
39
|
-
meta: PWRESTOptions
|
|
40
|
-
meta_class
|
|
39
|
+
meta: PWRESTOptions # type: ignore[override]
|
|
40
|
+
meta_class = PWRESTOptions
|
|
41
41
|
|
|
42
42
|
@overload
|
|
43
43
|
async def prepare_collection(
|
|
@@ -131,8 +131,7 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
|
|
|
131
131
|
if not data:
|
|
132
132
|
return
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
resources = await meta.manager.fetchall(self.collection.where(model_pk << data)) # type: ignore[]
|
|
134
|
+
resources = await meta.manager.fetchall(self.collection.where(meta.model_pk << data)) # type: ignore[]
|
|
136
135
|
|
|
137
136
|
if not resources:
|
|
138
137
|
raise APIError.NOT_FOUND()
|
muffin_rest/peewee/openapi.py
CHANGED
muffin_rest/peewee/options.py
CHANGED
|
@@ -12,15 +12,15 @@ class PWRESTOptions(RESTOptions):
|
|
|
12
12
|
"""Support Peewee."""
|
|
13
13
|
|
|
14
14
|
# Base filters class
|
|
15
|
-
filters_cls
|
|
15
|
+
filters_cls = PWFilters
|
|
16
16
|
|
|
17
17
|
# Base sorting class
|
|
18
|
-
sorting_cls
|
|
18
|
+
sorting_cls = PWSorting
|
|
19
19
|
|
|
20
|
-
Schema: type[ModelSchema]
|
|
20
|
+
Schema: type[ModelSchema] # type: ignore[override]
|
|
21
21
|
|
|
22
22
|
# Schema auto generation params
|
|
23
|
-
schema_base
|
|
23
|
+
schema_base = ModelSchema
|
|
24
24
|
|
|
25
25
|
base_property: str = "model"
|
|
26
26
|
|
muffin_rest/peewee/sorting.py
CHANGED
muffin_rest/sorting.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Generator, Iterable,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Generator, Iterable, Sequence, cast
|
|
6
6
|
|
|
7
7
|
from .utils import Mutate, Mutator
|
|
8
8
|
|
|
@@ -23,11 +23,10 @@ class Sort(Mutate):
|
|
|
23
23
|
return sorted(collection, key=lambda obj: getattr(obj, self.name), reverse=desc)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
class Sorting(Mutator):
|
|
26
|
+
class Sorting(Mutator[Sort]):
|
|
27
27
|
"""Build sorters for handlers."""
|
|
28
28
|
|
|
29
29
|
MUTATE_CLASS = Sort
|
|
30
|
-
mutations: Mapping[str, Sort]
|
|
31
30
|
|
|
32
31
|
def __init__(self, handler: type[RESTBase], params: Iterable):
|
|
33
32
|
"""Initialize the sorting."""
|
|
@@ -59,11 +58,13 @@ class Sorting(Mutator):
|
|
|
59
58
|
"""Prepare the collection."""
|
|
60
59
|
return collection
|
|
61
60
|
|
|
62
|
-
def convert(self, obj, **meta)
|
|
61
|
+
def convert(self, obj, **meta):
|
|
63
62
|
"""Prepare sorters."""
|
|
64
|
-
sort =
|
|
65
|
-
|
|
63
|
+
sort = super(Sorting, self).convert(obj, **meta)
|
|
64
|
+
|
|
65
|
+
if sort and sort.meta.get("default"):
|
|
66
66
|
self.default.append(sort)
|
|
67
|
+
|
|
67
68
|
return sort
|
|
68
69
|
|
|
69
70
|
def sort_default(self, collection: TVCollection) -> TVCollection:
|
|
@@ -35,7 +35,8 @@ class SQLAlchemyAutoSchema(BaseSQLAlchemyAutoSchema):
|
|
|
35
35
|
"""
|
|
36
36
|
cols_to_fields = {f.attribute or f.name: f for f in self.declared_fields.values()}
|
|
37
37
|
if not partial:
|
|
38
|
-
|
|
38
|
+
table = self.opts.table # type: ignore[]
|
|
39
|
+
for column in table.columns:
|
|
39
40
|
field = cols_to_fields.get(column.name)
|
|
40
41
|
if not field:
|
|
41
42
|
continue
|
|
@@ -67,12 +68,11 @@ class SQLAlchemyAutoSchema(BaseSQLAlchemyAutoSchema):
|
|
|
67
68
|
class SARESTOptions(RESTOptions):
|
|
68
69
|
"""Support SQLAlchemy Core."""
|
|
69
70
|
|
|
70
|
-
filters_cls
|
|
71
|
-
sorting_cls
|
|
71
|
+
filters_cls = SAFilters
|
|
72
|
+
sorting_cls = SASorting
|
|
72
73
|
|
|
73
74
|
# Schema auto generation params
|
|
74
|
-
|
|
75
|
-
schema_base: type[SQLAlchemyAutoSchema] = SQLAlchemyAutoSchema
|
|
75
|
+
schema_base = SQLAlchemyAutoSchema
|
|
76
76
|
|
|
77
77
|
table: sa.Table
|
|
78
78
|
table_pk: sa.Column
|
|
@@ -110,8 +110,8 @@ class SARESTOptions(RESTOptions):
|
|
|
110
110
|
class SARESTHandler(RESTHandler[TVResource]):
|
|
111
111
|
"""Support SQLAlchemy Core."""
|
|
112
112
|
|
|
113
|
-
meta: SARESTOptions
|
|
114
|
-
meta_class
|
|
113
|
+
meta: SARESTOptions # type: ignore[bad-override]
|
|
114
|
+
meta_class = SARESTOptions
|
|
115
115
|
collection: sa.sql.Select
|
|
116
116
|
|
|
117
117
|
async def prepare_collection(self, request: Request) -> sa.sql.Select:
|
|
@@ -163,7 +163,7 @@ class SARESTHandler(RESTHandler[TVResource]):
|
|
|
163
163
|
"""Save the given resource."""
|
|
164
164
|
meta = self.meta
|
|
165
165
|
insert_query = meta.table.insert()
|
|
166
|
-
table_pk =
|
|
166
|
+
table_pk = meta.table_pk
|
|
167
167
|
if update:
|
|
168
168
|
update_query = self.meta.table.update().where(table_pk == resource[table_pk.name]) # type: ignore[call-overload]
|
|
169
169
|
await meta.database.execute(update_query, resource)
|
|
@@ -175,7 +175,7 @@ class SARESTHandler(RESTHandler[TVResource]):
|
|
|
175
175
|
|
|
176
176
|
async def remove(self, request: Request, resource: TVResource | None = None):
|
|
177
177
|
"""Remove the given resource."""
|
|
178
|
-
table_pk =
|
|
178
|
+
table_pk = self.meta.table_pk
|
|
179
179
|
keys = [resource[table_pk.name]] if resource else await request.data()
|
|
180
180
|
if not keys:
|
|
181
181
|
raise APIError.NOT_FOUND()
|
muffin_rest/utils.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""REST Utils."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import abc
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Iterable, Mapping
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Generic, Iterable, Mapping, TypeVar
|
|
6
7
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
8
9
|
from muffin import Request
|
|
@@ -28,16 +29,19 @@ class Mutate(abc.ABC):
|
|
|
28
29
|
return f"<{self.__class__.__name__} '{self.name}'>"
|
|
29
30
|
|
|
30
31
|
@abc.abstractmethod
|
|
31
|
-
async def apply(self, collection: TVCollection) -> TVCollection:
|
|
32
|
+
async def apply(self, collection: TVCollection) -> tuple[Any, TVCollection]:
|
|
32
33
|
"""Apply the mutation."""
|
|
33
|
-
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
M = TypeVar("M", bound=Mutate)
|
|
34
38
|
|
|
35
39
|
|
|
36
|
-
class Mutator(abc.ABC):
|
|
40
|
+
class Mutator(abc.ABC, Generic[M]):
|
|
37
41
|
"""Mutate collections."""
|
|
38
42
|
|
|
39
|
-
MUTATE_CLASS: type[
|
|
40
|
-
mutations: Mapping[str,
|
|
43
|
+
MUTATE_CLASS: type[M]
|
|
44
|
+
mutations: Mapping[str, M]
|
|
41
45
|
|
|
42
46
|
def __init__(self, handler, params: Iterable):
|
|
43
47
|
"""Initialize the mutations."""
|
|
@@ -63,16 +67,17 @@ class Mutator(abc.ABC):
|
|
|
63
67
|
def __bool__(self):
|
|
64
68
|
return bool(self.mutations)
|
|
65
69
|
|
|
66
|
-
def convert(self, obj, **meta) ->
|
|
70
|
+
def convert(self, obj, **meta) -> M | None:
|
|
67
71
|
"""Convert params to mutations."""
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
factory = self.MUTATE_CLASS
|
|
73
|
+
if not isinstance(obj, factory):
|
|
74
|
+
obj = factory(obj, **meta)
|
|
70
75
|
|
|
71
|
-
return
|
|
76
|
+
return obj
|
|
72
77
|
|
|
73
78
|
@abc.abstractmethod
|
|
74
79
|
async def apply(
|
|
75
80
|
self, request: Request, collection: TVCollection
|
|
76
81
|
) -> tuple[TVCollection, dict[str, Any]]:
|
|
77
82
|
"""Mutate a collection."""
|
|
78
|
-
|
|
83
|
+
...
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: muffin-rest
|
|
3
|
-
Version: 13.0
|
|
3
|
+
Version: 13.1.0
|
|
4
4
|
Summary: The package provides enhanced support for writing REST APIs with Muffin framework
|
|
5
|
-
License: MIT
|
|
6
|
-
License-File: LICENSE
|
|
7
5
|
Keywords: rest,api,muffin,asgi,asyncio,trio
|
|
8
6
|
Author: Kirill Klenov
|
|
9
|
-
Author-email: horneds@gmail.com
|
|
10
|
-
|
|
7
|
+
Author-email: Kirill Klenov <horneds@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
11
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
-
Classifier: Framework :: AsyncIO
|
|
13
|
-
Classifier: Framework :: Trio
|
|
14
10
|
Classifier: Intended Audience :: Developers
|
|
15
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
12
|
Classifier: Programming Language :: Python
|
|
@@ -21,20 +17,23 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
21
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
19
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist: marshmallow (>=3,<4)
|
|
29
|
-
Requires-Dist: marshmallow-peewee ; extra == "peewee"
|
|
30
|
-
Requires-Dist: marshmallow-sqlalchemy ; extra == "sqlalchemy"
|
|
20
|
+
Classifier: Framework :: AsyncIO
|
|
21
|
+
Classifier: Framework :: Trio
|
|
22
|
+
Requires-Dist: apispec>=6,<7
|
|
23
|
+
Requires-Dist: marshmallow>=3,<4
|
|
31
24
|
Requires-Dist: muffin
|
|
32
|
-
Requires-Dist: muffin-
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist: sqlalchemy ; extra ==
|
|
25
|
+
Requires-Dist: muffin-peewee-aio ; extra == 'peewee'
|
|
26
|
+
Requires-Dist: marshmallow-peewee ; extra == 'peewee'
|
|
27
|
+
Requires-Dist: muffin-databases ; extra == 'sqlalchemy'
|
|
28
|
+
Requires-Dist: marshmallow-sqlalchemy ; extra == 'sqlalchemy'
|
|
29
|
+
Requires-Dist: sqlalchemy ; extra == 'sqlalchemy'
|
|
30
|
+
Requires-Dist: pyyaml ; extra == 'yaml'
|
|
31
|
+
Requires-Python: >=3.10, <4
|
|
36
32
|
Project-URL: Homepage, https://github.com/klen/muffin-rest
|
|
37
33
|
Project-URL: Repository, https://github.com/klen/muffin-rest
|
|
34
|
+
Provides-Extra: peewee
|
|
35
|
+
Provides-Extra: sqlalchemy
|
|
36
|
+
Provides-Extra: yaml
|
|
38
37
|
Description-Content-Type: text/markdown
|
|
39
38
|
|
|
40
39
|
# Muffin‑REST
|
|
@@ -175,4 +174,3 @@ Pull requests, example additions, docs improvements welcome!
|
|
|
175
174
|
## License
|
|
176
175
|
|
|
177
176
|
Licensed under the [MIT license](http://opensource.org/licenses/MIT).
|
|
178
|
-
|
|
@@ -1,39 +1,38 @@
|
|
|
1
1
|
muffin_rest/__init__.py,sha256=JnwsHQNDmJpOqETeIjjPheG891T5WQ0JBaiEV72Wcmg,1242
|
|
2
2
|
muffin_rest/api.py,sha256=bdbXMPiZi5yXf7_u3XB_gaGDHZKzDPqnnFnrwHoO1Rs,3799
|
|
3
3
|
muffin_rest/errors.py,sha256=Todg7CrCE1ufOsyyARI2Bv-RqNPH4Rl1p-Tkpq7VYrA,1151
|
|
4
|
-
muffin_rest/filters.py,sha256=
|
|
5
|
-
muffin_rest/handler.py,sha256=
|
|
4
|
+
muffin_rest/filters.py,sha256=BMjH9Yai1dywvXWSR1pBMxYWE16TLKV9kB3PHNfTaRY,5928
|
|
5
|
+
muffin_rest/handler.py,sha256=Ob7viBiCf8ju7XEyCMw1mG3tbWnGhgPAnCO-XtKTOEc,10723
|
|
6
6
|
muffin_rest/limits.py,sha256=X6qwuQPUdKJiPRgiKLY2NquXZlrpbx7BYHH3njw9ros,2168
|
|
7
7
|
muffin_rest/marshmallow.py,sha256=-MyMKiaMTfiWw4y-4adpfoQd05HV1vEKqSXJh8eD3xg,548
|
|
8
|
-
muffin_rest/mongo/__init__.py,sha256=
|
|
8
|
+
muffin_rest/mongo/__init__.py,sha256=pYDY1OSxaW-uyfxqhtC_IvGn2-PK4CBV3Isb3P1bpxo,4603
|
|
9
9
|
muffin_rest/mongo/filters.py,sha256=yIxIDVqMn6SoDgVhCqiTxYetw0hoaf_3jIvX2Vnizok,964
|
|
10
|
-
muffin_rest/mongo/schema.py,sha256=
|
|
10
|
+
muffin_rest/mongo/schema.py,sha256=agMWipU3L23sFJLpzuDGM7-2smNsFhwSCjHAJX7YBpg,1232
|
|
11
11
|
muffin_rest/mongo/sorting.py,sha256=iJBnaFwE7g_JMwpGpQkoqSqbQK9XULx1K3skiRRgLgY,870
|
|
12
12
|
muffin_rest/mongo/types.py,sha256=jaODScgwwYbzHis3DY4bPzU1ahiMJMSwquH5_Thi-Gg,200
|
|
13
|
-
muffin_rest/mongo/utils.py,sha256=
|
|
14
|
-
muffin_rest/openapi.py,sha256=
|
|
13
|
+
muffin_rest/mongo/utils.py,sha256=NEfg_BLn15BbpM7-XomfgM2fwkcNb_QBzw2r51EGbJE,3967
|
|
14
|
+
muffin_rest/openapi.py,sha256=mFJ2K-I6K2WD2QoZL8ozmtLqGd3pCHv3s9VKNUK5NbY,8656
|
|
15
15
|
muffin_rest/options.py,sha256=Mzv0mq6YylL6cNtad9JhEhkmA4RgmDsu-On2hn5VEiw,2208
|
|
16
16
|
muffin_rest/peewee/__init__.py,sha256=94DSj_ftT6fbPksHlBv40AH2HWaiZommUFOMN2jd9a4,129
|
|
17
|
-
muffin_rest/peewee/filters.py,sha256=
|
|
18
|
-
muffin_rest/peewee/handler.py,sha256=
|
|
19
|
-
muffin_rest/peewee/openapi.py,sha256=
|
|
20
|
-
muffin_rest/peewee/options.py,sha256=
|
|
17
|
+
muffin_rest/peewee/filters.py,sha256=T_Gspasump_4Jn1dy-sE-wHpfqH94lB4K2moVtARrX8,2427
|
|
18
|
+
muffin_rest/peewee/handler.py,sha256=iDEgPPxSC-Y11MTcSl-Hs9iNWgOeQv-mU1tx-6OAe5E,5360
|
|
19
|
+
muffin_rest/peewee/openapi.py,sha256=Jp_wtKNRCCjpaElJGxPtF5JYIv_hlx9S2LYT0FcxamI,1119
|
|
20
|
+
muffin_rest/peewee/options.py,sha256=iLZ4ORF8T6i9aUNU2C5F-TAwwd7hW80N6wC9FztvT_o,1428
|
|
21
21
|
muffin_rest/peewee/schemas.py,sha256=hQG2UKR7gEMsz0n75L3tvkVzUX3baXMJ94emgIc1Scw,439
|
|
22
|
-
muffin_rest/peewee/sorting.py,sha256=
|
|
22
|
+
muffin_rest/peewee/sorting.py,sha256=BVff9kp2PdyuUJ9eSGdt8JQ2UVMVxcHT4QGSplsdfoY,1949
|
|
23
23
|
muffin_rest/peewee/types.py,sha256=cgCXhpGHkImKwudA1lulZHz5oJswHH168AiW5MhZRCM,155
|
|
24
24
|
muffin_rest/peewee/utils.py,sha256=8wTSKmjeD3qtF5NFHX1wkBn4jiXFvCzNEK9WW7r_JmI,693
|
|
25
25
|
muffin_rest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
26
|
muffin_rest/redoc.html,sha256=GtuHIMvTuSi8Ro6bgI-G8VB94AljMyfjcZseqtBmGCY,559
|
|
27
27
|
muffin_rest/schemas.py,sha256=BW3dF82C6Q6STs4tZjej1x8Ii1rI3EZUJZR4mNNKmu4,875
|
|
28
|
-
muffin_rest/sorting.py,sha256=
|
|
29
|
-
muffin_rest/sqlalchemy/__init__.py,sha256=
|
|
28
|
+
muffin_rest/sorting.py,sha256=4umK_iPAt_Jth6WGMMZDG9ULHWpx3n-hdM0ibl39sGg,2774
|
|
29
|
+
muffin_rest/sqlalchemy/__init__.py,sha256=XJwVKEcRz-vrKs4NPH_hRy_N2YWTEssQgioltetJUw4,6255
|
|
30
30
|
muffin_rest/sqlalchemy/filters.py,sha256=Lf1nPOQjDT7Nv1HAwowfWYTHWCrIMpw-4aWwoaNVlVU,2475
|
|
31
31
|
muffin_rest/sqlalchemy/sorting.py,sha256=qmzP18ongriCGs2Ok_OllEybqWgppU-Kakg0JL1DM_M,1690
|
|
32
32
|
muffin_rest/sqlalchemy/types.py,sha256=Exm-zAQCtPAwXvYcCTtPRqSa-wTEWRcH_v2YSsJkB6s,198
|
|
33
33
|
muffin_rest/swagger.html,sha256=2uGLu_KpkYf925KnDKHBJmV9pm6OHn5C3BWScESsUS8,1736
|
|
34
34
|
muffin_rest/types.py,sha256=5NY9gPTIptv6U2Qab2xMXlS_jZuTuR0bquFFwWRURcA,510
|
|
35
|
-
muffin_rest/utils.py,sha256=
|
|
36
|
-
muffin_rest-13.0.
|
|
37
|
-
muffin_rest-13.0.
|
|
38
|
-
muffin_rest-13.0.
|
|
39
|
-
muffin_rest-13.0.3.dist-info/RECORD,,
|
|
35
|
+
muffin_rest/utils.py,sha256=71hDcTVUClNN6l8bfxzCBy4IbiO2vo0b5EeMEj61n_w,2119
|
|
36
|
+
muffin_rest-13.1.0.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
37
|
+
muffin_rest-13.1.0.dist-info/METADATA,sha256=x7P0mJbUe-UDcqHp-AeayK_BRWHbIQ_Q3nDrtsuF3RE,4982
|
|
38
|
+
muffin_rest-13.1.0.dist-info/RECORD,,
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
The MIT License (MIT)
|
|
3
|
-
|
|
4
|
-
Copyright (c) 2015, Kirill Klenov
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|