Auto-Reloadable Modules and Namespaces
Project description
relmod
- auto-reloading module development library
Place your Python code in a directory and start using it immediately.
- Import file names as auto-deep-reloading modules.
- Import directories as auto-reloading namespaces.
- Run
unittest
cases easily.
Running the following:
import relmod
with open('./myfunc.py', 'w') as f: # create a file with a function
f.write("""
def add(x, y):
return x + y
""")
myfunc = relmod.at('./myfunc.py') # load file as a module
print(myfunc.add(3, 4)) # call the function
import unittest
class TestMyFunc(unittest.TestCase): # create a test
def test_add(self):
self.assertEqual(
myfunc.add(3, 4), 7)
relmod.runtest(TestMyFunc) # run the test
produces this output:
7
test_add (__main__.TestMyFunc) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.003s
OK
Motivation
The relmod
library allows for placing helper modules and functions
in a directory and making them quickly available, with reloading if needed.
This helps with converting existing notebook cells into re-usable
library code.
Tests for these library functions can be developed easily along the way.
When you're finished, you no longer need relmod
. You have a readily usable
Python library. Packaging is up to you.
Examples
Use the current working directory as a namespace module:
lib = relmod.at('.')
Entering folders that are not valid Python identifiers is supported:
py = lib['./Documents and Settings'].sub.folders
Relative directories can be given:
parent = lib['../'] # go up a directory, using []
Importing
Import an object from a module into the global namespace:
relmod.imp('./myfunc.py', 'add')
Rename references in the import using as
relmod.imp('./myfunc.py', 'add as add2')
print(add2(3, 4))
Names can be comma-separated, e.g. 'add, sub, mult, div'
.
Import a filename as another name:
relmod.imp('./myfunc.py as mfunc')
mfunc.add(1, 2)
Note: Non-module objects imported using relmod.imp
are not automatically
reloaded if changes occur to the file. You will need to reimport them.
Cell Mode
The .install
function will use the current working directory
if __file__
is not defined. This is useful in a cell-mode
environment.
here = relmod.install(globals())
Using .install
allows for relative imports within __main__
:
from . import myfunc
print(myfunc.add(3, 4))
Use the parent directory of __file__
as a namespace:
here = relmod.up(__file__)
Top-level Modules
You can register a directory or file as a top-level module and then import it.
relmod.toplevel('myfunc', './myfunc.py')
import myfunc
myfunc.add(3, 4)
Testing
Run a single test case method:
relmod.runtest(TestMyFunc, 'test_add')
Find and run all unittest.TestCase
classes in a module:
relmod.testmod(mod)
Only run a single class in a test file and exit:
@relmod.testonly()
class Test(unittest.TestCase):
...
@relmod.testfocus # optionally focus only on this test
def test_thing(self):
...
How it works
The .imp
, .at
, .up
, .use
, and .install
functions return FakeModuleType
objects wrapped in a ModuleProxy
object that trigger reloading when
accessing its attributes, if needed. Namespace and __init__.py
fake
modules perform auto-reloading on attribute access as well.
The files and directories accessed via relmod
are not found in
sys.modules
. These "fake modules" are handled separately and
behave as regular Python modules, with enhancements. Relative
imports within a fake module perform dependency tracking,
allowing for lazy deep-reloading of modules.
The auto-reloading of a module's source will not hot-patch existing
objects like the %autoreload
magic from IPython. Hot-patching makes
certain assumptions about your code, and if violated, will introduce
subtle bugs.
Relative Path Resolution
The relmod.at
and relmod.up
functions use os.getcwd()
when resolving
relative paths.
The relmod.use
and relmod.imp
functions use __file__
from the calling
frame's globals dictionary, and uses os.getcwd()
as a fallback if __file__
is not defined.
It is recommended to use .use
and .imp
in library scripts where relative
paths must resolve relative to the script's file path rather than the current
working directory.
Here is a comparison of different ways to resolve the path "."
:
lib = relmod.at('.') # resolved using os.getcwd()
lib = relmod.use('.') # resolved using __file__
relmod.imp('. as lib') # resolved using __file__,
# injects `lib` into caller's global namespace
Other Utilities
There are other utilities in relmod
that are useful for quick development.
API | Description |
---|---|
relmod.execfile() |
Executes a file's contents in a provided namespace |
relmod.auto |
Auto-imports toplevel modules on attribute access |
relmod.site |
Predefined site module names, see fakesite.py |
Install
pip3 install relmod
Zen
-
Beautiful is better than ugly.
relmod
is a useful alternative toimportlib.reload
andsys.path
hacking.
-
Explicit is better than implicit.
- If you want a file, request it.
-
Namespaces are one honking great idea -- let's do more of those!
relmod
turns the filesystem into a namespace
-
There should be one-- and preferably only one --obvious way to do it.
relmod
is the way ;-)
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.