Skip to content

Commit

Permalink
Refactor main code, separate code into modules (#55)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
marcuxyz authored Nov 18, 2023
1 parent adf7482 commit a4ef4ff
Show file tree
Hide file tree
Showing 14 changed files with 200 additions and 137 deletions.
80 changes: 2 additions & 78 deletions mvc_flask/__init__.py
Original file line number Diff line number Diff line change
@@ -1,79 +1,3 @@
from importlib import import_module
from .mvc_flask import FlaskMVC, Router

from flask import Flask
from flask.blueprints import Blueprint

from .router import Router
from .middlewares.html import HTMLMiddleware
from .middlewares.http_method_override import (
HTTPMethodOverrideMiddleware,
CustomRequest,
)


class FlaskMVC:
def __init__(self, app: Flask = None, path="app"):
if app is not None:
self.init_app(app, path)

def init_app(self, app: Flask = None, path="app"):
self.hook = Hook()
self.path = path

app.template_folder = "views"
app.request_class = CustomRequest
app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app)

# register blueprint
self.register_blueprint(app)

@app.context_processor
def inject_stage_and_region():
return dict(method=HTMLMiddleware().method)

def register_blueprint(self, app: Flask):
# load routes defined from users
import_module(f"{self.path}.routes")

for route in Router._method_route().items():
controller = route[0]
blueprint = Blueprint(controller, controller)

obj = import_module(f"{self.path}.controllers.{controller}_controller")
view_func = getattr(obj, f"{controller.title()}Controller")
instance_of_controller = view_func()
self.hook.register(instance_of_controller, blueprint)

for resource in route[1]:
blueprint.add_url_rule(
rule=resource.path,
endpoint=resource.action,
view_func=getattr(instance_of_controller, resource.action),
methods=resource.method,
)

app.register_blueprint(blueprint)


class Hook:
def register(self, instance_of_controller, blueprint):
accept_attributes = [
"before_request",
"after_request",
"teardown_request",
"after_app_request",
"before_app_request",
"teardown_app_request",
"before_app_first_request",
]

attrs = [
attr for attr in dir(instance_of_controller) if attr in accept_attributes
]

if attrs:
for attr in attrs:
values = getattr(instance_of_controller, attr)
for value in values:
hook_method = getattr(instance_of_controller, value)
getattr(blueprint, attr)(hook_method)
__all__ = ["FlaskMVC", "Router"]
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import markupsafe


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

def input_hidden_method(self, input_method):
"""
Determines the appropriate HTML string to return based on the given method string.
Args:
- string (str): The method string (e.g., 'put', 'delete').
Returns:
- Markup: A markupsafe.Markup object containing the appropriate HTML string.
This object is safe to render directly in templates.
"""
result = {
"put": self._put(),
"delete": self._delete(),
}[input_method.lower()]

return markupsafe.Markup(result)

def _input_html(self, input_method):
"""
Generates a hidden HTML input element.
Expand Down Expand Up @@ -42,21 +60,3 @@ def _delete(self):
- str: An HTML string for a hidden input element for the DELETE method.
"""
return self._input_html("delete")

def method(self, string):
"""
Determines the appropriate HTML string to return based on the given method string.
Args:
- string (str): The method string (e.g., 'put', 'delete').
Returns:
- Markup: A markupsafe.Markup object containing the appropriate HTML string.
This object is safe to render directly in templates.
"""
result = {
"put": self._put(),
"delete": self._delete(),
}[string.lower()]

return markupsafe.Markup(result)
38 changes: 38 additions & 0 deletions mvc_flask/middlewares/blueprint_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from flask import Flask
from importlib import import_module

from flask.blueprints import Blueprint

from .hook_middleware import HookMiddleware

from .http.router_middleware import RouterMiddleware as Router


class BlueprintMiddleware:
def __init__(self, app: Flask, path: str) -> None:
self.app = app
self.path = path

# load routes defined from users
import_module(f"{self.path}.routes")

def register(self):
for route in Router._method_route().items():
controller = route[0]
blueprint = Blueprint(controller, controller)

obj = import_module(f"{self.path}.controllers.{controller}_controller")
view_func = getattr(obj, f"{controller.title()}Controller")
instance_of_controller = view_func()

HookMiddleware().register(instance_of_controller, blueprint)

for resource in route[1]:
blueprint.add_url_rule(
rule=resource.path,
endpoint=resource.action,
view_func=getattr(instance_of_controller, resource.action),
methods=resource.method,
)

self.app.register_blueprint(blueprint)
24 changes: 24 additions & 0 deletions mvc_flask/middlewares/hook_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class HookMiddleware:
def register(self, controller_instance, blueprint_instance):
attrs = [
attr for attr in dir(controller_instance) if attr in self.accept_attributes
]

