muffin-rest 11.0.1__py3-none-any.whl → 11.0.3__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 CHANGED
@@ -17,24 +17,24 @@ Api = API
17
17
 
18
18
  __all__ = (
19
19
  "API",
20
- "Api",
21
- "RESTHandler",
22
20
  "APIError",
23
- "PWRESTHandler",
21
+ "Api",
22
+ "MongoFilter",
23
+ "MongoFilters",
24
+ "MongoRESTHandler",
25
+ "MongoSort",
26
+ "MongoSorting",
24
27
  "PWFilter",
25
28
  "PWFilters",
29
+ "PWRESTHandler",
26
30
  "PWSort",
27
31
  "PWSorting",
28
- "SARESTHandler",
32
+ "RESTHandler",
29
33
  "SAFilter",
30
34
  "SAFilters",
35
+ "SARESTHandler",
31
36
  "SASort",
32
37
  "SASorting",
33
- "MongoRESTHandler",
34
- "MongoFilter",
35
- "MongoFilters",
36
- "MongoSort",
37
- "MongoSorting",
38
38
  )
39
39
 
40
40
  # Support Peewee ORM
muffin_rest/filters.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import operator
6
- from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterable, Mapping, Optional # py39
6
+ from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterable, Mapping
7
7
 
8
8
  import marshmallow as ma
9
9
  from asgi_tools._compat import json_loads
@@ -59,8 +59,8 @@ class Filter(Mutate):
59
59
  name: str,
60
60
  *,
61
61
  field: Any = None,
62
- schema_field: Optional[ma.fields.Field] = None,
63
- operator: Optional[str] = None,
62
+ schema_field: ma.fields.Field | None = None,
63
+ operator: str | None = None,
64
64
  **meta,
65
65
  ):
66
66
  """Initialize filter.
@@ -75,7 +75,7 @@ class Filter(Mutate):
75
75
  self.schema_field = schema_field or self.schema_field
76
76
  self.default_operator = operator or self.default_operator
77
77
 
78
- async def apply(self, collection: Any, data: Optional[Mapping] = None):
78
+ async def apply(self, collection: Any, data: Mapping | None = None):
79
79
  """Filter given collection."""
80
80
  if not data:
81
81
  return None, collection
muffin_rest/handler.py CHANGED
@@ -39,7 +39,7 @@ class RESTHandlerMeta(HandlerMeta):
39
39
 
40
40
  def __new__(mcs, name, bases, params):
41
41
  """Prepare options for the handler."""
42
- kls = cast(type["RESTBase"], super().__new__(mcs, name, bases, params))
42
+ kls = cast("type[RESTBase]", super().__new__(mcs, name, bases, params))
43
43
  kls.meta = kls.meta_class(kls)
44
44
 
45
45
  if getattr(kls.meta, kls.meta_class.base_property, None) is not None:
@@ -237,8 +237,10 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
237
237
  ) -> TVData[TVResource]:
238
238
  """Load data from request and create/update a resource."""
239
239
  schema = self.get_schema(request, resource=resource, **schema_options)
240
- data = cast(Union[Mapping, list], await self.load_data(request))
241
- return cast(TVData[TVResource], await load_data(data, schema, partial=resource is not None))
240
+ data = cast("Union[Mapping, list]", await self.load_data(request))
241
+ return cast(
242
+ "TVData[TVResource]", await load_data(data, schema, partial=resource is not None)
243
+ )
242
244
 
243
245
  @overload
244
246
  async def dump( # type: ignore[misc]
@@ -269,7 +271,7 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
269
271
  if resource
270
272
  else self.dump(request, data=self.collection, many=True)
271
273
  )
272
- return ResponseJSON(res)
274
+ return ResponseJSON(res) # type: ignore[type-var]
273
275
 
274
276
  async def post(
275
277
  self, request: Request, *, resource: Optional[TVResource] = None
@@ -283,7 +285,7 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
283
285
  if many:
284
286
  data = await self.save_many(request, data, update=resource is not None)
285
287
  else:
286
- data = await self.save(request, cast(TVResource, data), update=resource is not None)
288
+ data = await self.save(request, cast("TVResource", data), update=resource is not None)
287
289
 
288
290
  res = await self.dump(request, data, many=many)
289
291
  return ResponseJSON(res)
@@ -1,4 +1,5 @@
1
1
  """Mongo DB support."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import TYPE_CHECKING, Optional, cast
@@ -68,7 +69,7 @@ class MongoRESTHandler(RESTHandler[TVResource]):
68
69
  counts = list(self.collection.aggregate(pipeline_num))
69
70
  return (
70
71
  self.collection.aggregate(pipeline_all),
71
- counts and counts[0]["total"] or 0, # type: ignore[]
72
+ (counts and counts[0]["total"]) or 0, # type: ignore[]
72
73
  )
73
74
  total = None
74
75
  if self.meta.limit_total:
@@ -118,7 +119,9 @@ class MongoRESTHandler(RESTHandler[TVResource]):
118
119
  async def delete(self, request: Request, resource: Optional[TVResource] = None):
119
120
  """Remove the given resource(s)."""
120
121
  meta = self.meta
121
- oids = [resource[meta.collection_id]] if resource else cast(list[str], await request.data())
122
+ oids = (
123
+ [resource[meta.collection_id]] if resource else cast("list[str]", await request.data())
124
+ )
122
125
  if not oids:
123
126
  raise APIError.NOT_FOUND()
124
127
 
@@ -73,12 +73,12 @@ class MongoChain:
73
73
  ) -> Awaitable:
74
74
  """Apply filters and return cursor."""
75
75
  query = self.__update__(query)
76
- query = query and {"$and": query} or {}
76
+ query = (query and {"$and": query}) or {}
77
77
  return self.collection.find_one(query, projection=projection)
78
78
 
79
79
  def count(self) -> Awaitable[int]:
80
80
  """Count documents."""
81
- query = self.query and {"$and": self.query} or {}
81
+ query = (self.query and {"$and": self.query}) or {}
82
82
  return self.collection.count_documents(query)
83
83
 
84
84
  def aggregate(self, pipeline, **kwargs):
@@ -121,7 +121,7 @@ class MongoChain:
121
121
 
122
122
  def __iter__(self):
123
123
  """Iterate by self collection."""
124
- query = self.query and {"$and": self.query} or {}
124
+ query = (self.query and {"$and": self.query}) or {}
125
125
  if self.sorting:
126
126
  return self.collection.find(query, self.projection).sort(self.sorting)
127
127
 
@@ -130,7 +130,7 @@ class MongoChain:
130
130
  def __getattr__(self, name):
131
131
  """Proxy any attributes except find to self.collection."""
132
132
  if name in self.CURSOR_METHODS:
133
- query = self.query and {"$and": self.query} or {}
133
+ query = (self.query and {"$and": self.query}) or {}
134
134
  cursor = self.collection.find(query, self.projection)
135
135
  if self.sorting:
136
136
  cursor = cursor.sort(self.sorting)
muffin_rest/openapi.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Create openapi schema from the given API."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import inspect
@@ -88,7 +89,7 @@ def route_to_spec(route: Route, spec: APISpec, tags: dict) -> dict:
88
89
  for param in route.params:
89
90
  results["parameters"].append({"in": "path", "name": param})
90
91
 
91
- target = cast(Callable, route.target)
92
+ target = cast("Callable", route.target)
92
93
  if isinstance(target, partial):
93
94
  target = target.func
94
95
 
@@ -15,6 +15,8 @@ from .utils import get_model_field_by_name
15
15
  if TYPE_CHECKING:
16
16
  from muffin_rest.types import TFilterValue
17
17
 
18
+ from . import PWRESTHandler
19
+
18
20
 
19
21
  class PWFilter(Filter):
20
22
  """Support Peewee."""
@@ -41,7 +43,9 @@ class PWFilter(Filter):
41
43
  """Apply the filters to Peewee QuerySet.."""
42
44
  column = self.field
43
45
  if isinstance(column, ColumnBase):
44
- collection = cast(ModelSelect, collection.where(*[op(column, val) for op, val in ops]))
46
+ collection = cast(
47
+ "ModelSelect", collection.where(*[op(column, val) for op, val in ops])
48
+ )
45
49
  return collection
46
50
 
47
51
 
@@ -52,9 +56,8 @@ class PWFilters(Filters):
52
56
 
53
57
  def convert(self, obj: Union[str, Field, PWFilter], **meta):
54
58
  """Convert params to filters."""
