Monkey Patching - the way to change objects behaviour in runtime. The
way that you can use to customize some third-party logic specially for
your purposes.
This is a pretty small article with an illustration case where Monkey
Patching can be useful. One day I thought about how to avoid sending
common used variables to Jinja2 templates in each route. And in Django,
Flask and aiohttp-jinja2 and many others frameworks/libs for this exists
context_processor.
Let’s imagine - you have a Web App with Sanic and Server Side Rendering
Templates with Jinja2. For example, you have a header with Menu, some
logo, title - any information that is used on each page of your app. You
want to define it in one place.
In my case I need this in the process of work on the Gino-Admin panel.
And I use Sanic & Sanic-Jinja2.
I have something like this on each route:
return jinja.render(
"login.html", request, objects=cfg.models, url_prefix=cfg.URL_PREFIX,
)
return jinja.render(
"db_drop.html",
request,
data=await count_elements_in_db(),
objects=cfg.models,
url_prefix=cfg.URL_PREFIX,
)
return jinja.render(
"presets.html",
request,
presets_folder=cfg.presets_folder,
presets=utils.get_presets()['presets'],
objects=cfg.models,
url_prefix=cfg.URL_PREFIX,
)
And etc.
As you see each time I send url_prefix=cfg.URL_PREFIX
, prefix that
used as a base route for admin panel and objects=cfg.models
- list of
models that we render in Admin panel for add/edit/delete.
I don’t like to duplicate code, also I want to add more customisation,
like set custom title for admin panel in config and other things in the
future. And sending all new common variables inside each route will be
painful. So I need a way to provide these variables only once.
Sanic-Jinja2 also has context_processor arg but I tried a lot of things
to send in it, and nothing works for me. I failed. Maybe I have the
wrong hands & head.
But I want this feature, so go on.
SanicJinja2.render
To render all templates, the Sanic-JInja2 package uses the main method
render(...)
If you never used Sanic-Jinja2 to add it to Sanic app looks like:
from sanic_jinja2 import SanicJinja2
jinja = SanicJinja2()
And that's it, after in routes you use it like:
return jinja.render(
"presets.html",
request,
presets_folder=cfg.presets_folder,
presets=utils.get_presets()['presets'],
objects=cfg.models,
url_prefix=cfg.URL_PREFIX,
)
Okay. Time for monkey patching.
Main idea
go inside the source code, check that part code you need re-write
-
and patch with your own implementation that solves your pain.
What I need? I need to patch each response before page render with
common context variables that are used in each template.
If we jump in SanicJinja2 source code we will see, that
https://github.com/lixxu/sanic-jinja2/blob/master/sanic_jinja2/__init__.py#L143
I need to change small method:
def render(self, template, request, status=200, headers=None, **context):
return html(
self.render_string(template, request, **context),
status=status,
headers=headers,
)
So I will define my own method. Let’s call it ‘custom_render’. I will
just copy the paste base method - render and add into context all
variables that I need.
def custom_render(
self, template, request, status=200, headers=None, **context
):
context["admin_panel_title"] = cfg.name
context["objects"] = cfg.models
context["url_prefix"] = cfg.route
context["admin_panel_version"] = __version__
context["round_number"] = cfg.round_number
return html(
self.render_string(template, request, **context),
status=status,
headers=headers,
)
And now we need to ‘patch’ our target class SanicJinja2:
SanicJinja2.render = custom_render
So all the next code that will use this class after the line with
patch
will call our custom method logic as the render
method of
SanicJinja2 class.
Beautiful freedom of Python. You have a lot of ways to solve your
issues.
Code samples in project repo:
https://github.com/xnuinside/gino-admin/blob/master/gino_admin/config.py#L20
Top comments (0)