Skip to content

Commit a4ef4ff

Browse files
authored
Refactor main code, separate code into modules (#55)
* feat(middlewares): add CustomRequestMiddleware to handle custom form and files properties in Flask requests test(callbacks_controller_test): add test for creating a message in callbacks controller * fix(__init__.py): fix import statement for HookMiddleware class feat(hook_middleware.py): add HookMiddleware class to register hooks for controller methods in blueprints The import statement for the `HookMiddleware` class in `__init__.py` was incorrect. It was fixed to import from the correct module. A new file `hook_middleware.py` was added to the `middlewares` directory. This file contains the `HookMiddleware` class which is responsible for registering hooks for controller methods in blueprints. The `register` method takes a controller instance and a blueprint instance as arguments, and registers the appropriate hooks based on the attributes of the controller instance. The `accept_attributes` property defines the list of attributes that are accepted as hooks. * feat(mvc_flask): add InputMethodHelper class to handle HTML-related operations The InputMethodHelper class is added to the mvc_flask/helpers/html module. This class provides methods for generating HTML input elements with specific methods (like PUT and DELETE) that are not natively supported by HTML forms. The class includes the following methods: - input_hidden_method: Determines the appropriate HTML string to return based on the given method string. - _input_html: Generates a hidden HTML input element. - _put: Generates a hidden input field for the PUT method. - _delete: Generates a hidden input field for the DELETE method. This class is intended to be used in the FlaskMVC class in the mvc_flask/__init__.py file. The inject_stage_and_region method in the FlaskMVC class now uses the InputMethodHelper class to generate the appropriate HTML for the method attribute in the returned dictionary. * refactor(__init__.py): rename MethodOverrideMiddleware class to MethodOverrideMiddleware for consistency and clarity refactor(hook_middleware.py): rename HookMidleware class to HookMiddleware for consistency and clarity feat(__init__.py): add import statements for MethodOverrideMiddleware and CustomRequestMiddleware feat(__init__.py): remove unused Hook class and its related code feat(__init__.py): update app.request_class to use CustomRequestMiddleware instead of CustomRequest feat(__init__.py): update app.wsgi_app to use MethodOverrideMiddleware instead of HTTPMethodOverrideMiddleware * feat(callbacks_controller.py): add CallbacksController class with index method and before_request callback feat(routes.py): add route for callbacks with only index method test(routes_test.py): add tests for the newly added callbacks route and controller * fix(messages_controller.py): change query method from `get` to `filter_by` to handle cases where the message with the given id does not exist * fix(hook_middleware.py): format the list comprehension for better readability fix(custom_request_middleware.py): remove extra blank line fix(callbacks_controller.py): change single quotes to double quotes for consistency fix(routes_test.py): format the assert statement for better readability * fix(mvc_flask): fix typo in method_override_middleware filename feat(mvc_flask): add method_override_middleware to handle HTTP method override functionality The typo in the filename of the method_override_middleware module has been fixed. The correct filename is now method_override_middleware.py. A new file, method_override_middleware.py, has been added to the mvc_flask/middlewares/http directory. This file contains the implementation of the MethodOverrideMiddleware class, which is responsible for handling HTTP method override functionality. The middleware allows clients to override the HTTP method of a request by including a special "_method" parameter in the request body. The allowed methods for override are GET, POST, DELETE, PUT, and PATCH. The middleware also handles cases where the overridden method is a bodyless method (GET, HEAD, OPTIONS, DELETE) by setting the appropriate values in the WSGI environment. * fix(__init__.py): update import statement for RouterMiddleware to reflect new file structure feat(router_middleware.py): add RouterMiddleware class to manage routes in a web application feat(namespace_middleware.py): add NamespaceMiddleware class to create namespaces for routes * refactor(__init__.py): remove unused imports and commented out code feat(blueprint_middleware.py): add BlueprintMiddleware class to handle registering blueprints and routes dynamically * refactor(mvc_flask): reorganize code structure and improve readability - Move FlaskMVC class to a separate file `mvc_flask.py` for better organization - Remove unnecessary imports and unused code from `__init__.py` - Rename `init_app` method in `FlaskMVC` class to `perform` for better clarity - Extract configuration logic into separate methods in `FlaskMVC` class for better modularity and readability - Update method names in `FlaskMVC` class to better reflect their purpose - Update variable names in `FlaskMVC` class for better clarity - Update comments and docstrings in `FlaskMVC` class for better understanding
1 parent adf7482 commit a4ef4ff

14 files changed

+200
-137
lines changed

mvc_flask/__init__.py

Lines changed: 2 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,3 @@
1-
from importlib import import_module
1+
from .mvc_flask import FlaskMVC, Router
22

3-
from flask import Flask
4-
from flask.blueprints import Blueprint
5-
6-
from .router import Router
7-
from .middlewares.html import HTMLMiddleware
8-
from .middlewares.http_method_override import (
9-
HTTPMethodOverrideMiddleware,
10-
CustomRequest,
11-
)
12-
13-
14-
class FlaskMVC:
15-
def __init__(self, app: Flask = None, path="app"):
16-
if app is not None:
17-
self.init_app(app, path)
18-
19-
def init_app(self, app: Flask = None, path="app"):
20-
self.hook = Hook()
21-
self.path = path
22-
23-
app.template_folder = "views"
24-
app.request_class = CustomRequest
25-
app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app)
26-
27-
# register blueprint
28-
self.register_blueprint(app)
29-
30-
@app.context_processor
31-
def inject_stage_and_region():
32-
return dict(method=HTMLMiddleware().method)
33-
34-
def register_blueprint(self, app: Flask):
35-
# load routes defined from users
36-
import_module(f"{self.path}.routes")
37-
38-
for route in Router._method_route().items():
39-
controller = route[0]
40-
blueprint = Blueprint(controller, controller)
41-
42-
obj = import_module(f"{self.path}.controllers.{controller}_controller")
43-
view_func = getattr(obj, f"{controller.title()}Controller")
44-
instance_of_controller = view_func()
45-
self.hook.register(instance_of_controller, blueprint)
46-
47-
for resource in route[1]:
48-
blueprint.add_url_rule(
49-
rule=resource.path,
50-
endpoint=resource.action,
51-
view_func=getattr(instance_of_controller, resource.action),
52-
methods=resource.method,
53-
)
54-
55-
app.register_blueprint(blueprint)
56-
57-
58-
class Hook:
59-
def register(self, instance_of_controller, blueprint):
60-
accept_attributes = [
61-
"before_request",
62-
"after_request",
63-
"teardown_request",
64-
"after_app_request",
65-
"before_app_request",
66-
"teardown_app_request",
67-
"before_app_first_request",
68-
]
69-
70-
attrs = [
71-
attr for attr in dir(instance_of_controller) if attr in accept_attributes
72-
]
73-
74-
if attrs:
75-
for attr in attrs:
76-
values = getattr(instance_of_controller, attr)
77-
for value in values:
78-
hook_method = getattr(instance_of_controller, value)
79-
getattr(blueprint, attr)(hook_method)
3+
__all__ = ["FlaskMVC", "Router"]

mvc_flask/middlewares/html.py renamed to mvc_flask/helpers/html/input_method_helper.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import markupsafe
22

33

4-
class HTMLMiddleware:
4+
class InputMethodHelper:
55
"""
66
A middleware class for handling HTML-related operations, specifically for creating hidden input fields
77
with specific methods (like PUT and DELETE) that are not natively supported by HTML forms.
@@ -13,6 +13,24 @@ class HTMLMiddleware:
1313
- method: Public method to handle the generation of appropriate HTML based on a given string.
1414
"""
1515

16+
def input_hidden_method(self, input_method):
17+
"""
18+
Determines the appropriate HTML string to return based on the given method string.
19+
20+
Args:
21+
- string (str): The method string (e.g., 'put', 'delete').
22+
23+
Returns:
24+
- Markup: A markupsafe.Markup object containing the appropriate HTML string.
25+
This object is safe to render directly in templates.
26+
"""
27+
result = {
28+
"put": self._put(),
29+
"delete": self._delete(),
30+
}[input_method.lower()]
31+
32+
return markupsafe.Markup(result)
33+
1634
def _input_html(self, input_method):
1735
"""
1836
Generates a hidden HTML input element.
@@ -42,21 +60,3 @@ def _delete(self):
4260
- str: An HTML string for a hidden input element for the DELETE method.
4361
"""
4462
return self._input_html("delete")
45-
46-
def method(self, string):
47-
"""
48-
Determines the appropriate HTML string to return based on the given method string.
49-
50-
Args:
51-
- string (str): The method string (e.g., 'put', 'delete').
52-
53-
Returns:
54-
- Markup: A markupsafe.Markup object containing the appropriate HTML string.
55-
This object is safe to render directly in templates.
56-
"""
57-
result = {
58-
"put": self._put(),
59-
"delete": self._delete(),
60-
}[string.lower()]
61-
62-
return markupsafe.Markup(result)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from flask import Flask
2+
from importlib import import_module
3+
4+
from flask.blueprints import Blueprint
5+
6+
from .hook_middleware import HookMiddleware
7+
8+
from .http.router_middleware import RouterMiddleware as Router
9+
10+
11+
class BlueprintMiddleware:
12+
def __init__(self, app: Flask, path: str) -> None:
13+
self.app = app
14+
self.path = path
15+
16+
# load routes defined from users
17+
import_module(f"{self.path}.routes")
18+
19+
def register(self):
20+
for route in Router._method_route().items():
21+
controller = route[0]
22+
blueprint = Blueprint(controller, controller)
23+
24+
obj = import_module(f"{self.path}.controllers.{controller}_controller")
25+
view_func = getattr(obj, f"{controller.title()}Controller")
26+
instance_of_controller = view_func()
27+
28+
HookMiddleware().register(instance_of_controller, blueprint)
29+
30+
for resource in route[1]:
31+
blueprint.add_url_rule(
32+
rule=resource.path,
33+
endpoint=resource.action,
34+
view_func=getattr(instance_of_controller, resource.action),
35+
methods=resource.method,
36+
)
37+
38+
self.app.register_blueprint(blueprint)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class HookMiddleware:
2+
def register(self, controller_instance, blueprint_instance):
3+
attrs = [
4+
attr for attr in dir(controller_instance) if attr in self.accept_attributes
5+
]
6+
7+
if attrs:
8+
for attr in attrs:
9+
values = getattr(controller_instance, attr)
10+
for value in values:
11+
hook_method = getattr(controller_instance, value)
12+
getattr(blueprint_instance, attr)(hook_method)
13+
14+
@property
15+
def accept_attributes(self):
16+
return [
17+
"before_request",
18+
"after_request",
19+
"teardown_request",
20+
"after_app_request",
21+
"before_app_request",
22+
"teardown_app_request",
23+
"before_app_first_request",
24+
]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from flask import Request
2+
3+
4+
class CustomRequestMiddleware(Request):
5+
@property
6+
def form(self):
7+
if "wsgi._post_form" in self.environ:
8+
return self.environ["wsgi._post_form"]
9+
return super().form
10+
11+
@property
12+
def files(self):
13+
if "wsgi._post_files" in self.environ:
14+
return self.environ["wsgi._post_files"]
15+
return super().files
Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
from flask import Request
21
from werkzeug.formparser import parse_form_data
32

