muffin-rest 11.0.0__tar.gz → 11.0.3__tar.gz

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.
Files changed (41) hide show
  1. muffin_rest-11.0.3/PKG-INFO +176 -0
  2. muffin_rest-11.0.3/README.md +138 -0
  3. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/filters.py +4 -4
  4. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/handler.py +7 -5
  5. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/mongo/__init__.py +5 -2
  6. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/mongo/utils.py +4 -4
  7. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/openapi.py +2 -1
  8. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/peewee/filters.py +6 -3
  9. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/peewee/handler.py +7 -7
  10. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/peewee/schemas.py +0 -2
  11. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/peewee/sorting.py +2 -2
  12. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/sorting.py +4 -3
  13. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/sqlalchemy/__init__.py +5 -5
  14. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/sqlalchemy/filters.py +2 -2
  15. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/sqlalchemy/sorting.py +9 -4
  16. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/pyproject.toml +7 -7
  17. muffin_rest-11.0.0/PKG-INFO +0 -182
  18. muffin_rest-11.0.0/README.rst +0 -143
  19. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/LICENSE +0 -0
  20. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/__init__.py +9 -9
  21. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/api.py +0 -0
  22. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/errors.py +0 -0
  23. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/limits.py +0 -0
  24. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/marshmallow.py +0 -0
  25. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/mongo/filters.py +0 -0
  26. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/mongo/schema.py +0 -0
  27. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/mongo/sorting.py +0 -0
  28. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/mongo/types.py +0 -0
  29. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/options.py +0 -0
  30. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/peewee/__init__.py +0 -0
  31. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/peewee/openapi.py +0 -0
  32. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/peewee/options.py +0 -0
  33. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/peewee/types.py +0 -0
  34. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/peewee/utils.py +0 -0
  35. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/py.typed +0 -0
  36. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/redoc.html +0 -0
  37. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/schemas.py +0 -0
  38. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/sqlalchemy/types.py +0 -0
  39. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/swagger.html +0 -0
  40. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/types.py +0 -0
  41. {muffin_rest-11.0.0 → muffin_rest-11.0.3}/muffin_rest/utils.py +0 -0
