-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path__init__.py
145 lines (112 loc) · 4.46 KB
/
__init__.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
"""Flask & Connexion base REST API implementation"""
import logging
import os
from functools import partial, wraps
import connexion
import connexion.utils
from connexion.resolver import Resolver
from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
from ...interface import Api, CoreEntryNotFound, CoreException, CoreInvalidRequest
LOGGER = logging.getLogger("api.rest")
OPERATION_ID_PREFIX = "conexample.api"
def _make_response(status, details, title, headers=None):
return (
{"details": details, "status": status, "title": title, "type": "about:blank"},
status,
headers,
)
def request_context(func):
"""Decorator handling common request errands"""
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except CoreException as ex:
LOGGER.error("Internal error: %s", ex)
raise InternalServerError(str(ex))
return wrapper
class RestApi(Api, connexion.FlaskApp):
"""API handler implemented w/ connexion framework."""
class entry: # pylint: disable=invalid-name
"""Container for conexample.api.entry.* routes"""
@staticmethod
@request_context
def get(handler):
"""Implements conexample.api.entry.get"""
return handler.core.get_entries()
@staticmethod
@request_context
def post(handler, body):
"""Implements conexample.api.entry.post"""
try:
created = handler.core.set_entry_rating(
name=body["name"], rating=body["rating"]
)
except CoreInvalidRequest as ex:
raise BadRequest(str(ex))
if created:
return _make_response(
201,
"Entry created",
"Created",
{"Location": "entry/%s" % body["name"]},
)
return _make_response(
200, "Entry updated", "OK", {"Location": "entry/%s" % body["name"]}
)
class element: # pylint: disable=invalid-name
"""Container for conexample.api.entry.element.* routes"""
@staticmethod
@request_context
def get(handler, name):
"""Implements conexample.api.entry.element.get"""
try:
return {"name": name, "rating": handler.core.get_rating(name)}
except CoreEntryNotFound:
raise NotFound("Entry not found")
@staticmethod
@request_context
def post(handler, name, body):
"""Implements conexample.api.entry.element.post"""
try:
created = handler.core.set_entry_rating(
name=name, rating=body["rating"]
)
except CoreInvalidRequest as ex:
raise BadRequest(str(ex))
if created:
return _make_response(
201, "Entry created", "Created", {"Location": "%s" % name}
)
return _make_response(
200, "Entry updated", "OK", {"Location": "%s" % name}
)
@staticmethod
@request_context
def delete(handler, name):
"""Implements conexample.api.entry.element.delete"""
try:
handler.core.delete_entry(name)
except CoreEntryNotFound:
raise NotFound("Entry not found")
return _make_response(200, "Entry deleted", "OK")
def __init__(self, core):
# pylint: disable=super-init-not-called
self.core = core
connexion.FlaskApp.__init__( # pylint: disable=non-parent-init-called
self, __name__
)
self.add_api(
os.path.join(os.path.dirname(__file__), "api.yml"),
resolver=Resolver(self.func_resolv),
strict_validation=True,
)
def func_resolv(self, function_name):
"""Resolves request handlers based on OpenAPI specified operation ids"""
if not function_name.startswith(OPERATION_ID_PREFIX):
raise ImportError()
subject = self
handler_path = function_name[len(OPERATION_ID_PREFIX) + 1 :].split(".")
for step in handler_path:
subject = getattr(subject, step)
return partial(subject, self)