Package: runtime-syspath
runtime-syspathis a package to ease programmatically adding src root
paths tosys.path. This is targeted at python test code that needs to
discover a project's solution source to test.:exclamation: It is generallyfrowned uponto alter thesys.pathprogrammatically as it confuses development, especially refactoring.
Python IDEs can statically determine if a dependent package's import
statement is left wanting whether a PyPi installation in needed or
source cannot be discovered through standard Python paths. A static
analysis tool'smissing importdetection will end up registering
false-negatives if the import is discovered via dynamic (programmatic)
additions tosys.pathat runtime.The following description assumes the use ofpytestunit testing
support and a project file structuring that includes project root
directories namedsrc(project solution) andtests(project tests of
project source undersrc. Bothsrcandtestsare not intended to
have package initializers (__init__.py). Packages therein will
typically have package initializers allowing for test modules to have
that same name (in separate packages). However, as a general rule, test
modules are not intended to import other test modules. Therefore, there
should be no need for__init__.py-enabled, relative importation
between test cases or sub-package test cases.pytest'sdefault test discoveryand intended design use negates the need for :├─ src
│  └─ __init__.py
|  └─ foo.py
├─ tests
│  └─ test_foo.py
│     └─ foo_and_goo
│        └─ __init__.py
│        └─ test_foo.py
│        └─ test_goo.py
└─ setup.pyThat structure is based uponthis guidance.When testing solution source in a project, the test casescouldstatically access the solution source by importing with thesrcpackage prefix:import src.packagename.fooNot only does that not feel right at all, that solution implies that
tests are runonlyfrom the project root, not within thetestsdirectory itself. If the test is run within thetestsdirectory, thesrcpackage won't be found at runtime.So, using:import packagename.foo... thesrcdirectory would need to be programmatically added to thesys.path. This will allow for tests to be run form any working
directory under thetestssub-tree.runtime_syspath.add_srcdirs_to_syspath()will discover allsrcdirectories under<project root>/src. The reason that there may be
more is if your project may be leveraginggit subprojectsunder<project root>/srcthat have their ownsrcdirectories. Those need
to be added tosys.pathalso.To leverageruntime-syspathto add thesrcdirectory everytime a
test is run, importruntime-syspathand runadd_srcdirs_to_syspath()intests/conftest.py. (Iftestscontain moreconftest.pyunder its directory tree, the call still only
need appear in the roottest/conftest.py!):from runtime_syspath import add_srcdirs_to_syspath

add_srcdirs_to_syspath()add_srcdirs_to_syspath()will recursively discoverallsrcsubdirectories under the . For projects that usegit submodules, theirsrcdirectories need to be added tosrc.pathfor
import access.git subprojectscould be added tosrcortestsdirectory trees:├─ src
│  └─ __init__.py
|  └─ projectpackage
│     └─ __init__.py
|     └─ foo.py
|  └─ subproject
|     └─ src
│       └─ __init__.py
|       └─ bar.py
|     └─ tests
├─ tests
│  └─ test_foo.py
|  └─ test_subproject
|     └─ src
│       └─ __init__.py
|       └─ unfoobarrator.py
|     └─ tests
└─ setup.py:exclamation: Due to the code maintenance and grok'ing mayhem caused
by indiscriminate runtime additions tosys.path, your goal should be
to limit that anti-pattern to this discovery-of-source aspect for
import discovery.:bulb: Since programmatically adding to asys.pathimpairs an IDE's
ability to do static import discovery and leveraging IDE refactoring
features between the solution source and the test code, an IDE user
would need to manually mark allsrcdirectories as such.PyCharm example:SysPathSleuth; runtime reporting of programmaticsys.pathaccessOn a project riddled with programmatically appending source paths tosys.path, a tool to discover which modules are mucking withsys.pathand when could prove useful. This discovery can assist with manually
eradicatingsys.pathaccess in favor of updating imports with
fully-qualified (anchored at but, not includingsrc), absolute
module/package names. static tools would then be able to discover the
modules/packages imported.Relative paths: There is a place for relative paths when importing
intra-package modules. But, when importing inter-package modules,
leveraging fully-qualified, absolute module/package names is a wiser
play.SysPathSleuth is a monkey-patch ofsys.pathto report onsys.pathaccess that comes with an installer to install/uninstall SysPathSleuth
into either the user or system site'scustomizemodules
(~/pathto/user_site/usercustomize.pyor/pathto/python/site-packages/sitecustomize.py). SysPathSleuth can be
installed/uninstalled using one of following option:python -m syspath_sleuth \[--install _or_ --uninstall]syspath_sleuth_injector \[--install _or_ --uninstall]at the start within a running programAt the start of a running program prior:import atexit
import syspath_sleuth
from runtime-syspath import syspath_slueth

syspath_sleuth.inject_sleuth()
def uninstall_syspath_sleuth():
    syspath_sleuth.uninstall_sleuth()

atexit.register(uninstall_syspath_sleuth)

if __name__ == "__main__":
    go_main_go()It is possible to provide your own SysPathSleuth for more interesting
data gathering using the CLI:syspath_sleuth_injector --install --custom my_custom_syspath_sleuth.pyThat file must have a class namedSysPathSleuthand wrap thesys.pathor thesyspath_sleuth_injectorwill reject it.Seesrc/runtime_syspath/syspath_sleuth/syspath_sleuth.pyfor out-of-box
implementation.Think along the lines of providing telemetry as long-running programs
wheedle there ways over their execution paths using loggerHandlerthat sending data to a service.See example uses in theexamplessubdirectory of this project.