Skip to main content

A checker to restrict the tangling of import interdependencies

Project description

Orbie

Orbie is a checker to control and restrict the web of interdependencies within a Python project. It can raise warnings when unwanted interdependencies pop up in a project.

Orbie is named after the orb-weaver spider known for the complex webs it can create.

Why?

Orbie is intended be a tool to set rules and guidance on how the different parts of a Python project can interdepend on each other. Why might this be interesting to someone? Well, sometimes in projects where cohesion and complexity is not kept in mind for the architecture, people will glue random parts together like making the birthday calculator function interdependent on the number of pets string formatter. And what starts out as a quick five minute adventure to fix the dog counter displayed after coming back from a holiday turns into a 6 week tour through the code base untangling it. So this kind of checker can be used to stop the tangling up of the code base.

By minimising the interdependencies (or strictly controlling it), it can decrease the system complexity and open up options to do things like optimise the ci-cd to test only the affected parts of a project without fear of missing re-testing unrelated but affected components.

Example

One example usage might be where you have a single Python project with several subprojects being shipped as part of it but you want to have some level of isolation between the different modules.

src
└── example
    ├── example_a
    │   ├── children
    │   │   ├── child.py
    │   │   └── grandchildren
    │   │       └── grandchild.py
    │   ├── example_a.py
    │   └── sibling.py
    ├── example_b
    │   └── example_b.py
    ├── example_c
    │   ├── children
    │   │   └── child.py
    │   ├── example_c.py
    │   └── sibling.py
    ├── example_d
    │   └── example_d.py
    └── example_shared
        └── shared.py

We may not want example_a referencing anything from example_b, example_c or example_d. And vice-versa for example_b and example_c and example_d.

This can be enforced with some simple code review but that may fail if the reviewer does not have enough coffee. We need a more complicated solution for the sake of being programmers building tools for programmers.

We can configure Orbie to not allow imports from subprojects to its cousins or ancestors and only allow imports within their project spaces.

How It Works

Orbie is really just a wrapper script around [importlab][importlab] which can statically parse the imports dependency tree. Orbie can then filter the project dependencies to find dependencies that are not permitted. If the number of these exceed zero, the script will exit with error code 1.

[importlab]: https://github.com/google/importlab)

Installation

Orbie can be installed from pypi.

Example:

pip install orbie

Usage

$ orbie --help
usage: orbie [-h]
             [--project-root PROJECT_ROOT]
             [--exception EXCEPTIONS]
             [--except-ancestors]
             [--except-descendents]
             [--except-siblings]
             [--except-cousins]
             [--no-except-venv]
             [--debug]
             [-V PYTHON_VERSION]
             [-P PYTHONPATH]
             input

positional arguments:
  input                 Input file or directory

options:
  -h, --help            show this help message and exit
  --project-root PROJECT_ROOT
                        Base project path to find tangled files within
                        (default: ./)
  --exception EXCEPTIONS
                        Adds an exception for an specific tangled file path or
                        entire directory path
  --except-ancestors    Ignore tangled files coming from parent directories of
                        the importer
  --except-descendents  Ignore tangled files coming from subdirectories below
                        the importer
  --except-siblings     Ignore tangled files coming from the same directory as
                        the importer
  --except-cousins      Ignore tangled files coming from directories and
                        subdirectories under the grandparent's directory
  --no-except-venv      Set to not ignore tangled files found in .venv
  --debug               Set to show debug trace logs
  -V PYTHON_VERSION, --python_version PYTHON_VERSION
                        Python version of target code, e.g. '3.12'
  -P PYTHONPATH, --pythonpath PYTHONPATH
                        Directories for reading dependencies - a list of paths
                        separated by ':'

Note that Orbie is assumed to be called from the project root. The current working directory is used to filter internal and external dependencies. Imports coming from a virtual environment in the current working directory (i.e. in a .venv subdirectory) are marked as external dependencies.

Usage: Explicit Exceptions

Of course, restricting interdependencies to 0 typically makes no sense. The more realistic use case of Orbie is to only allow select interdependencies that meet the architecture of the project. One example might be only allowing modules in a specific shared space from being imported (thus creating a clear structure around the interdependencies).

These exceptional import paths can be specified with --exception. They can reference files or directories.

