Faking Python Imports
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
.
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.
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
b.py
c.py
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
:
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.