55
- from . import PWRESTHandler
56
59
 
57
- handler = cast(PWRESTHandler, self.handler)
60
+ handler = cast("PWRESTHandler", self.handler)
58
61
  if isinstance(obj, PWFilter):
59
62
  return obj
60
63
 
@@ -5,7 +5,6 @@ from __future__ import annotations
5
5
  from typing import TYPE_CHECKING, Any, Optional, Union, cast, overload
6
6
 
7
7
  import marshmallow as ma
8
- import peewee as pw
9
8
  from apispec.ext.marshmallow import MarshmallowPlugin
10
9
  from marshmallow_peewee import ForeignKey
11
10
  from peewee_aio.model import AIOModel, AIOModelSelect
@@ -19,6 +18,7 @@ from .schemas import EnumField
19
18
  from .types import TVModel
20
19
 
21
20
  if TYPE_CHECKING:
21
+ import peewee as pw
22
22
  from muffin import Request
23
23
  from peewee_aio.types import TVAIOModel
24
24
 
@@ -89,7 +89,7 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
89
89
  async def paginate(self, _: Request, *, limit: int = 0, offset: int = 0): # type: ignore[override]
90
90
  """Paginate the collection."""
91
91
  if self.meta.limit_total:
92
- cqs = cast(pw.ModelSelect, self.collection.order_by())
92
+ cqs = cast("pw.ModelSelect", self.collection.order_by())
93
93
  if cqs._group_by: # type: ignore[misc]
94
94
  cqs._returning = cqs._group_by # type: ignore[misc]
95
95
  cqs._having = None # type: ignore[misc]
@@ -131,7 +131,7 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
131
131
  if not data:
132
132
  return
133
133
 
134
- model_pk = cast(pw.Field, meta.model_pk)
134
+ model_pk = cast("pw.Field", meta.model_pk)
135
135
  resources = await meta.manager.fetchall(self.collection.where(model_pk << data)) # type: ignore[]
136
136
 
137
137
  if not resources:
@@ -13,5 +13,3 @@ def build_field(field, opts, **params):
13
13
 
14
14
 
15
15
  DefaultConverter.register(URLField, ma.fields.Url)
16
-
17
- # ruff: noqa: ARG001, ARG002
@@ -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
 
@@ -36,12 +37,11 @@ class PWSorting(Sorting):
36
37
 
