Created
June 4, 2021 23:22
-
-
Save tabebqena/efb5b7d25c7ba3e3924e84fec9d11143 to your computer and use it in GitHub Desktop.
improvement of flask class based view
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from views2 import View2 | |
| from flask import Blueprint, Flask, jsonify, session, abort | |
| import typing as t | |
| app = Flask(__name__) | |
| app.secret_key = "extremely secret" | |
| def auth_required(fn): | |
| def wrap(obj, *args, **kwargs): | |
| logged_in = session.get("is_authenticated", False) | |
| print("logged in: ", logged_in) | |
| if logged_in: | |
| return fn(obj, *args, **kwargs) | |
| abort(403) | |
| return wrap | |
| class Greeting(View2): | |
| def __init__(self, owner: t.Union[Flask, Blueprint]) -> None: | |
| super().__init__(owner, base_url="/greeting") | |
| @View2.route( | |
| rule="/w/<name>", | |
| endpoint="welcome_user", | |
| ) | |
| def welcome(self, name): | |
| return f"welcome {name}" | |
| @View2.route() | |
| def goodBye(self): | |
| return "Good Bye!" | |
| @View2.route( | |
| rule="/s", | |
| ) | |
| @auth_required | |
| def secret_route(self): | |
| return "You are logged in, So you can see Secret info, " | |
| class Auth(View2): | |
| def __init__(self, owner: t.Union[Flask, Blueprint]) -> None: | |
| super().__init__(owner, base_url="/auth") | |
| @View2.route( | |
| rule="/login", | |
| ) | |
| def login(self): | |
| session["is_authenticated"] = True | |
| return "logged in" | |
| @View2.route( | |
| rule="/logout", | |
| ) | |
| @auth_required | |
| def logout(self): | |
| session["is_authenticated"] = False | |
| return "logged out" | |
| @View2.route( | |
| rule="/state", | |
| ) | |
| def is_authenticated(self): | |
| print("logged in?") | |
| logged = session.get("is_authenticated", False) | |
| print(logged) | |
| return jsonify({"auth": logged}) | |
| greeting = Greeting(app) | |
| auth = Auth(app) | |
| greeting.register() | |
| auth.register() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from flask import Blueprint, Flask | |
| import typing as t | |
| class _Route(object): | |
| """ | |
| Route class: A decorator class that is responsible for storing the methods decorated by `View2.route()` | |
| and the route parameter for each one. | |
| Don't decorate your methods by this class directly. | |
| """ | |
| def __init__( | |
| self, | |
| function: t.Callable, | |
| route_parameters: t.Dict[str, t.Any], | |
| *args, | |
| **kwargs | |
| ): | |
| """ | |
| __init__ [recieve and store the route parameters.] | |
| :param function: The decorated function | |
| :type function: [typing.Callable] | |
| :param route_parameters: [The route parameter] | |
| :type route_parameters: [dict] | |
| """ | |
| self.function: t.Callable = function | |
| self.args = args | |
| self.kwargs = kwargs | |
| self.route_parameters: t.Dict[str, t.Any] = route_parameters | |
| def __call__(self, obj, *args, **kwargs): | |
| """ | |
| __call__ [call the decorated view function] | |
| [extended_summary] | |
| :param obj: [The instance object `self` of the decorated method] | |
| :type obj: [object] | |
| """ | |
| return self.function(obj, *args, **kwargs) | |
| @classmethod | |
| def methods(cls, object) -> t.Dict[str, t.Callable]: | |
| """ | |
| methods return list of all methods decorated by _Route class in a given object | |
| [extended_summary] | |
| :param object: [ The object instance] | |
| :type object: [object] | |
| """ | |
| def g(): | |
| for name in dir(object): | |
| method = getattr(object, name) | |
| if isinstance(method, _Route): | |
| yield name, method | |
| views: t.Dict[str, t.Callable] = {} | |
| for name, method in g(): | |
| views[name] = method | |
| return views | |
| class View2: | |
| """Alternative way to use view functions. A subclass should | |
| decorate its view functions by `View2.route()` decorator. | |
| Note that you can include many view functions with similar logics in the same class. | |
| Note also That each view function will create fresh object for each request. | |
| So, view functions couldn't share the object name space. | |
| This is good to prevent state persistance between requests. | |
| but also it has performance drawbacks if the `__init__` code is heavy. | |
| After creating the instance object, just call `register()` | |
| method which is equal to calling `add_url_rule()` for each view dunctions. | |
| The view functions could be decorated with any number of deocrators. | |
| However, The `View2.route()` decorator should be the outermost one. | |
| """ | |
| #: The value of this properties `provide_automatic_options` and `methods` will be applied as default if the `route()` | |
| #: decorators doesn't specify another value. | |
| #: Setting this disables or force-enables the automatic options handling. | |
| provide_automatic_options: t.Optional[bool] = None | |
| #: A list of methods this view can handle. | |
| methods: t.Optional[t.List[str]] = None | |
| def __init__( | |
| self, | |
| owner: t.Union[Flask, Blueprint], | |
| base_url="/", | |
| *class_args, | |
| **class_kwargs | |
| ) -> None: | |
| """ | |
| :param owner: Flask application or blueprint that will register the view functions | |
| :type owner: t.Union[Flask, Blueprint] | |
| :param base_url: If specified it will prepend each view function rule, defaults to "/" | |
| :type base_url: str, optional | |
| """ | |
| self.owner = owner | |
| self.base_url = base_url | |
| self.class_args = class_args | |
| self.class_kwargs = class_kwargs | |
| def __get_rule__( | |
| self, method_name: str, route_parameters: t.Dict[str, t.Any] | |
| ) -> str: | |
| #: build rule. If `route_parameters.rule` is specified, it will be used | |
| #: If not, the method name wil be used instead. | |
| #: If `self.base_url` is specified, It will prepend the rule. i.e:. | |
| #: The rule will be equal to: `/self.base_url/route_parameters.get("rule")` | |
| #: or `/self.base_url/method_name` | |
| rule = None | |
| if route_parameters.get("rule"): | |
| rule = "/".join( | |
| s.strip("/") | |
| for s in [ | |
| "/", | |
| self.base_url, | |
| route_parameters.get("rule"), | |
| ] | |
| ) | |
| else: | |
| rule = "/".join(s.strip("/") for s in ["/", self.base_url, method_name]) | |
| return rule | |
| # return view_func(instance, *args, **kwargs) | |
| def register(self): | |
| """ | |
| register register all view methods decorated by `route()` decorator | |
| This method collects all methods that are decorated by the `route()` decorator, | |
| extracts the route parameters for each, create a view function on fly and | |
| call `add_url_rule()` for each function. | |
| :return: [None] | |
| :rtype: [None] | |
| """ | |
| #: dict key is the method name | |
| #: value: is the method itself, it is subclass of `_Route`. | |
| # So it has `route_paramters` property | |
| clses: t.Dict[str, t.Callable] = _Route.methods(self) | |
| for method_name, route_cls in clses.items(): | |
| route_parameters = route_cls.route_parameters | |
| rule = self.__get_rule__(method_name, route_parameters) | |
| #: onfly view function | |
| #: Note: We should hard code the method name | |
| #: Note: we should get fresh object of View2 subclass | |
| def view(method_name=method_name, *args, **kwargs): | |
| """ | |
| view view function. | |
| This view function consumes the decorated view function from the | |
| `View2` instances. For each request, it creates new object instance | |
| and call the method specified by `method_name`. | |
| """ | |
| #: Always get Fresh object for each request | |
| instance = type(self)(self.owner, *self.class_args, **self.class_kwargs) | |
| view_func = getattr(instance, method_name) | |
| return view_func(instance, *args, **kwargs) | |
| view.__name__ = method_name | |
| view.__doc__ = self.__doc__ | |
| view.__module__ = self.__module__ | |
| view.methods = route_parameters.get("methods", self.methods) | |
| provide_automatic_options = route_parameters.get( | |
| "provide_automatic_options", | |
| self.provide_automatic_options, | |
| ) | |
| endpoint = route_parameters.get("endpoint") or None | |
| options = route_parameters.get("options") or {} | |
| self.owner.add_url_rule( | |
| rule, | |
| endpoint=endpoint, | |
| view_func=view, | |
| provide_automatic_options=provide_automatic_options, | |
| **options, | |
| ) | |
| @staticmethod | |
| def route( | |
| rule=None, | |
| methods=None, | |
| endpoint=None, | |
| provide_automatic_options=None, | |
| **options | |
| ): | |
| """ | |
| route route decorator specific for subclasses of `View2` class | |
| decorate all your view functions by this decorator. All the decorated | |
| methods will be consumed later by `register()`. All the parameters of | |
| this decorator will be passed to `app.add_url_rule()` | |
| or `blueprint.add_url_rule()`. | |
| :param rule: The rule parameter, if not specified, the method name | |
| will be used. If you specify `self.base_url`, It will be prepended to | |
| the `rule` parameter | |
| :type rule: [str] | |
| :param methods: [list of HTTP methods ex:. ["GET",]], defaults to None | |
| list of HTTP methods. | |
| :type methods: [type], optional | |
| :param endpoint: [The endpoint], defaults to None | |
| :type endpoint: [type], optional | |
| :param provide_automatic_options: [whether to provide options method or not. | |
| If not specified, The `self.provide_automatic_options` will be used], | |
| defaults to None | |
| :type provide_automatic_options: [type], optional | |
| :return: [description] | |
| :rtype: [type] | |
| """ | |
| route_arguments = { | |
| "rule": rule, | |
| "methods": methods, | |
| "endpoint": endpoint, | |
| "provide_automatic_options": provide_automatic_options, | |
| "options": options, | |
| } | |
| def wrapper(function, *args, **kwargs): | |
| return _Route(function, route_arguments, *args, **kwargs) | |
| return wrapper |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment