Skip to content

Instantly share code, notes, and snippets.

@tabebqena
Created June 4, 2021 23:22
Show Gist options
  • Select an option

  • Save tabebqena/efb5b7d25c7ba3e3924e84fec9d11143 to your computer and use it in GitHub Desktop.

Select an option

Save tabebqena/efb5b7d25c7ba3e3924e84fec9d11143 to your computer and use it in GitHub Desktop.
improvement of flask class based view
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()
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