-
Notifications
You must be signed in to change notification settings - Fork 0
/
routing.py
98 lines (78 loc) · 3.01 KB
/
routing.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
import flask
class Route:
def __init__(self, path="/", handler=None, formatters={}):
self.path = self.translate(path, formatters)
self.handler = handler if handler else lambda: None
# flask expects url params to be wrapped in angle brackets.
# we allow for string formatting syntax, and translate into something flask understands.
# furthermore, if there's an annotation, we add the flask-compatable type
# to the url
def translate(self, path, formatters):
type_map = {
int: "int",
float: "float",
str: "string",
#"path": "path",
#"any": "any",
#"uuid": "uuid"
}
for url_param in formatters:
formatter = formatters[url_param]
if formatter in type_map:
formatter = type_map[formatter]
_from = "{{{0}}}".format(url_param)
to = "<{0}:{1}>".format(formatter, url_param)
path = path.replace(_from, to)
return path
def __repr__(self):
return "{0} => {1}".format(self.path, self.handler)
class Namespace:
# what we call a namespace, other frameworks refer to as "apps" or "modules"
# a namespace is a self-contained collection of endpoints, bound to a url
# path
def __init__(self, site=None, attached="/"):
self.site = site
self.attachment_point = attached
self.routes = []
self.errors = []
def __enter__(self):
return self
def __exit__(self, *args):
self.site.register_namespace(self.attachment_point, self.routes)
def error(self, error_code, handler):
self.errors.append({"code": error_code, "handler": handler})
def route(self, route):
path = None
anno = route.__annotations__
if "return" in anno:
path = anno["return"]
del anno["return"]
# TODO: lists should be allowed in annotations so a controller can
# listen to multiple uris
self.routes.append(Route(path=path, handler=route, formatters=anno))
# allow for route chaining
return self
def namespace(self, base):
return Namespace(self, base)
def register_namespace(self, attachment_point, routes):
for route in routes:
new_path = "{0}{1}".format(attachment_point, route.path)
new_route = Route(
path=new_path,
handler=route.handler
)
self.routes.append(new_route)
class Root:
def __init__(self, app: flask.Flask):
self.app = app
self.root = None
def __enter__(self):
self.root = Namespace()
return self.root
def __exit__(self, *_):
for route in self.root.routes:
# flask uses decorators for the routing.
# so let flask's routing know what would-have-been decorated.
self.app.route(route.path)(route.handler)
for err in self.root.errors:
self.app.errorhandler(err["code"])(err["handler"])