muffin-rest 9.3.0__py3-none-any.whl → 10.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- muffin_rest/filters.py +17 -4
- muffin_rest/handler.py +3 -11
- muffin_rest/limits.py +15 -11
- muffin_rest/mongo/filters.py +3 -2
- muffin_rest/options.py +1 -16
- muffin_rest/peewee/filters.py +6 -4
- muffin_rest/peewee/utils.py +2 -1
- muffin_rest/sqlalchemy/filters.py +6 -6
- muffin_rest/types.py +3 -0
- {muffin_rest-9.3.0.dist-info → muffin_rest-10.0.0.dist-info}/METADATA +3 -3
- {muffin_rest-9.3.0.dist-info → muffin_rest-10.0.0.dist-info}/RECORD +13 -13
- {muffin_rest-9.3.0.dist-info → muffin_rest-10.0.0.dist-info}/WHEEL +1 -1
- {muffin_rest-9.3.0.dist-info → muffin_rest-10.0.0.dist-info}/LICENSE +0 -0
muffin_rest/filters.py
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
"""Support API filters."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import operator
|
|
5
6
|
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterable, Mapping, Optional # py39
|
|
6
7
|
|
|
7
8
|
import marshmallow as ma
|
|
8
|
-
from asgi_tools._compat import json_loads
|
|
9
|
+
from asgi_tools._compat import json_loads
|
|
9
10
|
|
|
10
11
|
from .utils import Mutate, Mutator
|
|
11
12
|
|
|
12
13
|
if TYPE_CHECKING:
|
|
13
14
|
from muffin import Request
|
|
14
15
|
|
|
16
|
+
from muffin_rest.types import TFilterOps, TFilterValue
|
|
17
|
+
|
|
15
18
|
from .types import TVCollection
|
|
16
19
|
|
|
17
20
|
FILTERS_PARAM = "where"
|
|
@@ -87,7 +90,7 @@ class Filter(Mutate):
|
|
|
87
90
|
|
|
88
91
|
return ops, collection
|
|
89
92
|
|
|
90
|
-
async def filter(self, collection, *ops:
|
|
93
|
+
async def filter(self, collection, *ops: TFilterValue) -> Any:
|
|
91
94
|
"""Apply the filter to collection."""
|
|
92
95
|
|
|
93
96
|
def validator(obj):
|
|
@@ -95,12 +98,22 @@ class Filter(Mutate):
|
|
|
95
98
|
|
|
96
99
|
return [item for item in collection if validator(item)]
|
|
97
100
|
|
|
98
|
-
def
|
|
101
|
+
def get_simple_value(self, ops: TFilterOps) -> Any:
|
|
102
|
+
"""Get simple value from filter's data.
|
|
103
|
+
|
|
104
|
+
In case of simple filter, return the value.
|
|
105
|
+
"""
|
|
106
|
+
if not ops:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
return ops[0][1]
|
|
110
|
+
|
|
111
|
+
def parse(self, data: Mapping) -> TFilterOps:
|
|
99
112
|
"""Parse operator and value from filter's data."""
|
|
100
113
|
value = data.get(self.name, ma.missing)
|
|
101
114
|
return tuple(self._parse(value))
|
|
102
115
|
|
|
103
|
-
def _parse(self, value):
|
|
116
|
+
def _parse(self, value) -> Iterable[TFilterValue]:
|
|
104
117
|
deserialize = self.schema_field.deserialize
|
|
105
118
|
if isinstance(value, dict):
|
|
106
119
|
for op, val in value.items():
|
muffin_rest/handler.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Base class for API REST Handlers."""
|
|
2
|
+
|
|
2
3
|
import abc
|
|
3
4
|
import inspect
|
|
4
5
|
from typing import (
|
|
@@ -97,8 +98,6 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
|
|
|
97
98
|
self.auth = await self.authorize(request)
|
|
98
99
|
|
|
99
100
|
meta = self.meta
|
|
100
|
-
if meta.rate_limit:
|
|
101
|
-
await self.rate_limit(request)
|
|
102
101
|
|
|
103
102
|
self.collection = await self.prepare_collection(request)
|
|
104
103
|
resource = await self.prepare_resource(request)
|
|
@@ -142,11 +141,6 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
|
|
|
142
141
|
raise APIError.UNAUTHORIZED()
|
|
143
142
|
return auth
|
|
144
143
|
|
|
145
|
-
async def rate_limit(self, request: Request):
|
|
146
|
-
"""Default rate limit method. Proxy rate limit to self.api."""
|
|
147
|
-
if not await self.meta.rate_limiter.check(f"{self.auth}"):
|
|
148
|
-
raise APIError.TOO_MANY_REQUESTS()
|
|
149
|
-
|
|
150
144
|
# Prepare data
|
|
151
145
|
# ------------
|
|
152
146
|
@abc.abstractmethod
|
|
@@ -240,12 +234,10 @@ class RESTBase(Generic[TVResource], Handler, metaclass=RESTHandlerMeta):
|
|
|
240
234
|
@overload
|
|
241
235
|
async def dump( # type: ignore[misc]
|
|
242
236
|
self, request, data: TVData, *, many: Literal[True]
|
|
243
|
-
) -> list[TSchemaRes]:
|
|
244
|
-
...
|
|
237
|
+
) -> list[TSchemaRes]: ...
|
|
245
238
|
|
|
246
239
|
@overload
|
|
247
|
-
async def dump(self, request, data: TVData, *, many: bool = False) -> TSchemaRes:
|
|
248
|
-
...
|
|
240
|
+
async def dump(self, request, data: TVData, *, many: bool = False) -> TSchemaRes: ...
|
|
249
241
|
|
|
250
242
|
async def dump(
|
|
251
243
|
self,
|
muffin_rest/limits.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
from time import time
|
|
3
|
+
from typing import Any
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class RateLimiter(abc.ABC):
|
|
6
7
|
"""Rate limiter."""
|
|
7
8
|
|
|
8
|
-
def __init__(self, limit: int, period: int, **opts):
|
|
9
|
+
def __init__(self, limit: int, *, period: int = 60, **opts):
|
|
9
10
|
"""Initialize the rate limiter.
|
|
10
11
|
|
|
11
12
|
Args:
|
|
@@ -21,26 +22,29 @@ class RateLimiter(abc.ABC):
|
|
|
21
22
|
raise NotImplementedError
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
RATE_LIMITS = {}
|
|
25
|
-
|
|
26
|
-
|
|
27
25
|
class MemoryRateLimiter(RateLimiter):
|
|
28
26
|
"""Memory rate limiter. Do not use in production."""
|
|
29
27
|
|
|
28
|
+
def __init__(self, limit: int, **opts):
|
|
29
|
+
super().__init__(limit, **opts)
|
|
30
|
+
self.storage: dict[Any, tuple[float, int]] = {}
|
|
31
|
+
|
|
30
32
|
async def check(self, key: str) -> bool:
|
|
31
33
|
"""Check the request."""
|
|
32
34
|
now = time()
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
storage = self.storage
|
|
36
|
+
|
|
37
|
+
if key not in storage:
|
|
38
|
+
storage[key] = (now, 1)
|
|
35
39
|
return True
|
|
36
40
|
|
|
37
|
-
last, count =
|
|
41
|
+
last, count = storage[key]
|
|
38
42
|
if now - last > self.period:
|
|
39
|
-
|
|
43
|
+
storage[key] = (now, 1)
|
|
40
44
|
return True
|
|
41
45
|
|
|
42
46
|
if count < self.limit:
|
|
43
|
-
|
|
47
|
+
storage[key] = (last, count + 1)
|
|
44
48
|
return True
|
|
45
49
|
|
|
46
50
|
return False
|
|
@@ -51,7 +55,7 @@ class RedisRateLimiter(RateLimiter):
|
|
|
51
55
|
|
|
52
56
|
# TODO: Asyncio lock
|
|
53
57
|
|
|
54
|
-
def __init__(self, limit: int,
|
|
58
|
+
def __init__(self, limit: int, *, redis, **opts):
|
|
55
59
|
"""Initialize the rate limiter.
|
|
56
60
|
|
|
57
61
|
Args:
|
|
@@ -59,7 +63,7 @@ class RedisRateLimiter(RateLimiter):
|
|
|
59
63
|
period (int): The period of time in seconds.
|
|
60
64
|
redis (aioredis.Redis): The Redis connection.
|
|
61
65
|
"""
|
|
62
|
-
super().__init__(limit,
|
|
66
|
+
super().__init__(limit, **opts)
|
|
63
67
|
self.redis = redis
|
|
64
68
|
|
|
65
69
|
async def check(self, key: str) -> bool:
|
muffin_rest/mongo/filters.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Support filters for Mongo."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import ClassVar
|
|
4
4
|
|
|
5
5
|
from muffin_rest.filters import Filter, Filters
|
|
6
|
+
from muffin_rest.types import TFilterValue
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class MongoFilter(Filter):
|
|
@@ -21,7 +22,7 @@ class MongoFilter(Filter):
|
|
|
21
22
|
"$ends": lambda _, v: ("$regex", f"{ v }$"),
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
async def filter(self, collection, *ops:
|
|
25
|
+
async def filter(self, collection, *ops: TFilterValue):
|
|
25
26
|
"""Apply the filter."""
|
|
26
27
|
return collection.find({self.field: dict(op(self.name, v) for op, v in ops)})
|
|
27
28
|
|
muffin_rest/options.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"""REST Options."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import ClassVar
|
|
4
4
|
|
|
5
5
|
import marshmallow as ma
|
|
6
6
|
|
|
7
|
-
from muffin_rest.limits import MemoryRateLimiter, RateLimiter
|
|
8
|
-
|
|
9
7
|
from .filters import Filters
|
|
10
8
|
from .sorting import Sorting
|
|
11
9
|
|
|
@@ -53,14 +51,6 @@ class RESTOptions:
|
|
|
53
51
|
schema_meta: ClassVar[dict] = {}
|
|
54
52
|
schema_unknown: str = ma.EXCLUDE
|
|
55
53
|
|
|
56
|
-
# Rate Limiting
|
|
57
|
-
# -------------
|
|
58
|
-
|
|
59
|
-
rate_limit: int = 0
|
|
60
|
-
rate_limit_period: int = 60
|
|
61
|
-
rate_limit_cls: type[RateLimiter] = MemoryRateLimiter
|
|
62
|
-
rate_limit_cls_opts: ClassVar[dict[str, Any]] = {}
|
|
63
|
-
|
|
64
54
|
def __init__(self, cls):
|
|
65
55
|
"""Inherit meta options."""
|
|
66
56
|
for base in reversed(cls.mro()):
|
|
@@ -86,11 +76,6 @@ class RESTOptions:
|
|
|
86
76
|
if not self.limit_max:
|
|
87
77
|
self.limit_max = self.limit
|
|
88
78
|
|
|
89
|
-
if self.rate_limit:
|
|
90
|
-
self.rate_limiter = self.rate_limit_cls(
|
|
91
|
-
self.rate_limit, self.rate_limit_period, **self.rate_limit_cls_opts
|
|
92
|
-
)
|
|
93
|
-
|
|
94
79
|
def setup_schema_meta(self, _):
|
|
95
80
|
"""Generate meta for schemas."""
|
|
96
81
|
return type(
|
muffin_rest/peewee/filters.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Support filters for Peewee ORM."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import operator
|
|
5
6
|
from functools import reduce
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import TYPE_CHECKING, ClassVar, Union, cast
|
|
7
8
|
|
|
8
9
|
from peewee import ColumnBase, Field, ModelSelect
|
|
9
10
|
|
|
@@ -11,6 +12,9 @@ from muffin_rest.filters import Filter, Filters
|
|
|
11
12
|
|
|
12
13
|
from .utils import get_model_field_by_name
|
|
13
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from muffin_rest.types import TFilterValue
|
|
17
|
+
|
|
14
18
|
|
|
15
19
|
class PWFilter(Filter):
|
|
16
20
|
"""Support Peewee."""
|
|
@@ -33,9 +37,7 @@ class PWFilter(Filter):
|
|
|
33
37
|
|
|
34
38
|
list_ops = (*Filter.list_ops, "$between")
|
|
35
39
|
|
|
36
|
-
async def filter(
|
|
37
|
-
self, collection: ModelSelect, *ops: tuple[Callable, Any], **kwargs
|
|
38
|
-
) -> ModelSelect:
|
|
40
|
+
async def filter(self, collection: ModelSelect, *ops: TFilterValue) -> ModelSelect:
|
|
39
41
|
"""Apply the filters to Peewee QuerySet.."""
|
|
40
42
|
column = self.field
|
|
41
43
|
if isinstance(column, ColumnBase):
|
muffin_rest/peewee/utils.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Support filters for Peewee ORM."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
from typing import TYPE_CHECKING, Optional
|
|
@@ -8,7 +9,7 @@ if TYPE_CHECKING:
|
|
|
8
9
|
from peewee import Field
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
def get_model_field_by_name(handler, name: str, stacklevel=5) -> Optional[Field]:
|
|
12
|
+
def get_model_field_by_name(handler, name: str, *, stacklevel=5) -> Optional[Field]:
|
|
12
13
|
"""Get model field by name."""
|
|
13
14
|
fields = handler.meta.model._meta.fields
|
|
14
15
|
candidate = fields.get(name)
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import TYPE_CHECKING, Any,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Union, cast
|
|
6
6
|
|
|
7
7
|
from sqlalchemy import Column
|
|
8
8
|
|
|
9
9
|
from muffin_rest.filters import Filter, Filters
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
|
+
from muffin_rest.types import TFilterValue
|
|
13
|
+
|
|
12
14
|
from .types import TVCollection
|
|
13
15
|
|
|
14
16
|
|
|
@@ -29,17 +31,15 @@ class SAFilter(Filter):
|
|
|
29
31
|
|
|
30
32
|
list_ops = (*Filter.list_ops, "$between")
|
|
31
33
|
|
|
32
|
-
async def filter(
|
|
33
|
-
self, collection: TVCollection, *ops: tuple[Callable, Any], **kwargs
|
|
34
|
-
) -> TVCollection:
|
|
34
|
+
async def filter(self, collection: TVCollection, *ops: TFilterValue) -> TVCollection:
|
|
35
35
|
"""Apply the filters to SQLAlchemy Select."""
|
|
36
36
|
column = self.field
|
|
37
37
|
if ops and column is not None:
|
|
38
|
-
return self.query(collection, column, *ops
|
|
38
|
+
return self.query(collection, column, *ops)
|
|
39
39
|
|
|
40
40
|
return collection
|
|
41
41
|
|
|
42
|
-
def query(self, select: TVCollection, column: Column, *ops
|
|
42
|
+
def query(self, select: TVCollection, column: Column, *ops: TFilterValue) -> TVCollection:
|
|
43
43
|
"""Filter a select."""
|
|
44
44
|
return select.where(*[op(column, val) for op, val in ops])
|
|
45
45
|
|
muffin_rest/types.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: muffin-rest
|
|
3
|
-
Version:
|
|
3
|
+
Version: 10.0.0
|
|
4
4
|
Summary: The package provides enhanced support for writing REST APIs with Muffin framework
|
|
5
|
-
Home-page: https://github.com/klen/muffin-rest
|
|
6
5
|
License: MIT
|
|
7
6
|
Keywords: rest,api,muffin,asgi,asyncio,trio
|
|
8
7
|
Author: Kirill Klenov
|
|
@@ -33,6 +32,7 @@ Requires-Dist: muffin-databases ; extra == "sqlalchemy"
|
|
|
33
32
|
Requires-Dist: muffin-peewee-aio ; extra == "peewee"
|
|
34
33
|
Requires-Dist: pyyaml ; extra == "yaml"
|
|
35
34
|
Requires-Dist: sqlalchemy ; extra == "sqlalchemy"
|
|
35
|
+
Project-URL: Homepage, https://github.com/klen/muffin-rest
|
|
36
36
|
Project-URL: Repository, https://github.com/klen/muffin-rest
|
|
37
37
|
Description-Content-Type: text/x-rst
|
|
38
38
|
|
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
muffin_rest/__init__.py,sha256=NBZeOEJgQHtFFhVgd9d0fpApFRgU405sbm0cu1y1MOU,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=
|
|
5
|
-
muffin_rest/handler.py,sha256=
|
|
6
|
-
muffin_rest/limits.py,sha256=
|
|
4
|
+
muffin_rest/filters.py,sha256=hc-fhBODwrgJM_CvXNtTVH6jIOkjLog8En0iKKYXAGU,5947
|
|
5
|
+
muffin_rest/handler.py,sha256=p3YQZRF2XgoBrf3r2QNVECqjTilYk5ZXFQ53GiJpZAA,10450
|
|
6
|
+
muffin_rest/limits.py,sha256=Fnlu4Wj3B-BzpahLK-rDbd1GEd7CEQ3zxyOD0vee7GE,2007
|
|
7
7
|
muffin_rest/marshmallow.py,sha256=jWsZeMj3KslbGGgzEMo8-e5eeuGhmqFolL0mIEquylc,789
|
|
8
8
|
muffin_rest/mongo/__init__.py,sha256=SiYSbX6ySJl43fw9aGREIs8ZsS8Qk_ieizoPOj4DjJc,4656
|
|
9
|
-
muffin_rest/mongo/filters.py,sha256=
|
|
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
13
|
muffin_rest/mongo/utils.py,sha256=mNkLM-D6gqOA9YW2Qdw0DvE2N4LRmxLAiPMKH9WLttM,3958
|
|
14
14
|
muffin_rest/openapi.py,sha256=0QU7qrfBjGl0vl378SJC5boZZI2ogddl45fS9WL4Axw,8751
|
|
15
|
-
muffin_rest/options.py,sha256=
|
|
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=
|
|
17
|
+
muffin_rest/peewee/filters.py,sha256=p813eJqyTkAhmS3C1P8rWFWb9Tl33OtADjgLctqKnns,2475
|
|
18
18
|
muffin_rest/peewee/handler.py,sha256=Tk20ChTGkhzSF0K1-TFruPfXJyHfJE_fDKcQqGQeoAU,5297
|
|
19
19
|
muffin_rest/peewee/openapi.py,sha256=lDnLnoXi33p0YeFVwRgaVrndyrG2XL93RH-BzbxinOY,1105
|
|
20
20
|
muffin_rest/peewee/options.py,sha256=TimJtErC9e8B7BRiEkHiBZd71_bZbYr-FE2PIlQvfH0,1455
|
|
21
21
|
muffin_rest/peewee/schemas.py,sha256=w6jBziUp40mOOjkz_4RCXuY0x5ZDIe9Ob25k1FnZSfc,469
|
|
22
22
|
muffin_rest/peewee/sorting.py,sha256=aTLL2zYeNfkamfbGuKkIClOsJktdZZZlzZKafccWxyQ,1977
|
|
23
23
|
muffin_rest/peewee/types.py,sha256=cgCXhpGHkImKwudA1lulZHz5oJswHH168AiW5MhZRCM,155
|
|
24
|
-
muffin_rest/peewee/utils.py,sha256=
|
|
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
28
|
muffin_rest/sorting.py,sha256=7k7dxElnEGiwvdfDivzcoLnAOXqpQoK52H-ss79Gw0g,2813
|
|
29
29
|
muffin_rest/sqlalchemy/__init__.py,sha256=9MSvOXWP6665LiA5O1Icl2V05bpz6Ex5sUHB9YWKLvE,6393
|
|
30
|
-
muffin_rest/sqlalchemy/filters.py,sha256=
|
|
30
|
+
muffin_rest/sqlalchemy/filters.py,sha256=ejqjEZU0vqvnLwG0sX7QV3RjcXI0a2Ro_A34AAiGMEc,2489
|
|
31
31
|
muffin_rest/sqlalchemy/sorting.py,sha256=YlFKpIet4TUy7fJ2UBLC8b9lAOwY66QBpPDDApbyh8M,1643
|
|
32
32
|
muffin_rest/sqlalchemy/types.py,sha256=Exm-zAQCtPAwXvYcCTtPRqSa-wTEWRcH_v2YSsJkB6s,198
|
|
33
33
|
muffin_rest/swagger.html,sha256=2uGLu_KpkYf925KnDKHBJmV9pm6OHn5C3BWScESsUS8,1736
|
|
34
|
-
muffin_rest/types.py,sha256=
|
|
34
|
+
muffin_rest/types.py,sha256=m27-g6BI7qdSWGym4fWALBJa2ZpWR0_m0nlrDx7iTCo,566
|
|
35
35
|
muffin_rest/utils.py,sha256=c08E4HJ4SLYC-91GKPEbsyKTZ4sZbTN4qDqJbNg_HTE,2076
|
|
36
|
-
muffin_rest-
|
|
37
|
-
muffin_rest-
|
|
38
|
-
muffin_rest-
|
|
39
|
-
muffin_rest-
|
|
36
|
+
muffin_rest-10.0.0.dist-info/LICENSE,sha256=xHPkOZhjyKBMOwXpWn9IB_BVLjrrMxv2M9slKkHj2hM,1082
|
|
37
|
+
muffin_rest-10.0.0.dist-info/METADATA,sha256=zVOQR6PAyL4QD4P8S1HPS3g5CPMfCpR3S-EGKCnsBpg,4147
|
|
38
|
+
muffin_rest-10.0.0.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
39
|
+
muffin_rest-10.0.0.dist-info/RECORD,,
|
|
File without changes
|