Table of Contents

Background

I’m currently working on a Flask project and I wanted to combine two packages (Flask-CDN and Flask-Static-Digest) that both modify Flask’s url_for function.

Problem

However, these two packages are not made to work together. Flask-CDN overrides the url_for function in the context of templates. Once that function is called, it applies its processing and then calls Flask’s underlying url_for function. Flask-Static-Digest works similarly, but instead defines a new function static_url_for. This function still ends up calling url_for.

How everything originally works.
How everything originally works.

What I wanted was to have Flask-Static-Digest call the url_for function of Flask-CDN so that I could combine the features of both.

How I want it to work.
How I want it to work.

I really wanted to avoid editing code of the dependencies if at all possible, so I tried to see if I could get Flask-Static-Digest to import Flask-CDN while making it think it was importing Flask.

Solution

The answer is yes, you can do this, by overwriting sys.modules. Here is an example:

a.py

1def func(val):
2    print("A")
3    return val + 5

b.py

1def func(val):
2    print("B")
3    return val + 10

c.py

1import a
2
3def func(val):
4    return a.func(val)

main.py

 1import sys
 2
 3import c
 4
 5VAL = 20
 6
 7print("The real value of c.func is: {}".format(c.func(VAL)))
 8
 9# ideally, you would have never imported this module
10sys.modules.pop("c")
11# create reference to real import so we don't lose it
12a_real_import = __import__("a")
13a_fake_import = __import__("b")
14
15# fake the import
16sys.modules["a"] = a_fake_import
17import c
18# set it back to the real value
19sys.modules["a"] = a_real_import
20
21print("The fake value of c.func is: {}".format(c.func(VAL)))

And after running main.py:

1$ > python main.py
2A
3The real value of c.func is: 25
4B
5The fake value of c.func is: 30

The dictionary sys.module contains references to every module you have imported.

1Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 23:03:10) [MSC v.1916 64 bit (AMD64)] on win32
2Type "help", "copyright", "credits" or "license" for more information.
3>>> import sys
4>>> import string
5>>> sys.modules["string"]
6<module 'string' from 'C:\\Python38\\lib\\string.py'>
7>>>

All you have to do is inject or overwrite your own values. For reference, here is what I’ve done in my Flask app:

 1# static digest
 2# we need this to call the CDN's url_for and not flask
 3# don't try this at home, kids
 4flask_cdn_import = __import__("flask_cdn")
 5flask_real_import = __import__("flask")
 6
 7# replace real flask import with fake flask cdn import
 8sys.modules["flask"] = flask_cdn_import
 9# import the flask digest module with the fake import
10from flask_static_digest import FlaskStaticDigest  # noqa
11
12# put the flask module back to what it was
13sys.modules["flask"] = flask_real_import
14# run the class init from flask digest
15static_digest = FlaskStaticDigest()

Conclusion

While this trick can be very useful, it can also very dangerous and easy to break stuff. Remember, with great power comes great responsibility.

References