importloc¶
Import Python objects from arbitrary locations specified by string.
Features¶
Minimalistic fully typed package
Import from files or named modules
Import deeply nested objects
Import all instances or all subclasses
Configurable module name conflict resolution
Atomicity: on import error, new module is removed, and previous, if any, is restored
Installation¶
$ pip install importloc
$ uv add importloc
Overview¶
Arbitrary importable location. |
|
Package-based importable location, e.g. |
|
Filesystem-based importable location, e.g. |
Get object members that are instances of specified type. |
|
Get object members that are subclasses of specified class (excluding the class itself). |
|
Get nested attribute value. |
|
Generate random module name based on UUID4. |
Usage¶
Various locations
Various targets
Custom module name
What if module is already imported?
What if object does not exist?
Quick start¶
The main and most used entity is Location.
from importloc import Location
Various locations¶
Import from file¶
Location('app/config.py:conf').load()
>>> loc = Location('app/config.py:conf')
>>> loc
<PathLocation 'app/config.py' obj='conf'>
>>> loc.load()
<config.Config object at 0x...>
Import from module¶
Location('app.__main__:cli').load()
>>> loc = Location('app.__main__:cli')
>>> loc
<ModuleLocation 'app.__main__' obj='cli'>
>>> loc.load()
<function cli at 0x...>
Distinguish file and module locations¶
Location('./config.py:conf').load()
>>> loc = Location('config.py:conf')
>>> loc
<ModuleLocation 'config.py' obj='conf'>
>>> loc.load()
Traceback (most recent call last):
...
ModuleNotFoundError: No module named 'config.py'...
Use relative path (similar to Docker bind mount). Path separator will result in
PathLocation instead of ModuleLocation.
>>> loc = Location('./config.py:conf')
>>> loc
<PathLocation 'config.py' obj='conf'>
>>> loc.load()
<config.Config object at 0x...>
Various targets¶
Import nested class¶
Location('app/config.py:Config.Nested').load()
>>> loc = Location('app/config.py:Config.Nested')
>>> loc
<PathLocation 'app/config.py' obj='Config.Nested'>
>>> loc.load()
<class 'config.Config.Nested'>
Import module as a whole¶
Location('app/config.py').load()
>>> loc = Location('app/config.py')
>>> loc
<PathLocation 'app/config.py'>
>>> loc.load()
<module 'config' from '...'>
Use Path object when loading module¶
Location(Path('config.py')).load()
>>> from pathlib import Path
>>> loc = Location(Path('config.py'))
>>> loc
<PathLocation 'config.py'>
>>> loc.load()
<module 'config' from '...'>
Import all instances of some type¶
get_instances(Location('app.__main__').load(), Callable)
>>> from collections.abc import Callable
>>> from importloc import get_instances
>>> loc = Location('app.__main__')
>>> loc
<ModuleLocation 'app.__main__'>
>>> get_instances(loc.load(), Callable)
[<function cli at 0x...>]
Import all subclasses¶
get_subclasses(Location('app.errors').load(), Exception)
>>> from importloc import get_subclasses
>>> loc = Location('app.errors')
>>> loc
<ModuleLocation 'app.errors'>
>>> get_subclasses(loc.load(), Exception)
[<class 'app.errors.Error1'>, <class 'app.errors.Error2'>]
Custom module name¶
Use different module name¶
Location('...').load(modname='app_main')
>>> Location('app/config.py:Config').load(modname='app_main')
<class 'app_main.Config'>
Generate module name at run time¶
Location('...').load(modname=random_name)
>>> from importloc import random_name
>>> Location('app/config.py:Config').load(modname=random_name)
<class 'u....Config'>
What if module is already imported?¶
The module name conflict can be resolved with one the methods:
reuseexisting module imported beforereloadexisting modulereplaceexisting modulerenamenew module (try to import under new name)raiseexception (default)
For details, see documentation on ConflictResolution.
Module name conflict raises error by default¶
Location('...').load()
>>> Location('app/config.py:Config').load()
<class 'config.Config'>
>>> Location('app/config.py:Config').load()
Traceback (most recent call last):
...
importloc.exc.ModuleNameConflict: Module "config" is already imported
Reuse module that is already imported¶
Location('...').load(on_conflict='reuse')
>>> C = Location('app/config.py:Config').load()
>>> C
<class 'config.Config'>
>>> old_id = id(C)
>>> C = Location('app/config.py:Config').load(on_conflict='reuse')
>>> C
<class 'config.Config'>
>>> # C is the same object:
>>> id(C) == old_id
True
Reload module that is already imported¶
Location('...').load(on_conflict='reload')
>>> import sys
>>> C = Location('app/config.py:Config').load()
>>> C
<class 'config.Config'>
>>> old_id = id(C)
>>> mod_id = id(sys.modules['config'])
>>> C = Location('app/config.py:Config').load(on_conflict='reload')
>>> C
<class 'config.Config'>
>>> # module object remains the same after reloading:
>>> id(sys.modules['config']) == mod_id
True
>>> # C is the new object from reloaded module:
>>> id(C) == old_id
False
Replace old module with imported one¶
Location('...').load(on_conflict='replace')
>>> import sys
>>> C = Location('app/config.py:Config').load()
>>> C
<class 'config.Config'>
>>> mod_id = id(sys.modules['config'])
>>> C = Location('app/config.py:Config').load(on_conflict='replace')
>>> C
<class 'config.Config'>
>>> # module object is the new one:
>>> id(sys.modules['config']) == mod_id
False
Load module under different generated name¶
Location('...').load(on_conflict='rename', rename=random_name)
>>> from importloc import random_name
>>> Location('app/config.py').load()
<module 'config' from ...>
>>> Location('app/config.py').load(on_conflict='rename', rename=random_name)
<module 'u...'>
Combine override and rename¶
Location('...').load(modname='...', on_conflict='rename', rename=random_name)
>>> from importloc import random_name
>>> Location('app/config.py').load(modname='app_config')
<module 'app_config' from ...>
>>> Location('app/config.py').load(
... modname='app_config', on_conflict='rename', rename=random_name
... )
<module 'u...' from ...>
What if object does not exist?¶
Missing object causes AttributeError¶
When module was imported but requested object does not exist, AttributeError
is raised.
>>> Location('app/config.py:unknown').load()
Traceback (most recent call last):
...
AttributeError: object has no attribute 'unknown'
>>> # due to import atomicity, module 'config' was removed
>>> import sys
>>> 'config' in sys.modules
False