Skip to content

Commit 1afb4da

Browse files
committed
core blueprint refactor
1 parent ac48fef commit 1afb4da

File tree

6 files changed

+193
-169
lines changed

6 files changed

+193
-169
lines changed

app/core/blueprint.py

Lines changed: 0 additions & 167 deletions
This file was deleted.

app/core/blueprint/__init__.py

Whitespace-only changes.

app/core/blueprint/core.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import pkgutil
2+
import inspect
3+
from app.core.blueprint.package_extractor import PackageExtractor
4+
5+
"""
6+
Description:: Initialize the blueprints inside in the root folder
7+
and sub folder
8+
9+
Requirements:: all directories and sub directories must consist of __init__.py
10+
to be considered as a package.
11+
12+
files are ignored if its not end with .py or __.py
13+
14+
NOTE :: directories must not consist of __ in their name
15+
16+
"""
17+
18+
19+
class Core:
20+
__app = None
21+
22+
def __init__(self, app, root_path):
23+
24+
""" save sanic app module """
25+
self.__app = app
26+
self.root_path = root_path
27+
28+
""" register blueprint to the current path """
29+
PackageExtractor(application=app, path=root_path)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import inspect
2+
3+
4+
class ModuleRouter:
5+
def __init__(self, mod): # module
6+
self._module = mod
7+
if self._is_valid_module():
8+
self.model_add_router()
9+
10+
def model_add_router(self):
11+
if hasattr(self._module, '__routes__') and len(self._module.__routes__):
12+
route_type, route_data = self._routing_type(route=self._module.__routes__.pop(0))
13+
if route_type == 'cls':
14+
""" If it's a class it needs to extract the methods by function names
15+
magic functions are excluded
16+
"""
17+
route_name, slug, cls = route_data
18+
self.class_member_route(route=route_data, members=self.get_cls_fn_members(cls))
19+
20+
elif route_type == 'fn':
21+
route_name, slug, fn, methods = route_data
22+
23+
self._module.__method__.add_url_rule(
24+
rule=slug,
25+
endpoint=fn.__name__,
26+
view_func=fn,
27+
methods=methods)
28+
self.model_add_router()
29+
30+
def _is_valid_module(self):
31+
return hasattr(self._module, '__routes__') or hasattr(self._module, '__method__')
32+
33+
@staticmethod
34+
def _routing_type(route):
35+
__type = None
36+
if isinstance(route, tuple):
37+
if len(route) == 3 and inspect.isclass(route[2]):
38+
__type = 'cls'
39+
elif len(route) == 4 and inspect.isfunction(route[2]):
40+
if isinstance(route[3], (list, tuple, set)):
41+
__type = 'fn'
42+
else:
43+
raise TypeError("methods must be a list.")
44+
else:
45+
raise TypeError("Invalid route syntax.")
46+
return __type, route
47+
48+
@staticmethod
49+
def get_http_methods(names):
50+
if isinstance(names, list):
51+
methods = []
52+
53+
for name in names:
54+
if "__" not in name:
55+
if name == "index":
56+
methods.append('GET')
57+
elif name == "create":
58+
methods.append('POST')
59+
elif name == "update":
60+
methods.append('PUT')
61+
elif name == "destroy":
62+
methods.append('DELETE')
63+
else:
64+
methods.append('GET')
65+
66+
return methods
67+
else:
68+
raise TypeError("names must be a list")
69+
70+
@staticmethod
71+
def get_cls_fn_members(cls):
72+
return [member for member in inspect.getmembers(cls, predicate=inspect.isfunction)]
73+
74+
def class_member_route(self, route, members):
75+
if isinstance(members, (list, set)):
76+
if len(members):
77+
(fn_name, fn_object) = members.pop(0)
78+
route_name, slug, cls = route
79+
if inspect.isfunction(fn_object):
80+
self._module.__method__.add_url_rule(
81+
rule=slug,
82+
endpoint=fn_name,
83+
view_func=fn_object,
84+
methods=self.get_http_methods([fn_name]))
85+
else:
86+
raise KeyError("Member is not a function.")
87+
self.class_member_route(route, members)
88+
else:
89+
raise TypeError("members must be a list.")
90+
91+
def register_route(self, app, name):
92+
app.register_blueprint(self._module.__method__, url_prefix=self.blueprint_name(name))
93+
94+
@staticmethod
95+
def blueprint_name(name):
96+
97+
root_module = name.replace(".", "")
98+
99+
name = str(name).replace(root_module, "")
100+
""" set index automatically as home page """
101+
if "index" in name:
102+
name = str(name).replace("index", "")
103+
if "routes" in name:
104+
name = str(name).replace("routes", "")
105+
if "module" in name:
106+
name = str(name).replace("module", "")
107+
108+
""" remove the last . in the string it it ends with a .
109+
for the url structure must follow the flask routing format
110+
it should be /model/method instead of /model/method/
111+
"""
112+
if name[-1:] == ".":
113+
name = name[:-1]
114+
http_name = str(name).replace(".", "/")
115+
116+
return http_name
117+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import inspect
2+
import pkgutil
3+
from app.core.blueprint.module_router import ModuleRouter
4+
5+
6+
class PackageExtractor:
7+
8+
__packages = None
9+
10+
def __init__(self, application, path):
11+
self.path = path
12+
self.application = application
13+
_packages = self.__inspect_packages(packages=pkgutil.walk_packages(path, prefix="", onerror=None))
14+
self.extract_packages(packages=_packages)
15+
16+
@staticmethod
17+
def __inspect_packages(packages):
18+
if inspect.isgenerator(packages):
19+
packages = [package for package in packages]
20+
21+
if isinstance(packages, (list, set)):
22+
if len(packages):
23+
return packages
24+
else:
25+
raise ValueError("Package does not have an item.")
26+
27+
def extract_packages(self, packages):
28+
if len(packages):
29+
loader, name, is_pkg = packages.pop(0)
30+
""" if module found load module and save all attributes in the module found """
31+
mod = loader.find_module(name).load_module(name)
32+
33+
""" find the attribute method on each module """
34+
if hasattr(mod, '__method__'):
35+
module_router = ModuleRouter(mod)
36+
37+
""" register to the blueprint if method attribute found """
38+
module_router.register_route(app=self.application, name=name)
39+
40+
else:
41+
""" prompt not found notification """
42+
# print('{} has no module attribute method'.format(mod))
43+
pass
44+
self.extract_packages(packages)
45+

app/core/bootstrap.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from flask import Flask
44

55
from app.config.app import DevelopmentConfig as Config
6-
from app.core.blueprint import CoreBlueprint
6+
from app.core.blueprint.core import Core
77

88

99
class Bootstrap:
@@ -32,5 +32,5 @@ def configuration(self, conf):
3232

3333
def start(self):
3434
""" for blueprint registration """
35-
CoreBlueprint(app=self.__app, root_path='.module')
35+
Core(app=self.__app, root_path='.module')
3636
self.__app.run(host=Config.HOST, port=Config.PORT)

0 commit comments

Comments
 (0)