Example:

src
└── example
    ├── example_a
    │   └── example_a.py
    └── example_shared
        └── shared.py

In this case, if example_a.py should freely import modules from example_shared, this can be done with the explicit exception like so.

--exception src/example/example_shared

Usage: Ignore Ancestors

Imported modules that sit in the file system hierarchy above the importer module tree can be excepted with --except-ancestors.

Example: if child.py imports something from sibling.py, this is considered an ancestor import.

src
└── example
    └── example_a
        ├── children
        │   ├── child.py
        ├── example_a.py
        └── sibling.py

Usage: Ignore Descendents

Imported modules that sit in the file system hierarchy below the importer module tree can be excepted with --except-descendents.

Example: if example_a.py imports something from child.py, this is considered a descendent import.

src
└── example
    └── example_a
        ├── children
        │   ├── child.py
        └── example_a.py

Usage: Ignore Siblings

Imported modules that sit in the file system hierarchy parallel to the importer module tree can be excepted with --except-siblings.

Example: if example_a.py imports something from sibling.py, this is considered a sibling import.

src
└── example
    └── example_a
        ├── example_a.py
        └── sibling.py

Usage: Ignore Cousins

Imported modules that sit in the file system tree parallel to the importer module's parent directory tree can be excepted with --except-cousins.

Example: if example_a.py imports something from example_b.py, this is considered a cousin import.

src
└── example
    ├── example_a
    │   └── example_a.py
    └── example_b
        └── example_b.py

Example Project

You can try run Orbie on the subdir example.

Example:

git clone https://github.com/michael131468/orbie.git
cd orbie
cd example
pdm install
pdm run orbie src/example/example_a
pdm run orbie src/example/example_b
pdm run orbie src/example/example_c
pdm run orbie src/example/example_d

Example Project: Example A

In this example, example_a.py should show some sibling imports, descendent imports, and an import from the example_shared space (a cousin import).

$ pdm run orbie src/example/example_a/example_a.py
INFO:orbie.orbie:Accepting imports from .venv (i.e. external dependencies)
INFO:orbie.orbie:Source tree:
+ src/example/example_a/example_a.py
    :: example/example_a/sibling.py
    :: example/example_a/children/child.py
    :: example/example_a/children/grandchildren/grandchild.py
    :: example/example_shared/shared.py
INFO:orbie.orbie:Checking tangleation of src/example/example_a/example_a.py
against other modules in
/Users/michael/git/github.com/michael131468/orbie/example
INFO:orbie.orbie:4 tangled module/s found:
INFO:orbie.orbie:src/example/example_a/children/child.py (descendent)
INFO:orbie.orbie:src/example/example_a/children/grandchildren/grandchild.py
(descendent)
INFO:orbie.orbie:src/example/example_a/sibling.py (sibling)
INFO:orbie.orbie:src/example/example_shared/shared.py (cousin)
INFO:orbie.orbie:Failing check due to the following file/s:
src/example/example_a/example_a.py

These imports could be excepted to get a passing result like so:

$ pdm run orbie src/example/example_a/example_a.py \
    --exception src/example/example_shared \
    --except-siblings \
    --except-descendents
INFO:orbie.orbie:Accepting imports from .venv (i.e. external dependencies)
INFO:orbie.orbie:Accepting imports from descendents
INFO:orbie.orbie:Accepting imports from siblings
INFO:orbie.orbie:Accepting the following paths exceptions:
src/example/example_shared
INFO:orbie.orbie:Source tree:
+ src/example/example_a/example_a.py
    :: example/example_a/sibling.py
    :: example/example_a/children/child.py
    :: example/example_a/children/grandchildren/grandchild.py
    :: example/example_shared/shared.py
INFO:orbie.orbie:Checking tangleation of src/example/example_a/example_a.py
against other modules in
/Users/michael/git/github.com/michael131468/orbie/example
INFO:orbie.orbie:0 tangled modules found!

Example Project: Example B

In this example, example_b.py should show a cousin import from example_a.py.

pdm run orbie src/example/example_b/example_b.py
INFO:orbie.orbie:Accepting imports from .venv (i.e. external dependencies)
INFO:orbie.orbie:Source tree:
+ src/example/example_b/example_b.py
    :: example/example_a/__init__.py
