forked from plangrid/flask-rebar
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtodo.py
179 lines (132 loc) · 5.31 KB
/
todo.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
from flask import Flask
from marshmallow import fields, pre_dump, pre_load
from flask_rebar import (
Rebar,
errors,
HeaderApiKeyAuthenticator,
Tag,
SwaggerV2Generator,
)
from flask_rebar.validation import RequestSchema, ResponseSchema
rebar = Rebar()
# Rebar will create a default swagger generator if none is specified.
# However, if you want more control over how the swagger is generated, you can
# provide your own.
# Here we've specified additional metadata for operation tags.
generator = SwaggerV2Generator(
tags=[Tag(name="todo", description="Operations for managing TODO items.")]
)
registry = rebar.create_handler_registry(swagger_generator=generator)
# Just a mock database, for demonstration purposes
todo_id_sequence = 0
todo_database = {}
# Rebar relies heavily on Marshmallow.
# These schemas will be used to validate incoming data, marshal outgoing
# data, and to automatically generate a Swagger specification.
class CreateTodoSchema(RequestSchema):
complete = fields.Boolean(required=True)
description = fields.String(required=True)
class UpdateTodoSchema(CreateTodoSchema):
# This schema provides an example of one way to re-use another schema while making some fields optional
# a "partial" schema in Marshmallow parlance:
def __init__(self, **kwargs):
super_kwargs = dict(kwargs)
partial_arg = super_kwargs.pop("partial", True)
# Note: if you only want to mark some fields as partial, pass partial= a collection of field names, e.g.,:
# partial_arg = super_kwargs.pop('partial', ('description', ))
super(UpdateTodoSchema, self).__init__(partial=partial_arg, **super_kwargs)
class GetTodoListSchema(RequestSchema):
complete = fields.Boolean()
class TodoSchema(ResponseSchema):
id = fields.Integer(required=True)
complete = fields.Boolean(required=True)
description = fields.String(required=True)
class TodoResourceSchema(ResponseSchema):
data = fields.Nested(TodoSchema)
@pre_dump
@pre_load
def envelope_in_data(self, data, **kwargs):
if type(data) is not dict or "data" not in data.keys():
return {"data": data}
else:
return data
class TodoListSchema(ResponseSchema):
data = fields.Nested(TodoSchema, many=True)
@pre_dump
@pre_load
def envelope_in_data(self, data, **kwargs):
if type(data) is not dict or "data" not in data.keys():
return {"data": data}
else:
return data
@registry.handles(
rule="/todos",
method="POST",
request_body_schema=CreateTodoSchema(),
tags=["todo"],
# This dictionary tells framer which schema to use for which response code.
# This is a little ugly, but tremendously helpful for generating swagger.
response_body_schema={
201: TodoResourceSchema()
}, # for versions <= 1.7.0, use marshal_schema
)
def create_todo():
global todo_id_sequence, todo_database
# The body is eagerly validated with the `request_body_schema` provided in
# the decorator. The resulting parameters are now available here:
todo = rebar.validated_body
todo_id_sequence += 1
todo["id"] = todo_id_sequence
todo_database[todo_id_sequence] = todo
# The return value may be an object to encoded as JSON or a tuple where
# the first item is the value to be encoded as JSON and the second is
# the HTTP response code. In the case where no response code is included,
# 200 is assumed.
return todo, 201
@registry.handles(
rule="/todos",
method="GET",
query_string_schema=GetTodoListSchema(),
tags=["todo"],
# If the value for this is not a dictionary, the response code is assumed
# to be 200
response_body_schema=TodoListSchema(), # for versions <= 1.7.0, use marshal_schema
)
def get_todos():
global todo_database
# Just like validated_body, query string parameters are eagerly validated
# and made available here. Flask-toolbox does treats a request body and
# query string parameters as two separate sources, and currently does not
# implement any abstraction on top of them.
args = rebar.validated_args
todos = todo_database.values()
if "complete" in args:
todos = [t for t in todos if ["complete"] == args["complete"]]
return todos
@registry.handles(
rule="/todos/<int:todo_id>",
method="PATCH",
response_body_schema=TodoResourceSchema(), # for versions <= 1.7.0, use marshal_schema
request_body_schema=UpdateTodoSchema(),
tags=["todo"],
)
def update_todo(todo_id):
global todo_database
if todo_id not in todo_database:
raise errors.NotFound()
params = rebar.validated_body
todo_database[todo_id].update(params)
todo = todo_database[todo_id]
return todo
def create_app(name):
app = Flask(name)
authenticator = HeaderApiKeyAuthenticator(header="X-MyApp-Key")
# The HeaderApiKeyAuthenticator does super simple authentication, designed for
# service-to-service authentication inside of a protected network, by looking for a
# shared secret in the specified header. Here we define what that shared secret is.
authenticator.register_key(key="my-api-key")
registry.set_default_authenticator(authenticator=authenticator)
rebar.init_app(app=app)
return app
if __name__ == "__main__":
create_app(__name__).run()