a utility library for declaring relative imports in Python
pip install dotrelay
out-of-the-box here's how to use dotrelay
to import a module from an ancestor directory containing a .relay
file:
import dotrelay
with dotrelay.Radio(__file__): # π»
import some_relatively_external_module
importing relatively external modules is hard (in Python π)
don't believe? just check out this 10+ years of discussion on the internet:
so forget about importing modules from another galaxy:
.
βββ andromeda
β βββ ufos.py -- πΈπΈπΈ
βββ milky_way
βββ sol
βββ earth
βββ animals
β βββ __init__.py
β βββ birds.py
β βββ fish.py
βββ lands
β βββ __init__.py
β βββ deserts.py
βββ waters
βββ __init__.py
βββ oceans.py
in order to import ufos
into deserts
you'd need this bit of boilerplate:
# deserts.py
import sys
import os
# get directory path containing `andromeda` (relatively from this module's file path)
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__) ) ) ) ) )
sys.path.append(root_path) # extend module import context
from andromeda import ufos # import that thing we need
sys.path.remove(root_path) # cleanup
# finally the real work can begin
def lose_cattle_mysteriously(cattle):
ufos.abduct_cattle(cattle, mode='random')
commonly referred to as a "sys.path
hack", this is what we want to provide a better alternative for - it's fairly low level, fairly ugly, noisy and just plain makes the code smelly ππ½
so let's make this better - we have the technology!
for starters let's create a .relay
file in the directory containing andromeda
, the module we want to import into oceans
*NOTE: this
.relay
file must be in one ofoceans
ancestor directories to be discoverable
.
βββ .relay -- π‘
βββ andromeda
β βββ ufos.py -- πΈπΈπΈ
βββ milky_way
βββ sol
βββ earth
βββ animals
β βββ __init__.py
β βββ birds.py
β βββ fish.py
βββ lands
β βββ __init__.py
β βββ deserts.py
βββ waters
βββ __init__.py
βββ oceans.py
now in oceans
we can use a dotrelay.Radio
to discover the .relay
file above it and establish a kind of temporary bridge for us to import andromeda
and/or other modules in the relay directory
# deserts.py
import dotrelay
with dotrelay.Radio(__file__): # π»
from andromeda import ufos
def lose_cattle_mysteriously(cattle):
ufos.abduct_cattle(cattle, mode='psuedo-random') # yes it happened
now the boilerplate has been reduced to something fairly high level, fairly clean, short and sweet
fun example aside, lets see how this fits into real world scenarios
so here's a typical file structure for most python lib projects where there's the main module and some test modules
.
βββ pything
β βββ __init__.py
β βββ main.py
βββ tests
βββ units.py
in order to test pything
it needs to be imported into units
, and you end up with more of that "sys.path
hack" bloat:
# units.py
import sys
import os
root_path = os.path.dirname( os.path.dirname( path.abspath(__file__) ) ) # the directory that contains pything
sys.path.append(root_path)
import pything
sys.path.remove(root_path) # cleanup
import unittest
# ...
an awkward thing to have to include in every single test module
with dotrelay
we simply add the .relay
file:
.
βββ .relay -- π‘
βββ pything
β βββ __init__.py
β βββ main.py
βββ tests
βββ units.py
and the boilerplate is reduced to:
# tests/units.py
import dotrelay
with dotrelay.Radio(__file__): # π»
import pything
building off the previous example, say units
were to be moved deeper into the project file structure:
.
βββ .relay -- π‘
βββ pything
β βββ __init__.py
β βββ main.py
βββ tests
βββ basic
βββ units.py
with a "sys.path
hack" the code for getting the root_path
would need to be updated since again it's relative to the module's own file path
so really then, overtime, as a project matures, this hack becomes something that needs to be manage.
but that can all be avoided with dotrelay
. no changes need to be made as long as the .relay
file remains with one of units
ancestor directories
sometimes it's also useful just having the path of the relay directory
.
βββ .relay -- π‘
βββ pything
β βββ __init__.py
β βββ main.py
βββ fixtures
β βββ data.json -- π
βββ tests
βββ units.py
so to read fixtures/data.json
from units
:
# tests/units.py
import dotrelay
with dotrelay.Radio(__file__) as rad: # π»
ROOT_PATH = rad.relay_path
import os, json
DATA_PATH = os.path.join(ROOT_PATH, 'fixtures', 'data.json')
with open(DATA_PATH, 'r') as fp:
DATA = json.load(fp)
import unittest
# ...
echoing the point from the previous example, this feature is pretty usefule when you need to move the static files around in a project