@@ -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
+
@@ -0,0 +1,138 @@
1
+ # Muffin‑REST
2
+
3
+ **Muffin‑REST** simplifies building RESTful APIs with [Muffin](https://github.com/klen/muffin) by offering:
4
+
5
+ - Declarative `API` class with resource registration
6
+ - Built-in filtering, sorting, pagination, and search
7
+ - Support for:
8
+ - [Peewee ORM](http://docs.peewee-orm.com/en/latest/) via `PeeweeEndpoint`
9
+ - [SQLAlchemy Core](https://docs.sqlalchemy.org/en/14/core/) via `SAEndpoint`
10
+ - [MongoDB](https://www.mongodb.com/) via `MongoEndpoint`
11
+ - [Swagger/OpenAPI](https://swagger.io/) autodocumentation
12
+ - Works with asyncio, Trio, and Curio
13
+
14
+ [![Tests Status](https://github.com/klen/muffin-rest/workflows/tests/badge.svg)](https://github.com/klen/muffin-rest/actions)
15
+ [![PyPI Version](https://img.shields.io/pypi/v/muffin-rest)](https://pypi.org/project/muffin-rest/)
16
+ [![Python Versions](https://img.shields.io/pypi/pyversions/muffin-rest)](https://pypi.org/project/muffin-rest/)
17
+
18
+ ## Requirements
19
+
20
+ - Python >= 3.10
21
+ - Trio requires Peewee backend
22
+
23
+ ## Installation
24
+
25
+ Install core package:
26
+
27
+ ```bash
28
+ pip install muffin-rest
29
+ ```
30
+
31
+ Add optional backend support:
32
+
33
+ - SQLAlchemy Core: `pip install muffin-rest[sqlalchemy]`
34
+ - Peewee ORM: `pip install muffin-rest[peewee]`
35
+ - YAML support for Swagger: `pip install muffin-rest[yaml]`
36
+
37
+ ## Quickstart (Peewee example)
38
+
39
+ ```python
40
+ from muffin import Application
41
+ from muffin_rest import API
42
+ from muffin_rest.peewee import PeeweeEndpoint
43
+ from models import User # your Peewee model
44
+
45
+ app = Application("myapp")
46
+ api = API(title="User Service", version="1.0")
47
+
48
+ @api.route
49
+ class UsersEndpoint(PeeweeEndpoint):
50
+ class Meta:
51
+ model = User
52
+ lookup_field = "id"
53
+ filters = ["name", "email"]
54
+ ordering = ["-created_at"]
55
+
56
+ api.setup(app, prefix="/api", swagger=True)
57
+ ```
58
+
59
+ Endpoints available:
60
+
61
+ - `GET /api/users/` — list with pagination, search, filtering
62
+ - `POST /api/users/` — create
63
+ - `GET /api/users/{id}/` — retrieve
64
+ - `PUT /api/users/{id}/` — replace
65
+ - `PATCH /api/users/{id}/` — update
66
+ - `DELETE /api/users/{id}/` — remove
67
+ - Docs: `/api/docs/`, OpenAPI spec: `/api/openapi.json`
68
+
69
+ ## Usage with SQLAlchemy
70
+
71
+ ```python
72
+ from muffin_rest import API
73
+ from muffin_rest.sqlalchemy import SAEndpoint
74
+ from models import my_table, db_engine
75
+
76
+ api = API()
77
+ @api.route
78
+ class MySAEndpoint(SAEndpoint):
79
+ class Meta:
80
+ table = my_table
81
+ database = db_engine
82
+
83
+ api.setup(app)
84
+ ```
85
+
86
+ ## Usage with MongoDB
87
+
88
+ ```python
89
+ from muffin_rest import API
90
+ from muffin_rest.mongo import MongoEndpoint
91
+ from models import mongo_collection
92
+
93
+ api = API()
94
+ @api.route
95
+ class MyMongoEndpoint(MongoEndpoint):
96
+ class Meta:
97
+ collection = mongo_collection
98
+
99
+ api.setup(app)
100
+ ```
101
+
102
+ ## Advanced Configuration
103
+
104
+ Customize Swagger and routes via constructor:
105
+
106
+ ```python
107
+ api = API(
108
+ title="Service API",
109
+ version="2.1",
110
+ swagger_ui=True,
111
+ openapi_path="/api/openapi.json",
112
+ docs_path="/api/docs/"
113
+ )
114
+ ```
115
+
116
+ ## Contributing & Examples
117
+
118
+ - See `examples/` for live application demos
119
+ - Tests in `tests/` focus on filtering, pagination, status codes
120
+ - Check `CHANGELOG.md` for latest changes
121
+
122
+ ## Bug Tracker
123
+
124
+ Report bugs or request features:
125
+ https://github.com/klen/muffin-rest/issues
126
+
127
+ ## Contributing
128
+
129
+ Repo: https://github.com/klen/muffin-rest
130
+ Pull requests, example additions, docs improvements welcome!
131
+
132
+ ## Contributors
133
+
134
+ - [klen](https://github.com/klen) (Kirill Klenov)
135
+
136
+ ## License
137
+
138
+ Licensed under the [MIT license](http://opensource.org/licenses/MIT).
@@ -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
@@ -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)
@@ -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
 
@@ -52,7 +52,7 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
52
52
  ) -> pw.ModelSelect: ...
53
53
 
54
54
  # NOTE: there is not a default sorting for peewee (conflict with muffin-admin)
55
- async def prepare_collection(self, _: Request):
55
+ async def prepare_collection(self, _: Request): # type: ignore[override]
56
56
  """Initialize Peeewee QuerySet for a binded to the resource model."""
57
57
  return self.meta.model.select()
58
58
 
@@ -86,10 +86,10 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
86
86
  self: PWRESTBase[pw.Model], _: Request, *, limit: int = 0, offset: int = 0
87
87
  ) -> tuple[pw.ModelSelect, int | None]: ...
88
88
 
89
- async def paginate(self, _: Request, *, limit: int = 0, offset: int = 0):
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]
@@ -114,9 +114,9 @@ class PWRESTBase(RESTBase[TVModel], PeeweeOpenAPIMixin):
114
114
  meta = self.meta
115
115
  manager = meta.manager
116
116
  if issubclass(meta.model, AIOModel):
117
- await resource.save()
117
+ await resource.save(force_insert=not update)
118
118
  else:
119
- await manager.save(resource)
119
+ await manager.save(resource, force_insert=not update)
120
120
 
121
121
  return resource
122
122
 
@@ -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
@@ -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)
@@ -1,8 +1,8 @@
1
1
  [tool.poetry]
2
2
  name = "muffin-rest"
3
- version = "11.0.0"
3
+ version = "11.0.3"
4
4
  description = "The package provides enhanced support for writing REST APIs with Muffin framework"
5
- readme = "README.rst"
5
+ readme = "README.md"
6
6
  homepage = "https://github.com/klen/muffin-rest"
7
7
  repository = "https://github.com/klen/muffin-rest"
8
8
  authors = ["Kirill Klenov <horneds@gmail.com>"]
@@ -14,7 +14,6 @@ classifiers = [
14
14
  "License :: OSI Approved :: MIT License",
15
15
  "Programming Language :: Python",
16
16
  "Programming Language :: Python :: 3",
17
- "Programming Language :: Python :: 3.9",
18
17
  "Programming Language :: Python :: 3.10",
19
18
  "Programming Language :: Python :: 3.11",
20
19
  "Programming Language :: Python :: 3.12",
@@ -25,10 +24,10 @@ classifiers = [
25
24
  ]
26
25
 
27
26
  [tool.poetry.dependencies]
28
- python = "^3.9"
27
+ python = "^3.10"
29
28
  apispec = "^6"
30
29
  marshmallow = "^3"
31
- muffin = "^0"
30
+ muffin = "*"
32
31
 
33
32
  # Optional dependencies
34
33
  pyyaml = { version = "*", optional = true }
@@ -78,7 +77,7 @@ ignore_missing_imports = true
78
77
  [tool.tox]
79
78
  legacy_tox_ini = """
80
79
  [tox]
81
- envlist = py310,py311,py312,py313,pypy39
80
+ envlist = py310,py311,py312,py313,pypy310
82
81
 
83
82
  [testenv]
84
83
  deps = -e .[tests]
@@ -101,9 +100,10 @@ ignore = [
101
100
  "D",
102
101
  "DJ",
103
102
  "EM",
104
- "FIX",
105
103
  "FA100",
104
+ "FIX",
106
105
  "N804",
106
+ "PLC0415",
107
107
  "PLR0912",
108
108
  "PLR2004",
109
109
  "RET",
@@ -1,182 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: muffin-rest
3
- Version: 11.0.0
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
-
@@ -1,143 +0,0 @@
1
- Muffin-REST
2
- ###########
3
-
4
- .. _description:
5
-
6
- **Muffin-REST** -- provides enhanced support for writing REST APIs with Muffin_.
7
-
8
-
9
- .. _badges:
10
-
11
- .. image:: https://github.com/klen/muffin-rest/workflows/tests/badge.svg
12
- :target: https://github.com/klen/muffin-rest/actions
13
- :alt: Tests Status
14
-
15
- .. image:: https://img.shields.io/pypi/v/muffin-rest
16
- :target: https://pypi.org/project/muffin-rest/
17
- :alt: PYPI Version
18
-
19
- .. image:: https://img.shields.io/pypi/pyversions/muffin-rest
20
- :target: https://pypi.org/project/muffin-rest/
21
- :alt: Python Versions
22
-
23
- ----------
24
-
25
- .. _features:
26
-
27
- Features
28
- --------
29
-
30
- - API class to simplify the creation of REST APIs;
31
- - Automatic filtering and sorting for resources;
32
- - Support for `Peewee ORM`_, Mongo_, `SQLAlchemy Core`_;
33
- - Auto documentation with Swagger_;
34
-
35
- .. _contents:
36
-
37
- .. contents::
38
-
39
- .. _requirements:
40
-
41
- Requirements
42
- =============
43
-
44
- - python >= 3.9
45
-
46
- .. note:: Trio is only supported with Peewee ORM
47
-
48
- .. _installation:
49
-
50
- Installation
51
- =============
52
-
53
- **Muffin-REST** should be installed using pip: ::
54
-
55
- pip install muffin-rest
56
-
57
- With `SQLAlchemy Core`_ support: ::
58
-
59
- pip install muffin-rest[sqlalchemy]
60
-
61
- With `Peewee ORM`_ support: ::
62
-
63
- pip install muffin-rest[peewee]
64
-
65
- With YAML support for autodocumentation: ::
66
-
67
- pip install muffin-rest[yaml]
68
-
69
- .. _usage:
70
-
71
- Usage
72
- =====
73
-
74
- Create an API:
75
-
76
- .. code-block:: python
77
-
78
- from muffin_rest import API
79
-
80
- api = API()
81
-
82
- Create endpoints and connect them to the API (example for sqlalchemy):
83
-
84
- .. code-block:: python
85
-
86
- from muffin_rest.sqlalchemy import SAEndpoint
87
- from project.api import api
88
-
89
- @api.route
90
- class MyEndpoint(SAEndpoint):
91
-
92
- class Meta:
93
- table = MyTable
94
- database = db
95
-
96
- Connect it to your Muffin_ application:
97
-
98
- .. code-block:: python
99
-
100
- from project.api import api
101
-
102
- api.setup(app, prefix='/api/v1')
103
-
104
-
105
- .. _bugtracker:
106
-
107
- Bug tracker
108
- ===========
109
-
110
- If you have any suggestions, bug reports or
111
- annoyances please report them to the issue tracker
112
- at https://github.com/klen/muffin-rest/issues
113
-
114
- .. _contributing:
115
-
116
- Contributing
117
- ============
118
-
119
- Development of Muffin-REST happens at: https://github.com/klen/muffin-rest
120
-
121
-
122
- Contributors
123
- =============
124
-
125
- * klen_ (Kirill Klenov)
126
-
127
- .. _license:
128
-
129
- License
130
- ========
131
-
132
- Licensed under a `MIT license`_.
133
-
134
- .. _links:
135
-
136
- .. _klen: https://github.com/klen
137
- .. _Muffin: https://github.com/klen/muffin
138
- .. _Swagger: https://swagger.io/tools/swagger-ui/
139
- .. _Mongo: https://www.mongodb.com/
140
- .. _Peewee ORM: http://docs.peewee-orm.com/en/latest/
141
- .. _SqlAlchemy Core: https://docs.sqlalchemy.org/en/14/core/
142
-
143
- .. _MIT license: http://opensource.org/licenses/MIT
File without changes
@@ -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