37
38
  def convert(self, obj: Union[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/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):
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
22
22
 
23
23
  from .types import TVResource
24
24
 
25
- ModelConverter._get_field_name = lambda _, prop_or_column: str(prop_or_column.key) # type: ignore[method-assign]
25
+ ModelConverter._get_field_name = lambda _, prop_or_column: str(prop_or_column.key) # type: ignore[assignment]
26
26
 
27
27
 
28
28
  class SQLAlchemyAutoSchema(BaseSQLAlchemyAutoSchema):
@@ -152,7 +152,7 @@ class SARESTHandler(RESTHandler[TVResource]):
152
152
  resource = await self.meta.database.fetch_one(qs)
153
153
  if resource is None:
154
154
  raise APIError.NOT_FOUND("Resource not found")
155
- return cast(TVResource, dict(resource))
155
+ return cast("TVResource", dict(resource))
156
156
 
157
157
  def get_schema(
158
158
  self, request: Request, *, resource: Optional[TVResource] = None, **schema_options
@@ -164,7 +164,7 @@ class SARESTHandler(RESTHandler[TVResource]):
164
164
  """Save the given resource."""
165
165
  meta = self.meta
166
166
  insert_query = meta.table.insert()
167
- table_pk = cast(sa.Column, meta.table_pk)
167
+ table_pk = cast("sa.Column", meta.table_pk)
168
168
  if update:
169
169
  update_query = self.meta.table.update().where(table_pk == resource[table_pk.name]) # type: ignore[call-overload]
170
170
  await meta.database.execute(update_query, resource)
@@ -176,12 +176,12 @@ class SARESTHandler(RESTHandler[TVResource]):
176
176
 
177
177
  async def remove(self, request: Request, resource: Optional[TVResource] = None):
178
178
  """Remove the given resource."""
179
- table_pk = cast(sa.Column, self.meta.table_pk)
179
+ table_pk = cast("sa.Column", self.meta.table_pk)
180
180
  pks = [resource[table_pk.name]] if resource else await request.data()
181
181
  if not pks:
182
182
  raise APIError.NOT_FOUND()
183
183
 
184
- delete = self.meta.table.delete().where(table_pk.in_(cast(list[Any], pks)))
184
+ delete = self.meta.table.delete().where(table_pk.in_(cast("list[Any]", pks)))
185
185
  await self.meta.database.execute(delete)
186
186
 
187
187
  delete = remove
@@ -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
 
@@ -51,9 +52,8 @@ class SAFilters(Filters):
51
52
 
52
53
  def convert(self, obj: Union[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,4 +1,5 @@
1
1
  """Support sorting for SQLAlchemy ORM."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import TYPE_CHECKING, Union, cast
@@ -8,6 +9,7 @@ from sqlalchemy import Column
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, collection: TVCollection, *, desc: bool = False, **_,
20
+ self,
21
+ collection: TVCollection,
22
+ *,
23
+ desc: bool = False,
24
+ **_,
19
25
  ) -> TVCollection:
20
26
  """Sort the collection."""
21
27
  field = self.field
@@ -32,19 +38,18 @@ class SASorting(Sorting):
32
38
 
33
39
  def convert(self, obj: Union[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)
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.3
2
+ Name: muffin-rest
3
+ Version: 11.0.3
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
+ [![Tests Status](https://github.com/klen/muffin-rest/workflows/tests/badge.svg)](https://github.com/klen/muffin-rest/actions)
52
+ [![PyPI Version](https://img.shields.io/pypi/v/muffin-rest)](https://pypi.org/project/muffin-rest/)
53
+ [![Python Versions](https://img.shields.io/pypi/pyversions/muffin-rest)](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
+
@@ -1,39 +1,39 @@
1
- muffin_rest/__init__.py,sha256=NBZeOEJgQHtFFhVgd9d0fpApFRgU405sbm0cu1y1MOU,1242
1
+ muffin_rest/__init__.py,sha256=JnwsHQNDmJpOqETeIjjPheG891T5WQ0JBaiEV72Wcmg,1242
2
2
  muffin_rest/api.py,sha256=zssoHjqTsa8UCAyxj6TxQVluYPX9Sigyiph6SRxBY6I,3870
3
3
  muffin_rest/errors.py,sha256=mxEBhNPo3pwpG2em6zaQonbfRgHFBJ3I8WunVYWDjvM,1163
4
- muffin_rest/filters.py,sha256=hc-fhBODwrgJM_CvXNtTVH6jIOkjLog8En0iKKYXAGU,5947
5
- muffin_rest/handler.py,sha256=xRKUMzR2zISva0SbXS-piQ3Xq3d1zu-rZg6H2K7Oy0I,10824
4
+ muffin_rest/filters.py,sha256=nWlwDLuId-mmP3tgZjp1joYCVx0k9ekV42fPIMMKphM,5920
5
+ muffin_rest/handler.py,sha256=lgZ0KVnzVye8eMZ4qPuo-cSCvg8Vh_fFfTw5g7pJNio,10878
6
6
  muffin_rest/limits.py,sha256=Fnlu4Wj3B-BzpahLK-rDbd1GEd7CEQ3zxyOD0vee7GE,2007
7
7
  muffin_rest/marshmallow.py,sha256=UeHxZLtETlrheDAlMZn7Xv_7vWPxvJQo-sj05nql9gM,574
8
- muffin_rest/mongo/__init__.py,sha256=SiYSbX6ySJl43fw9aGREIs8ZsS8Qk_ieizoPOj4DjJc,4656
8
+ muffin_rest/mongo/__init__.py,sha256=AVOM6ydkEZ4CAOefOlpNnDx0dlD65LdhAkLh_VR8Qf4,4685
9
9
  muffin_rest/mongo/filters.py,sha256=yIxIDVqMn6SoDgVhCqiTxYetw0hoaf_3jIvX2Vnizok,964
10
10
  muffin_rest/mongo/schema.py,sha256=y4OEPQnlV_COTIIQ3cKmpqDpD2r18eAWn0rijQldWm0,1205
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=mNkLM-D6gqOA9YW2Qdw0DvE2N4LRmxLAiPMKH9WLttM,3958
14
- muffin_rest/openapi.py,sha256=0QU7qrfBjGl0vl378SJC5boZZI2ogddl45fS9WL4Axw,8751
13
+ muffin_rest/mongo/utils.py,sha256=_6J-FYfU2OIjgFLF9JHRr98vHLzW4ACeL62pBXiuF_M,3966
14
+ muffin_rest/openapi.py,sha256=kpynUbNmK78L-2xTbn13FBgmPWLPGMKF_yejVGfHLwg,8754
15
15
  muffin_rest/options.py,sha256=38y7AasyAghXNffxX-3xgqLbWDQ1RxAhg77ze7c0Mu0,2232
16
16
  muffin_rest/peewee/__init__.py,sha256=94DSj_ftT6fbPksHlBv40AH2HWaiZommUFOMN2jd9a4,129
17
- muffin_rest/peewee/filters.py,sha256=p813eJqyTkAhmS3C1P8rWFWb9Tl33OtADjgLctqKnns,2475
18
- muffin_rest/peewee/handler.py,sha256=cpHbtwPVyNp3FJtJr4drxvFyM_mq-JgwNBF4wvmYdIg,5461
17
+ muffin_rest/peewee/filters.py,sha256=BL6Zd8TI7wc7ZacZJTPVmTPfsKk6v8Z634HA5t798c0,2506
18
+ muffin_rest/peewee/handler.py,sha256=qiJNZRj-7wHO9Fdkt8uV_lPmWJtBvjZQL-I6z5_BvXQ,5469
19
19
  muffin_rest/peewee/openapi.py,sha256=lDnLnoXi33p0YeFVwRgaVrndyrG2XL93RH-BzbxinOY,1105
20
20
  muffin_rest/peewee/options.py,sha256=TimJtErC9e8B7BRiEkHiBZd71_bZbYr-FE2PIlQvfH0,1455
21
- muffin_rest/peewee/schemas.py,sha256=w6jBziUp40mOOjkz_4RCXuY0x5ZDIe9Ob25k1FnZSfc,469
22
- muffin_rest/peewee/sorting.py,sha256=aTLL2zYeNfkamfbGuKkIClOsJktdZZZlzZKafccWxyQ,1977
21
+ muffin_rest/peewee/schemas.py,sha256=hQG2UKR7gEMsz0n75L3tvkVzUX3baXMJ94emgIc1Scw,439
22
+ muffin_rest/peewee/sorting.py,sha256=3IQzA1UfMRXl6NAp1yUyTv9-pNSkObtwI1-oZdbetQc,1975
23
23
  muffin_rest/peewee/types.py,sha256=cgCXhpGHkImKwudA1lulZHz5oJswHH168AiW5MhZRCM,155
24
24
  muffin_rest/peewee/utils.py,sha256=wrDcEEeCMzK4GdMSBi7BX0XpBPRZ6pF_EHwDsZPgL1s,706
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=7k7dxElnEGiwvdfDivzcoLnAOXqpQoK52H-ss79Gw0g,2813
29
- muffin_rest/sqlalchemy/__init__.py,sha256=9MSvOXWP6665LiA5O1Icl2V05bpz6Ex5sUHB9YWKLvE,6393
30
- muffin_rest/sqlalchemy/filters.py,sha256=ejqjEZU0vqvnLwG0sX7QV3RjcXI0a2Ro_A34AAiGMEc,2489
31
- muffin_rest/sqlalchemy/sorting.py,sha256=YlFKpIet4TUy7fJ2UBLC8b9lAOwY66QBpPDDApbyh8M,1643
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
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=m27-g6BI7qdSWGym4fWALBJa2ZpWR0_m0nlrDx7iTCo,566
35
35
  muffin_rest/utils.py,sha256=c08E4HJ4SLYC-91GKPEbsyKTZ4sZbTN4qDqJbNg_HTE,2076
36
- muffin_rest-11.0.1.dist-info/LICENSE,sha256=xHPkOZhjyKBMOwXpWn9IB_BVLjrrMxv2M9slKkHj2hM,1082
37
- muffin_rest-11.0.1.dist-info/METADATA,sha256=5091MXwTsurQRHcktQMiXUurYrCVM-nhueFAjIreZ7w,4147
38
- muffin_rest-11.0.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
39
- muffin_rest-11.0.1.dist-info/RECORD,,
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,,
@@ -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
-