43

5-
class HTTPMethodOverrideMiddleware:
4+
class MethodOverrideMiddleware:
65
allowed_methods = frozenset(
76
[
87
"GET",
@@ -12,7 +11,14 @@ class HTTPMethodOverrideMiddleware:
1211
"PATCH",
1312
]
1413
)
15-
bodyless_methods = frozenset(["GET", "HEAD", "OPTIONS", "DELETE"])
14+
bodyless_methods = frozenset(
15+
[
16+
"GET",
17+
"HEAD",
18+
"OPTIONS",
19+
"DELETE",
20+
]
21+
)
1622

1723
def __init__(self, app, input_name="_method"):
1824
self.app = app
@@ -32,17 +38,3 @@ def __call__(self, environ, start_response):
3238
environ["CONTENT_LENGTH"] = "0"
3339

3440
return self.app(environ, start_response)
35-
36-
37-
class CustomRequest(Request):
38-
@property
39-
def form(self):
40-
if "wsgi._post_form" in self.environ:
41-
return self.environ["wsgi._post_form"]
42-
return super().form
43-
44-
@property
45-
def files(self):
46-
if "wsgi._post_files" in self.environ:
47-
return self.environ["wsgi._post_files"]
48-
return super().files

mvc_flask/namespace.py renamed to mvc_flask/middlewares/http/namespace_middleware.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
"""Namespace module."""
2-
3-
4-
class Namespace:
5-
"""Namespace."""
1+
class NamespaceMiddleware:
2+
"""NamespaceMiddleware."""
63