if attrs:
for attr in attrs:
values = getattr(controller_instance, attr)
for value in values:
hook_method = getattr(controller_instance, value)
getattr(blueprint_instance, attr)(hook_method)

@property
def accept_attributes(self):
return [
"before_request",
"after_request",
"teardown_request",
"after_app_request",
"before_app_request",
"teardown_app_request",
"before_app_first_request",
]
15 changes: 15 additions & 0 deletions mvc_flask/middlewares/http/custom_request_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from flask import Request


class CustomRequestMiddleware(Request):
@property
def form(self):
if "wsgi._post_form" in self.environ:
return self.environ["wsgi._post_form"]
return super().form

@property
def files(self):
if "wsgi._post_files" in self.environ:
return self.environ["wsgi._post_files"]
return super().files
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from flask import Request
from werkzeug.formparser import parse_form_data


class HTTPMethodOverrideMiddleware:
class MethodOverrideMiddleware:
allowed_methods = frozenset(
[
"GET",
Expand All @@ -12,7 +11,14 @@ class HTTPMethodOverrideMiddleware:
"PATCH",
]
)
bodyless_methods = frozenset(["GET", "HEAD", "OPTIONS", "DELETE"])
bodyless_methods = frozenset(
[
"GET",
"HEAD",
"OPTIONS",
"DELETE",
]
)

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

return self.app(environ, start_response)


class CustomRequest(Request):
@property
def form(self):
if "wsgi._post_form" in self.environ:
return self.environ["wsgi._post_form"]
return super().form

@property
def files(self):
if "wsgi._post_files" in self.environ:
return self.environ["wsgi._post_files"]
return super().files
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
"""Namespace module."""


class Namespace:
"""Namespace."""
class NamespaceMiddleware:
"""NamespaceMiddleware."""

def __init__(self, name: str, router):
self.name = name
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from collections import namedtuple

from .namespace import Namespace
from .namespace_middleware import NamespaceMiddleware

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


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

routes = {}

for route in Router.ROUTES:
for route in RouterMiddleware.ROUTES:
value = list(route.values())[0]
for key in route:
if key not in routes:
Expand All @@ -63,16 +63,16 @@ def _method_route():
@staticmethod
def namespace(name: str):
"""
Creates a namespace for routes.
Creates a namespace middleware for routes.
Args:
name (str): The name of the namespace.
Returns:
Namespace: An instance of Namespace associated with the given name.
NamespaceMiddleware: An instance of NamespaceMiddleware associated with the given name.
"""

return Namespace(name, Router)
return NamespaceMiddleware(name, RouterMiddleware)

@staticmethod
def get(path: str, resource: str):
Expand All @@ -85,7 +85,9 @@ def get(path: str, resource: str):
"""

controller, action = resource.split("#")
Router.ROUTES.append({controller: Model(["GET"], path, controller, action)})
RouterMiddleware.ROUTES.append(
{controller: Model(["GET"], path, controller, action)}
)

@staticmethod
def post(path: str, resource: str):
Expand All @@ -98,7 +100,9 @@ def post(path: str, resource: str):
"""

controller, action = resource.split("#")
Router.ROUTES.append({controller: Model(["POST"], path, controller, action)})
RouterMiddleware.ROUTES.append(
{controller: Model(["POST"], path, controller, action)}
)

@staticmethod
def put(path: str, resource: str):
Expand All @@ -111,7 +115,7 @@ def put(path: str, resource: str):
"""

controller, action = resource.split("#")
Router.ROUTES.append(
RouterMiddleware.ROUTES.append(
{controller: Model(["PUT", "PATCH"], path, controller, action)},
)

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

controller, action = resource.split("#")
Router.ROUTES.append({controller: Model(["DELETE"], path, controller, action)})
RouterMiddleware.ROUTES.append(
{controller: Model(["DELETE"], path, controller, action)}
)

@staticmethod
def all(resource: str, only=None, base_path=""):
Expand All @@ -149,7 +155,7 @@ def all(resource: str, only=None, base_path=""):
"delete",
]
actions = only.split() if isinstance(only, str) else only
Router._add_routes(resource, actions if actions else group, base_path)
RouterMiddleware._add_routes(resource, actions if actions else group, base_path)

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

if action in parameters:
getattr(Router, parameters[action])(path, f"{name}#{action}")
getattr(RouterMiddleware, parameters[action])(path, f"{name}#{action}")
continue

getattr(Router, groups[action])(path, f"{name}#{action}")
getattr(RouterMiddleware, groups[action])(path, f"{name}#{action}")
Loading

0 comments on commit a4ef4ff

Please sign in to comment.