Skip to main content

A Python tool to guard against incorrect usage of python modules.

Project description

image image image image Checked with pyright Ruff

modguard

A Python tool to support and enforce a modular, decoupled package architecture.

Full Documentation

What is modguard?

Modguard enables you to explicitly define the public interface for a given module. Marking a package with a Boundary makes all of its internals private by default, exposing only the members marked with the @public decorator.

This enforces an architecture of decoupled and well-defined modules, and ensures the communication between domains is only done through their expected public interfaces.

Modguard is incredibly lightweight, and has no impact on the runtime of your code. Instead, its checks are performed through a static analysis CLI tool.

Installation

pip install modguard

Usage

Add a Boundary to the __init__.py of the module you're creating an interface for.

# project/core/__init__.py
import modguard

modguard.Boundary()

Add the public decorator to any callable in the module that should be exported. You can also export individual members by passing them to public as function call arguments.

# project/core/main.py
import modguard

# Adding the decorator here signifies this function is public
@modguard.public
def public_function(user_id: int) -> str:
    ...

# This function will be considered private
def private_function():
    ...

PUBLIC_CONSTANT = "Hello world"
# Allow export of PUBLIC_CONSTANT from this module
public(PUBLIC_CONSTANT)

Modguard will now flag any incorrect dependencies between modules.

# From the root of your python project (in this example, `project/`)
> modguard check .
❌ ./utils/helpers.py: Import 'core.main.private_function' in ./utils/helpers.py is blocked by boundary 'core.main'

You can also view your entire project's set of dependencies and public interfaces. Boundaries will be marked with a [B], and public members will be marked with a [P]. Note that a module can be both public and a boundary.

> modguard show .
example
  [B]core
    main
      [P]public_function
      [P]PUBLIC_CONSTANT
  [P][B]utils
    helpers

If you want to utilize this data for other purposes, run modguard show --write . This will persist the data about your project in a modguard.yaml file.

Setup

Modguard also comes bundled with a command to set up and define your initial boundaries.

modguard init .

By running modguard init from the root of your python project, modguard will inspect and declare boundaries on each python package within your project. Additionally, each accessed member of that package will be decorated with public.

This will automatically create boundaries and define your public interface for each package within your project, and instantly get your project to a passing state for modguard .

Advanced Usage

Modguard also supports specific allow lists within public.

@modguard.public(allowlist=['utils.helpers'])
def public_function(user_id: int) -> str:
    ...

PUBLIC_CONSTANT = "Hello world"
public(PUBLIC_CONSTANT, allowlist=['utils.helpers'])

This will allow for public_function and PUBLIC_CONSTANT to be imported and used in utils.helpers, but restrict its usage elsewhere.

Alternatively, you can mark an import with the modguard-ignore comment:

# modguard-ignore
from core.main import private_function

This will stop modguard from flagging this import as a boundary violation.

Given that python allows for dynamic importing at runtime, modguard will fail if a whole module is imported without being declared public.

from core import main # contains public and private members
# From the root of your project
> modguard .
❌ ./utils/helpers.py: Import 'core.main' in ./utils/helpers.py is blocked by boundary 'core.main'

If you expect to be able to import the entire contents of your module, you can declare an entire module as public to avoid this:

# core/main.py
import modguard

modguard.public()

...

This syntax also supports the allowlist parameter.

Details

Modguard works by analyzing the abstract syntax tree (AST) of your codebase. The Boundary class and @public decorator have no runtime impact, and are detected by modguard statically.

Boundary violations are detected at the import layer. This means that specific nonstandard custom syntax to access modules/submodules such as getattr or dynamically generated namespaces will not be caught by modguard.

PyPi Package

License

GNU GPLv3

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

modguard-0.6.0.tar.gz (33.7 kB view details)

Uploaded Source

Built Distribution

modguard-0.6.0-py3-none-any.whl (33.3 kB view details)

Uploaded Python 3

File details

Details for the file modguard-0.6.0.tar.gz.

File metadata

  • Download URL: modguard-0.6.0.tar.gz
  • Upload date:
  • Size: 33.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.9.18

File hashes

Hashes for modguard-0.6.0.tar.gz
Algorithm Hash digest
SHA256 ccfaeee4282058d8253697be7d39b67dfda62a1650d87f3f8b94f9a66c7389d0
MD5 f6cf9a72e47d80725344e4b40c8abe63
BLAKE2b-256 81b8ee284a4346c85df0aa8e90a30e70624a371e512754865bfe1942fdb61cc5

See more details on using hashes here.

File details

Details for the file modguard-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: modguard-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 33.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.9.18

File hashes

Hashes for modguard-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e681311d14d65202a74097927e3472d8a8107134cd84b2f55e864ff031ab9855
MD5 d88f6ab58f5ef50800d518c28fe28e99
BLAKE2b-256 54433d474b1836ab06d095f668f9c8c68f3b09472a77385bd0d5db63e586c5d1

See more details on using hashes here.

Supported by

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