74
def __init__(self, name: str, router):
85
self.name = name

mvc_flask/router.py renamed to mvc_flask/middlewares/http/router_middleware.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from collections import namedtuple
22

3-
from .namespace import Namespace
3+
from .namespace_middleware import NamespaceMiddleware
44

55
Model = namedtuple("Model", "method path controller action")
66

77

8-
class Router:
8+
class RouterMiddleware:
99
"""
10-
Router class for managing routes in a web application.
10+
RouterMiddleware class for managing routes in a web application.
1111
1212
This class provides methods to define and manage different HTTP routes
1313
(GET, POST, PUT, DELETE) for the application's controllers and actions.
@@ -51,7 +51,7 @@ def _method_route():
5151

5252
routes = {}
5353

54-
for route in Router.ROUTES:
54+
for route in RouterMiddleware.ROUTES:
5555
value = list(route.values())[0]
5656
for key in route:
5757
if key not in routes:
@@ -63,16 +63,16 @@ def _method_route():
6363
@staticmethod
6464
def namespace(name: str):
6565
"""
66-
Creates a namespace for routes.
66+
Creates a namespace middleware for routes.
6767
6868
Args:
6969
name (str): The name of the namespace.
7070
7171
Returns:
72-
Namespace: An instance of Namespace associated with the given name.
72+
NamespaceMiddleware: An instance of NamespaceMiddleware associated with the given name.
7373
"""
7474

75-
return Namespace(name, Router)
75+
return NamespaceMiddleware(name, RouterMiddleware)
7676

7777
@staticmethod
7878
def get(path: str, resource: str):
@@ -85,7 +85,9 @@ def get(path: str, resource: str):
8585
"""
8686

8787
controller, action = resource.split("#")
88-
Router.ROUTES.append({controller: Model(["GET"], path, controller, action)})
88+
RouterMiddleware.ROUTES.append(
89+
{controller: Model(["GET"], path, controller, action)}
90+
)
8991

9092
@staticmethod
9193
def post(path: str, resource: str):
@@ -98,7 +100,9 @@ def post(path: str, resource: str):
98100
"""
99101

100102
controller, action = resource.split("#")
101-
Router.ROUTES.append({controller: Model(["POST"], path, controller, action)})
103+
RouterMiddleware.ROUTES.append(
104+
{controller: Model(["POST"], path, controller, action)}
105+
)
102106

103107
@staticmethod
104108
def put(path: str, resource: str):
@@ -111,7 +115,7 @@ def put(path: str, resource: str):
111115
"""
112116

113117
controller, action = resource.split("#")
114-
Router.ROUTES.append(
118+
RouterMiddleware.ROUTES.append(
115119
{controller: Model(["PUT", "PATCH"], path, controller, action)},
116120
)
117121

@@ -126,7 +130,9 @@ def delete(path: str, resource: str):
126130
"""
127131

128132
controller, action = resource.split("#")
129-
Router.ROUTES.append({controller: Model(["DELETE"], path, controller, action)})
133+
RouterMiddleware.ROUTES.append(
134+
{controller: Model(["DELETE"], path, controller, action)}
135+
)
130136

131137
@staticmethod
132138
def all(resource: str, only=None, base_path=""):
@@ -149,7 +155,7 @@ def all(resource: str, only=None, base_path=""):
149155
"delete",
150156
]
151157
actions = only.split() if isinstance(only, str) else only
152-
Router._add_routes(resource, actions if actions else group, base_path)
158+
RouterMiddleware._add_routes(resource, actions if actions else group, base_path)
153159

154160
@staticmethod
155161
def _add_routes(name, actions, base_path):
@@ -185,7 +191,7 @@ def _add_routes(name, actions, base_path):
185191
path = f"{base_path}/{name}{urls.get(action, '')}"
186192

187193
if action in parameters:
188-
getattr(Router, parameters[action])(path, f"{name}#{action}")
194+
getattr(RouterMiddleware, parameters[action])(path, f"{name}#{action}")
189195
continue
190196

191-
getattr(Router, groups[action])(path, f"{name}#{action}")
197+
getattr(RouterMiddleware, groups[action])(path, f"{name}#{action}")

0 commit comments

Comments
 (0)