INFO:orbie.orbie:Checking tangleation of src/example/example_b/example_b.py
against other modules in
/Users/michael/git/github.com/michael131468/orbie/example
INFO:orbie.orbie:1 tangled module/s found:
INFO:orbie.orbie:src/example/example_a/__init__.py (cousin)
INFO:orbie.orbie:Failing check due to the following file/s:
src/example/example_b/example_b.py

This import could be excepted to get a passing result like so:

$ pdm run orbie src/example/example_b/example_b.py \
    --exception src/example/example_a/
INFO:orbie.orbie:Accepting imports from .venv (i.e. external dependencies)
INFO:orbie.orbie:Accepting the following paths exceptions:
src/example/example_a/
INFO:orbie.orbie:Source tree:
+ src/example/example_b/example_b.py
    :: example/example_a/__init__.py
INFO:orbie.orbie:Checking tangleation of src/example/example_b/example_b.py
against other modules in
/Users/michael/git/github.com/michael131468/orbie/example
INFO:orbie.orbie:0 tangled modules found!

Example Project: Example C

In this example, example_c.py should show a descendent import from the child child.py.

$ pdm run orbie src/example/example_c/example_c.py
INFO:orbie.orbie:Accepting imports from .venv (i.e. external dependencies)
INFO:orbie.orbie:Source tree:
+ src/example/example_c/example_c.py
    :: example/example_c/children/child.py
INFO:orbie.orbie:Checking tangleation of src/example/example_c/example_c.py against other modules in /Users/michael/git/github.com/michael131468/orbie/example
INFO:orbie.orbie:1 tangled module/s found:
INFO:orbie.orbie:src/example/example_c/children/child.py (descendent)
INFO:orbie.orbie:Failing check due to the following file/s:
src/example/example_c/example_c.py

This import could be excepted to get a passing result like so:

$ pdm run orbie src/example/example_c/example_c.py --except-descendents
INFO:orbie.orbie:Accepting imports from .venv (i.e. external dependencies)
INFO:orbie.orbie:Accepting imports from descendents
INFO:orbie.orbie:Source tree:
+ src/example/example_c/example_c.py
    :: example/example_c/children/child.py
INFO:orbie.orbie:Checking tangleation of src/example/example_c/example_c.py
against other modules in
/Users/michael/git/github.com/michael131468/orbie/example
INFO:orbie.orbie:0 tangled modules found!

Example Project: Example D

In this example, example_d.py should show no imports (and thus no tangling to resolve).

$ pdm run orbie src/example/example_d/example_d.py
INFO:orbie.orbie:Accepting imports from .venv (i.e. external dependencies)
INFO:orbie.orbie:Source tree:
+ src/example/example_d/example_d.py
INFO:orbie.orbie:Checking tangleation of src/example/example_d/example_d.py against other modules in /Users/michael/git/github.com/michael131468/orbie/example
INFO:orbie.orbie:0 tangled modules found!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

orbie-1.0.0.tar.gz (7.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

orbie-1.0.0-py3-none-any.whl (8.0 kB view details)

Uploaded Python 3

File details

Details for the file orbie-1.0.0.tar.gz.

File metadata

  • Download URL: orbie-1.0.0.tar.gz
  • Upload date:
  • Size: 7.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.15.2 CPython/3.11.7 Darwin/23.5.0

File hashes

Hashes for orbie-1.0.0.tar.gz
Algorithm Hash digest
SHA256 7d1f435419f53f1ae1dbeeda6aeee55c53bfa6683199076d947e4c10f54f1113
MD5 36663fad1612b92473e7d67e4cc61e2f
BLAKE2b-256 906dbfc1a4deac1e8259ff05d0a6020cd369192802ebefcf3bc020f80d103c53

See more details on using hashes here.

File details

Details for the file orbie-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: orbie-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 8.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.15.2 CPython/3.11.7 Darwin/23.5.0

File hashes

Hashes for orbie-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6cd4d55e3b729c86935d1a1fb8710cc23f60e63472f5d9f013d73b466a51c567
MD5 d5c47ab67bd260067c28c37092d33de5
BLAKE2b-256 b05537c800def479799852b725fea53e6f106a5278a1241c079e4fb5aad04e33

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page