Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor main code, separate code into modules #55

Merged
merged 11 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading