Skip to content

Commit 050458c

Browse files
authored
Merge pull request #3 from lazy-labs/0.0.15
0.0.15
2 parents 238f6c3 + 49a0b3d commit 050458c

File tree

6 files changed

+100
-18
lines changed

6 files changed

+100
-18
lines changed

README.md

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Object-oriented rest framework based on starlette, marshmallow and apispec.
77
* [Starlette] 0.12.0+
88
* [Marshmallow] 3.0.0rc8+
99
* [APISpec] 2.0.2+
10+
* [python-multipart] 0.0.5+
1011

1112
## Installation
1213

@@ -17,35 +18,53 @@ $ pip install star_resty
1718
## Example
1819

1920
```python
20-
from dataclasses import dataclass
21-
from typing import Optional
22-
23-
from marshmallow import Schema, fields, post_load, ValidationError
21+
from marshmallow import Schema, fields, ValidationError, post_load
2422
from starlette.applications import Starlette
23+
from starlette.datastructures import UploadFile
2524
from starlette.responses import JSONResponse
2625

27-
from star_resty import Method, Operation, endpoint, json_schema, query, setup_spec
26+
from dataclasses import dataclass
2827

28+
from star_resty import Method, Operation, endpoint, json_schema, json_payload, form_payload, query, setup_spec
29+
from typing import Optional
2930

3031
class EchoInput(Schema):
3132
a = fields.Int()
3233

3334

35+
# Json Payload (by schema)
36+
class JsonPayloadSchema(Schema):
37+
a = fields.Int(required=True)
38+
s = fields.String()
39+
40+
41+
# Json Payload (by dataclass)
3442
@dataclass
3543
class Payload:
3644
a: int
3745
s: Optional[str] = None
3846

39-
40-
class PayloadSchema(Schema):
41-
a = fields.Int(required=True)
42-
s = fields.String()
47+
class JsonPayloadDataclass(Schema):
48+
a=fields.Int(required=True)
49+
s=fields.Str()
4350

4451
@post_load
4552
def create_payload(self, data, **kwargs):
4653
return Payload(**data)
4754

4855

56+
# Form Payload
57+
class FormFile(fields.Field):
58+
def _validate(self, value):
59+
if not isinstance(value, UploadFile):
60+
raise ValidationError('Not a file')
61+
62+
63+
class FormPayload(Schema):
64+
id = fields.Int(required=True)
65+
file = FormFile()
66+
67+
4968
app = Starlette(debug=True)
5069

5170
@app.exception_handler(ValidationError)
@@ -64,13 +83,32 @@ class Echo(Method):
6483
return query_params
6584

6685

67-
@app.route('/post', methods=['POST'])
86+
@app.route('/post/schema', methods=['POST'])
87+
@endpoint
88+
class PostSchema(Method):
89+
meta = Operation(tag='default', description='post json (by schema)')
90+
91+
async def execute(self, item: json_payload(JsonPayloadSchema)):
92+
return {'a': item.get('a') * 2, 's': item.get('s')}
93+
94+
95+
@app.route('/post/dataclass', methods=['POST'])
96+
@endpoint
97+
class PostDataclass(Method):
98+
meta = Operation(tag='default', description='post json (by dataclass)')
99+
100+
async def execute(self, item: json_schema(JsonPayloadDataclass, Payload)):
101+
return {'a': item.a * 3, 's': item.s}
102+
103+
@app.route('/form', methods=['POST'])
68104
@endpoint
69-
class Post(Method):
70-
meta = Operation(tag='default', description='post')
105+
class PostForm(Method):
106+
meta = Operation(tag='default', description='post form')
71107

72-
async def execute(self, item: json_schema(PayloadSchema, Payload)):
73-
return {'a': item.a * 2, 's': item.s}
108+
async def execute(self, form_data: form_payload(FormPayload)):
109+
file_name = form_data.get('file').filename
110+
id = form_data.get('id')
111+
return {'message': f"file {file_name} with id {id} received"}
74112

75113

76114
if __name__ == '__main__':

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ typing_extensions
33
marshmallow>=3.0.0rc8,<4
44
starlette<1
55
apispec<4
6+
python-multipart
67

78
# Testing
89
pytest

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ def get_packages(package):
2828
'marshmallow>=3.0.0rc8,<4',
2929
'starlette<1',
3030
'apispec<4',
31+
'python-multipart'
3132
],
32-
version='0.0.14',
33+
version='0.0.15',
3334
url='https://github.com/slv0/start_resty',
3435
license='BSD',
3536
description='The web framework',

star_resty/method/parser.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,11 @@ def create_parser_from_data(data: Mapping) -> RequestParser:
6767
parsers = []
6868
async_parsers = []
6969
for key, value in data.items():
70-
if is_dataclass(value):
70+
parser = getattr(value, 'parser', None)
71+
if parser is None and is_dataclass(value):
7172
data = {field.name: field.type for field in fields(value)}
7273
factory = partial(DataClassParser, value)
7374
parser = create_parser_for_dc(data, factory=factory)
74-
else:
75-
parser = getattr(value, 'parser', None)
7675

7776
if parser is None or not isinstance(parser, (Parser, RequestParser)):
7877
continue

star_resty/payload/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
from .json import json_payload, json_schema
33
from .path import path, path_schema
44
from .query import query, query_schema
5+
from .form import form_payload, form_schema

star_resty/payload/form.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import types
2+
from typing import Mapping, Type, TypeVar, Union
3+
4+
from marshmallow import EXCLUDE, Schema
5+
from starlette.requests import Request
6+
7+
from star_resty.exceptions import DecodeError
8+
from .parser import Parser, set_parser
9+
10+
__all__ = ('form_schema', 'form_payload', 'FormParser')
11+
12+
P = TypeVar('P')
13+
14+
15+
def form_schema(schema: Union[Schema, Type[Schema]], cls: P,
16+
unknown: str = EXCLUDE) -> P:
17+
return types.new_class('FormDataInputParams', (cls,),
18+
exec_body=set_parser(FormParser.create(schema, unknown=unknown)))
19+
20+
21+
def form_payload(schema: Union[Schema, Type[Schema]], unknown=EXCLUDE) -> Type[Mapping]:
22+
return form_schema(schema, Mapping, unknown=unknown)
23+
24+
25+
class FormParser(Parser):
26+
__slots__ = ()
27+
28+
@property
29+
def location(self):
30+
return 'body'
31+
32+
@property
33+
def media_type(self):
34+
return 'multipart/form-data'
35+
36+
async def parse(self, request: Request):
37+
try:
38+
form_data = await request.form()
39+
form_data = {} if not form_data else form_data
40+
except Exception as e:
41+
raise DecodeError('Invalid form data: %s' % (str(e))) from e
42+
return self.schema.load(form_data, unknown=self.unknown)

0 commit comments

Comments
 (0)