muffin-rest 11.0.1__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/__init__.py +9 -9
- muffin_rest/api.py +8 -12
- muffin_rest/errors.py +3 -2
- muffin_rest/filters.py +4 -4
- muffin_rest/handler.py +31 -34
- muffin_rest/marshmallow.py +2 -2
- muffin_rest/mongo/__init__.py +13 -10
- muffin_rest/mongo/utils.py +7 -7
- muffin_rest/openapi.py +3 -4
- muffin_rest/options.py +1 -1
- muffin_rest/peewee/filters.py +8 -5
- muffin_rest/peewee/handler.py +11 -11
- muffin_rest/peewee/openapi.py +1 -1
- muffin_rest/peewee/schemas.py +0 -2
- muffin_rest/peewee/sorting.py +4 -4
- muffin_rest/peewee/utils.py +2 -2
- muffin_rest/sorting.py +4 -3
- muffin_rest/sqlalchemy/__init__.py +14 -15
- muffin_rest/sqlalchemy/filters.py +4 -4
- muffin_rest/sqlalchemy/sorting.py +11 -6
- muffin_rest/types.py +0 -2
- muffin_rest-12.0.1.dist-info/METADATA +176 -0
- muffin_rest-12.0.1.dist-info/RECORD +39 -0
- muffin_rest-11.0.1.dist-info/METADATA +0 -182
- muffin_rest-11.0.1.dist-info/RECORD +0 -39
- {muffin_rest-11.0.1.dist-info → muffin_rest-12.0.1.dist-info}/LICENSE +0 -0
- {muffin_rest-11.0.1.dist-info → muffin_rest-12.0.1.dist-info}/WHEEL +0 -0
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
|
|
|
@@ -11,6 +11,7 @@ from muffin_rest.sorting import Sort, Sorting
|
|
|
11
11
|
from .utils import get_model_field_by_name
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
+
from . import PWRESTHandler
|
|
14
15
|
from .types import TVCollection
|
|
15
16
|
|
|
16
17
|
|
|
@@ -34,14 +35,13 @@ class PWSorting(Sorting):
|
|
|
34
35
|
"""Prepare collection for sorting."""
|
|
35
36
|
return collection.order_by()
|
|
36
37
|
|
|
37
|
-
def convert(self, obj:
|
|
38
|
+
def convert(self, obj: str | Field | PWSort, **meta):
|
|
38
39
|
"""Prepare sorters."""
|
|
39
|
-
from . import PWRESTHandler
|
|
40
40
|
|
|
41
41
|
if isinstance(obj, PWSort):
|
|
42
42
|
return obj
|
|
43
43
|
|
|
44
|
-
handler = cast(PWRESTHandler, self.handler)
|
|
44
|
+
handler = cast("PWRESTHandler", self.handler)
|
|
45
45
|
|
|
46
46
|
if isinstance(obj, Field):
|
|
47
47
|
name, field = obj.name, obj
|
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)
|
muffin_rest/sorting.py
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"""Implement sorting."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
from typing import TYPE_CHECKING, Any, Generator, Iterable, Mapping, Sequence, cast
|
|
5
6
|
|
|
6
|
-
from .types import TVCollection
|
|
7
7
|
from .utils import Mutate, Mutator
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from muffin import Request
|
|
11
11
|
|
|
12
12
|
from .handler import RESTBase
|
|
13
|
+
from .types import TVCollection
|
|
13
14
|
|
|
14
15
|
SORT_PARAM = "sort"
|
|
15
16
|
|
|
@@ -60,14 +61,14 @@ class Sorting(Mutator):
|
|
|
60
61
|
|
|
61
62
|
def convert(self, obj, **meta) -> Sort:
|
|
62
63
|
"""Prepare sorters."""
|
|
63
|
-
sort = cast(Sort, super(Sorting, self).convert(obj, **meta))
|
|
64
|
+
sort = cast("Sort", super(Sorting, self).convert(obj, **meta))
|
|
64
65
|
if sort.meta.get("default"):
|
|
65
66
|
self.default.append(sort)
|
|
66
67
|
return sort
|
|
67
68
|
|
|
68
69
|
def sort_default(self, collection: TVCollection) -> TVCollection:
|
|
69
70
|
"""Sort by default."""
|
|
70
|
-
return cast(TVCollection, sorted(collection))
|
|
71
|
+
return cast("TVCollection", sorted(collection))
|
|
71
72
|
|
|
72
73
|
@property
|
|
73
74
|
def openapi(self):
|
|
@@ -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,11 +18,10 @@ 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
|
|
|
25
|
-
ModelConverter._get_field_name = lambda _, prop_or_column: str(prop_or_column.key) # type: ignore[
|
|
24
|
+
ModelConverter._get_field_name = lambda _, prop_or_column: str(prop_or_column.key) # type: ignore[assignment]
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
class SQLAlchemyAutoSchema(BaseSQLAlchemyAutoSchema):
|
|
@@ -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
|
|
|
@@ -152,19 +151,19 @@ class SARESTHandler(RESTHandler[TVResource]):
|
|
|
152
151
|
resource = await self.meta.database.fetch_one(qs)
|
|
153
152
|
if resource is None:
|
|
154
153
|
raise APIError.NOT_FOUND("Resource not found")
|
|
155
|
-
return cast(TVResource, dict(resource))
|
|
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()
|
|
167
|
-
table_pk = cast(sa.Column, meta.table_pk)
|
|
166
|
+
table_pk = cast("sa.Column", meta.table_pk)
|
|
168
167
|
if update:
|
|
169
168
|
update_query = self.meta.table.update().where(table_pk == resource[table_pk.name]) # type: ignore[call-overload]
|
|
170
169
|
await meta.database.execute(update_query, resource)
|
|
@@ -174,14 +173,14 @@ 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
|
-
table_pk = cast(sa.Column, self.meta.table_pk)
|
|
178
|
+
table_pk = cast("sa.Column", self.meta.table_pk)
|
|
180
179
|
pks = [resource[table_pk.name]] if resource else await request.data()
|
|
181
180
|
if not pks:
|
|
182
181
|
raise APIError.NOT_FOUND()
|
|
183
182
|
|
|
184
|
-
delete = self.meta.table.delete().where(table_pk.in_(cast(list[Any], pks)))
|
|
183
|
+
delete = self.meta.table.delete().where(table_pk.in_(cast("list[Any]", pks)))
|
|
185
184
|
await self.meta.database.execute(delete)
|
|
186
185
|
|
|
187
186
|
delete = remove
|
|
@@ -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
|
|
|
@@ -11,6 +11,7 @@ from muffin_rest.filters import Filter, Filters
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from muffin_rest.types import TFilterValue
|
|
13
13
|
|
|
14
|
+
from . import SARESTHandler
|
|
14
15
|
from .types import TVCollection
|
|
15
16
|
|
|
16
17
|
|
|
@@ -49,11 +50,10 @@ class SAFilters(Filters):
|
|
|
49
50
|
|
|
50
51
|
MUTATE_CLASS = SAFilter
|
|
51
52
|
|
|
52
|
-
def convert(self, obj:
|
|
53
|
+
def convert(self, obj: str | Column | SAFilter, **meta):
|
|
53
54
|
"""Convert params to filters."""
|
|
54
|
-
from . import SARESTHandler
|
|
55
55
|
|
|
56
|
-
handler = cast(SARESTHandler, self.handler)
|
|
56
|
+
handler = cast("SARESTHandler", self.handler)
|
|
57
57
|
|
|
58
58
|
if isinstance(obj, SAFilter):
|
|
59
59
|
if obj.field is None:
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"""Support sorting for SQLAlchemy ORM."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
|
-
from typing import TYPE_CHECKING,
|
|
5
|
+
from typing import TYPE_CHECKING, cast
|
|
5
6
|
|
|
6
7
|
from sqlalchemy import Column
|
|
7
8
|
|
|
8
9
|
from muffin_rest.sorting import Sort, Sorting
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
12
|
+
from . import SARESTHandler
|
|
11
13
|
from .types import TVCollection
|
|
12
14
|
|
|
13
15
|
|
|
@@ -15,7 +17,11 @@ class SASort(Sort):
|
|
|
15
17
|
"""Sorter for Peewee."""
|
|
16
18
|
|
|
17
19
|
async def apply(
|
|
18
|
-
self,
|
|
20
|
+
self,
|
|
21
|
+
collection: TVCollection,
|
|
22
|
+
*,
|
|
23
|
+
desc: bool = False,
|
|
24
|
+
**_,
|
|
19
25
|
) -> TVCollection:
|
|
20
26
|
"""Sort the collection."""
|
|
21
27
|
field = self.field
|
|
@@ -30,21 +36,20 @@ class SASorting(Sorting):
|
|
|
30
36
|
|
|
31
37
|
MUTATE_CLASS = SASort
|
|
32
38
|
|
|
33
|
-
def convert(self, obj:
|
|
39
|
+
def convert(self, obj: str | Column | SASort, **meta):
|
|
34
40
|
"""Prepare sorters."""
|
|
35
|
-
from . import SARESTHandler
|
|
36
41
|
|
|
37
42
|
if isinstance(obj, SASort):
|
|
38
43
|
return obj
|
|
39
44
|
|
|
40
|
-
handler = cast(SARESTHandler, self.handler)
|
|
45
|
+
handler = cast("SARESTHandler", self.handler)
|
|
41
46
|
|
|
42
47
|
if isinstance(obj, Column):
|
|
43
48
|
name, field = obj.name, obj
|
|
44
49
|
|
|
45
50
|
else:
|
|
46
51
|
name = obj
|
|
47
|
-
field = meta.get("field", handler.meta.table.c.get(name))
|
|
52
|
+
field = meta.get("field", handler.meta.table.c.get(name)) # type: ignore[assignment]
|
|
48
53
|
|
|
49
54
|
if field is not None:
|
|
50
55
|
sort = self.MUTATE_CLASS(name, field=field, **meta)
|
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,176 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: muffin-rest
|
|
3
|
+
Version: 12.0.1
|
|
4
|
+
Summary: The package provides enhanced support for writing REST APIs with Muffin framework
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: rest,api,muffin,asgi,asyncio,trio
|
|
7
|
+
Author: Kirill Klenov
|
|
8
|
+
Author-email: horneds@gmail.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Framework :: AsyncIO
|
|
12
|
+
Classifier: Framework :: Trio
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Provides-Extra: peewee
|
|
23
|
+
Provides-Extra: sqlalchemy
|
|
24
|
+
Provides-Extra: yaml
|
|
25
|
+
Requires-Dist: apispec (>=6,<7)
|
|
26
|
+
Requires-Dist: marshmallow (>=3,<4)
|
|
27
|
+
Requires-Dist: marshmallow-peewee ; extra == "peewee"
|
|
28
|
+
Requires-Dist: marshmallow-sqlalchemy ; extra == "sqlalchemy"
|
|
29
|
+
Requires-Dist: muffin
|
|
30
|
+
Requires-Dist: muffin-databases ; extra == "sqlalchemy"
|
|
31
|
+
Requires-Dist: muffin-peewee-aio ; extra == "peewee"
|
|
32
|
+
Requires-Dist: pyyaml ; extra == "yaml"
|
|
33
|
+
Requires-Dist: sqlalchemy ; extra == "sqlalchemy"
|
|
34
|
+
Project-URL: Homepage, https://github.com/klen/muffin-rest
|
|
35
|
+
Project-URL: Repository, https://github.com/klen/muffin-rest
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# Muffin‑REST
|
|
39
|
+
|
|
40
|
+
**Muffin‑REST** simplifies building RESTful APIs with [Muffin](https://github.com/klen/muffin) by offering:
|
|
41
|
+
|
|
42
|
+
- Declarative `API` class with resource registration
|
|
43
|
+
- Built-in filtering, sorting, pagination, and search
|
|
44
|
+
- Support for:
|
|
45
|
+
- [Peewee ORM](http://docs.peewee-orm.com/en/latest/) via `PeeweeEndpoint`
|
|
46
|
+
- [SQLAlchemy Core](https://docs.sqlalchemy.org/en/14/core/) via `SAEndpoint`
|
|
47
|
+
- [MongoDB](https://www.mongodb.com/) via `MongoEndpoint`
|
|
48
|
+
- [Swagger/OpenAPI](https://swagger.io/) autodocumentation
|
|
49
|
+
- Works with asyncio, Trio, and Curio
|
|
50
|
+
|
|
51
|
+
[](https://github.com/klen/muffin-rest/actions)
|
|
52
|
+
[](https://pypi.org/project/muffin-rest/)
|
|
53
|
+
[](https://pypi.org/project/muffin-rest/)
|
|
54
|
+
|
|
55
|
+
## Requirements
|
|
56
|
+
|
|
57
|
+
- Python >= 3.10
|
|
58
|
+
- Trio requires Peewee backend
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
Install core package:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install muffin-rest
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Add optional backend support:
|
|
69
|
+
|
|
70
|
+
- SQLAlchemy Core: `pip install muffin-rest[sqlalchemy]`
|
|
71
|
+
- Peewee ORM: `pip install muffin-rest[peewee]`
|
|
72
|
+
- YAML support for Swagger: `pip install muffin-rest[yaml]`
|
|
73
|
+
|
|
74
|
+
## Quickstart (Peewee example)
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from muffin import Application
|
|
78
|
+
from muffin_rest import API
|
|
79
|
+
from muffin_rest.peewee import PeeweeEndpoint
|
|
80
|
+
from models import User # your Peewee model
|
|
81
|
+
|
|
82
|
+
app = Application("myapp")
|
|
83
|
+
api = API(title="User Service", version="1.0")
|
|
84
|
+
|
|
85
|
+
@api.route
|
|
86
|
+
class UsersEndpoint(PeeweeEndpoint):
|
|
87
|
+
class Meta:
|
|
88
|
+
model = User
|
|
89
|
+
lookup_field = "id"
|
|
90
|
+
filters = ["name", "email"]
|
|
91
|
+
ordering = ["-created_at"]
|
|
92
|
+
|
|
93
|
+
api.setup(app, prefix="/api", swagger=True)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Endpoints available:
|
|
97
|
+
|
|
98
|
+
- `GET /api/users/` — list with pagination, search, filtering
|
|
99
|
+
- `POST /api/users/` — create
|
|
100
|
+
- `GET /api/users/{id}/` — retrieve
|
|
101
|
+
- `PUT /api/users/{id}/` — replace
|
|
102
|
+
- `PATCH /api/users/{id}/` — update
|
|
103
|
+
- `DELETE /api/users/{id}/` — remove
|
|
104
|
+
- Docs: `/api/docs/`, OpenAPI spec: `/api/openapi.json`
|
|
105
|
+
|
|
106
|
+
## Usage with SQLAlchemy
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from muffin_rest import API
|
|
110
|
+
from muffin_rest.sqlalchemy import SAEndpoint
|
|
111
|
+
from models import my_table, db_engine
|
|
112
|
+
|
|
113
|
+
api = API()
|
|
114
|
+
@api.route
|
|
115
|
+
class MySAEndpoint(SAEndpoint):
|
|
116
|
+
class Meta:
|
|
117
|
+
table = my_table
|
|
118
|
+
database = db_engine
|
|
119
|
+
|
|
120
|
+
api.setup(app)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Usage with MongoDB
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from muffin_rest import API
|
|
127
|
+
from muffin_rest.mongo import MongoEndpoint
|
|
128
|
+
from models import mongo_collection
|
|
129
|
+
|
|
130
|
+
api = API()
|
|
131
|
+
@api.route
|
|
132
|
+
class MyMongoEndpoint(MongoEndpoint):
|
|
133
|
+
class Meta:
|
|
134
|
+
collection = mongo_collection
|
|
135
|
+
|
|
136
|
+
api.setup(app)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Advanced Configuration
|
|
140
|
+
|
|
141
|
+
Customize Swagger and routes via constructor:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
api = API(
|
|
145
|
+
title="Service API",
|
|
146
|
+
version="2.1",
|
|
147
|
+
swagger_ui=True,
|
|
148
|
+
openapi_path="/api/openapi.json",
|
|
149
|
+
docs_path="/api/docs/"
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Contributing & Examples
|
|
154
|
+
|
|
155
|
+
- See `examples/` for live application demos
|
|
156
|
+
- Tests in `tests/` focus on filtering, pagination, status codes
|
|
157
|
+
- Check `CHANGELOG.md` for latest changes
|
|
158
|
+
|
|
159
|
+
## Bug Tracker
|
|
160
|
+
|
|
161
|
+
Report bugs or request features:
|
|
162
|
+
https://github.com/klen/muffin-rest/issues
|
|
163
|
+
|
|
164
|
+
## Contributing
|
|
165
|
+
|
|
166
|
+
Repo: https://github.com/klen/muffin-rest
|
|
167
|
+
Pull requests, example additions, docs improvements welcome!
|
|
168
|
+
|
|
169
|
+
## Contributors
|
|
170
|
+
|
|
171
|
+
- [klen](https://github.com/klen) (Kirill Klenov)
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
Licensed under the [MIT license](http://opensource.org/licenses/MIT).
|
|
176
|
+
|
|
@@ -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,182 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: muffin-rest
|
|
3
|
-
Version: 11.0.1
|
|
4
|
-
Summary: The package provides enhanced support for writing REST APIs with Muffin framework
|
|
5
|
-
License: MIT
|
|
6
|
-
Keywords: rest,api,muffin,asgi,asyncio,trio
|
|
7
|
-
Author: Kirill Klenov
|
|
8
|
-
Author-email: horneds@gmail.com
|
|
9
|
-
Requires-Python: >=3.9,<4.0
|
|
10
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
-
Classifier: Framework :: AsyncIO
|
|
12
|
-
Classifier: Framework :: Trio
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Programming Language :: Python
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
-
Provides-Extra: peewee
|
|
24
|
-
Provides-Extra: sqlalchemy
|
|
25
|
-
Provides-Extra: yaml
|
|
26
|
-
Requires-Dist: apispec (>=6,<7)
|
|
27
|
-
Requires-Dist: marshmallow (>=3,<4)
|
|
28
|
-
Requires-Dist: marshmallow-peewee ; extra == "peewee"
|
|
29
|
-
Requires-Dist: marshmallow-sqlalchemy ; extra == "sqlalchemy"
|
|
30
|
-
Requires-Dist: muffin (>=0,<1)
|
|
31
|
-
Requires-Dist: muffin-databases ; extra == "sqlalchemy"
|
|
32
|
-
Requires-Dist: muffin-peewee-aio ; extra == "peewee"
|
|
33
|
-
Requires-Dist: pyyaml ; extra == "yaml"
|
|
34
|
-
Requires-Dist: sqlalchemy ; extra == "sqlalchemy"
|
|
35
|
-
Project-URL: Homepage, https://github.com/klen/muffin-rest
|
|
36
|
-
Project-URL: Repository, https://github.com/klen/muffin-rest
|
|
37
|
-
Description-Content-Type: text/x-rst
|
|
38
|
-
|
|
39
|
-
Muffin-REST
|
|
40
|
-
###########
|
|
41
|
-
|
|
42
|
-
.. _description:
|
|
43
|
-
|
|
44
|
-
**Muffin-REST** -- provides enhanced support for writing REST APIs with Muffin_.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
.. _badges:
|
|
48
|
-
|
|
49
|
-
.. image:: https://github.com/klen/muffin-rest/workflows/tests/badge.svg
|
|
50
|
-
:target: https://github.com/klen/muffin-rest/actions
|
|
51
|
-
:alt: Tests Status
|
|
52
|
-
|
|
53
|
-
.. image:: https://img.shields.io/pypi/v/muffin-rest
|
|
54
|
-
:target: https://pypi.org/project/muffin-rest/
|
|
55
|
-
:alt: PYPI Version
|
|
56
|
-
|
|
57
|
-
.. image:: https://img.shields.io/pypi/pyversions/muffin-rest
|
|
58
|
-
:target: https://pypi.org/project/muffin-rest/
|
|
59
|
-
:alt: Python Versions
|
|
60
|
-
|
|
61
|
-
----------
|
|
62
|
-
|
|
63
|
-
.. _features:
|
|
64
|
-
|
|
65
|
-
Features
|
|
66
|
-
--------
|
|
67
|
-
|
|
68
|
-
- API class to simplify the creation of REST APIs;
|
|
69
|
-
- Automatic filtering and sorting for resources;
|
|
70
|
-
- Support for `Peewee ORM`_, Mongo_, `SQLAlchemy Core`_;
|
|
71
|
-
- Auto documentation with Swagger_;
|
|
72
|
-
|
|
73
|
-
.. _contents:
|
|
74
|
-
|
|
75
|
-
.. contents::
|
|
76
|
-
|
|
77
|
-
.. _requirements:
|
|
78
|
-
|
|
79
|
-
Requirements
|
|
80
|
-
=============
|
|
81
|
-
|
|
82
|
-
- python >= 3.9
|
|
83
|
-
|
|
84
|
-
.. note:: Trio is only supported with Peewee ORM
|
|
85
|
-
|
|
86
|
-
.. _installation:
|
|
87
|
-
|
|
88
|
-
Installation
|
|
89
|
-
=============
|
|
90
|
-
|
|
91
|
-
**Muffin-REST** should be installed using pip: ::
|
|
92
|
-
|
|
93
|
-
pip install muffin-rest
|
|
94
|
-
|
|
95
|
-
With `SQLAlchemy Core`_ support: ::
|
|
96
|
-
|
|
97
|
-
pip install muffin-rest[sqlalchemy]
|
|
98
|
-
|
|
99
|
-
With `Peewee ORM`_ support: ::
|
|
100
|
-
|
|
101
|
-
pip install muffin-rest[peewee]
|
|
102
|
-
|
|
103
|
-
With YAML support for autodocumentation: ::
|
|
104
|
-
|
|
105
|
-
pip install muffin-rest[yaml]
|
|
106
|
-
|
|
107
|
-
.. _usage:
|
|
108
|
-
|
|
109
|
-
Usage
|
|
110
|
-
=====
|
|
111
|
-
|
|
112
|
-
Create an API:
|
|
113
|
-
|
|
114
|
-
.. code-block:: python
|
|
115
|
-
|
|
116
|
-
from muffin_rest import API
|
|
117
|
-
|
|
118
|
-
api = API()
|
|
119
|
-
|
|
120
|
-
Create endpoints and connect them to the API (example for sqlalchemy):
|
|
121
|
-
|
|
122
|
-
.. code-block:: python
|
|
123
|
-
|
|
124
|
-
from muffin_rest.sqlalchemy import SAEndpoint
|
|
125
|
-
from project.api import api
|
|
126
|
-
|
|
127
|
-
@api.route
|
|
128
|
-
class MyEndpoint(SAEndpoint):
|
|
129
|
-
|
|
130
|
-
class Meta:
|
|
131
|
-
table = MyTable
|
|
132
|
-
database = db
|
|
133
|
-
|
|
134
|
-
Connect it to your Muffin_ application:
|
|
135
|
-
|
|
136
|
-
.. code-block:: python
|
|
137
|
-
|
|
138
|
-
from project.api import api
|
|
139
|
-
|
|
140
|
-
api.setup(app, prefix='/api/v1')
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.. _bugtracker:
|
|
144
|
-
|
|
145
|
-
Bug tracker
|
|
146
|
-
===========
|
|
147
|
-
|
|
148
|
-
If you have any suggestions, bug reports or
|
|
149
|
-
annoyances please report them to the issue tracker
|
|
150
|
-
at https://github.com/klen/muffin-rest/issues
|
|
151
|
-
|
|
152
|
-
.. _contributing:
|
|
153
|
-
|
|
154
|
-
Contributing
|
|
155
|
-
============
|
|
156
|
-
|
|
157
|
-
Development of Muffin-REST happens at: https://github.com/klen/muffin-rest
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
Contributors
|
|
161
|
-
=============
|
|
162
|
-
|
|
163
|
-
* klen_ (Kirill Klenov)
|
|
164
|
-
|
|
165
|
-
.. _license:
|
|
166
|
-
|
|
167
|
-
License
|
|
168
|
-
========
|
|
169
|
-
|
|
170
|
-
Licensed under a `MIT license`_.
|
|
171
|
-
|
|
172
|
-
.. _links:
|
|
173
|
-
|
|
174
|
-
.. _klen: https://github.com/klen
|
|
175
|
-
.. _Muffin: https://github.com/klen/muffin
|
|
176
|
-
.. _Swagger: https://swagger.io/tools/swagger-ui/
|
|
177
|
-
.. _Mongo: https://www.mongodb.com/
|
|
178
|
-
.. _Peewee ORM: http://docs.peewee-orm.com/en/latest/
|
|
179
|
-
.. _SqlAlchemy Core: https://docs.sqlalchemy.org/en/14/core/
|
|
180
|
-
|
|
181
|
-
.. _MIT license: http://opensource.org/licenses/MIT
|
|
182
|
-
|