Skip to content

CLI Mode

ImportSpy can also be used outside of runtime to validate a Python module against a contract from the command line.

This is useful in CI/CD pipelines, pre-commit hooks, or manual validations โ€” whenever you want to enforce import contracts without modifying the target module.


How it works

In CLI Mode, you invoke the importspy command and provide:

  • The path to the module to validate
  • The path to the YAML contract
  • (Optional) a log level for output verbosity

ImportSpy loads the module dynamically, builds its SpyModel, and compares it against the .yml contract.

If the module is non-compliant, the command will:

  • Exit with a non-zero status
  • Print a structured error explaining the violation

Basic usage

importspy extensions.py -s spymodel.yml -l WARNING

CLI options

$ importspy --help
Usage: importspy [OPTIONS] [MODULEPATH]

Validates a Python module against a YAML-defined SpyModel contract.

โ•ญโ”€ Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚   modulepath      [MODULEPATH]  Path to the Python module to load and validate.                                  โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ --version             -v                                  Show the version and exit.                              โ”‚
โ”‚ --spymodel            -s      TEXT                        Path to the import contract file (.yml).                โ”‚
โ”‚                                                           [default: spymodel.yml]                                 โ”‚
โ”‚ --log-level           -l      [DEBUG|INFO|WARNING|ERROR]  Log level for output verbosity. [default: None]         โ”‚
โ”‚ --install-completion                                      Install completion for the current shell.               โ”‚
โ”‚ --show-completion                                         Show completion for the current shell, to copy it or    โ”‚
โ”‚                                                           customize the installation.                             โ”‚
โ”‚ --help                                                    Show this message and exit.                             |
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Example project

Letโ€™s look at a full CLI-mode validation example.

Project structure

pipeline_validation/
โ”œโ”€โ”€ extensions.py
โ””โ”€โ”€ spymodel.yml

๐Ÿ“„ Source files

from plugin_interface import Plugin

author = "Luca Atella"

plugin_name = "plugin name"
plugin_description = "plugin description"

engine = "docker"

class Extension(Plugin):

    extension_name = "extension_value"

    def __init__(self) -> None:
        self.extension_instance_name = "extension_instance_value"

    def add_extension(self, msg:str) -> str:
        print(msg)
        return "Extension has added"

    def remove_extension(self):
        print("Extension has removed")

    def http_get_request(self):
        print("done")

class Foo:

    def get_bar(self):
        print("Foobar")
filename: extension.py
variables:
  - name: engine
    value: docker
  - name: plugin_name
    value: plugin name
  - name: plugin_description
    value: plugin description
classes:
  - name: Extension
    attributes:
      - type: instance
        name: extension_instance_name
        value: extension_instance_value
      - type: class
        name: extension_name
        value: extension_value
    methods:
      - name: __init__
        arguments:
          - name: self
        return_annotation:
      - name: add_extension
        arguments:
          - name: self
          - name: msg
            annotation: str
        return_annotation: str
      - name: remove_extension
        arguments:
          - name: self
        return_annotation:
      - name: http_get_request
        arguments:
          - name: self
    superclasses:
      - name: Plugin
  - name: Foo
    attributes:
    methods:
      - name: get_bar
        arguments:
          - name: self
deployments:
  - arch: x86_64
    systems:
      - os: linux
        pythons:
          - version: 3.12.9
            interpreter: CPython
            modules:
              - filename: extension.py
                version:
                variables:
                  - name: author 
                    value: Luca Atella
                functions:
                classes:
          - version: 3.12.4
            interpreter:
            modules:
              - filename: addons.py
          - interpreter: IronPython
            modules:
              - filename: addons.py
      - os: windows
        pythons:
          - version: 3.12.9
            interpreter: CPython
            modules:
              - filename: extension.py
                version:
                variables:
                  - name: author
                    value: Luca Atella

๐Ÿ” Run validation

cd examples/plugin_based_architecture/pipeline_validation
importspy extensions.py -s spymodel.yml -l WARNING

If the module matches the contract, the command exits silently with 0.
If it doesn't, youโ€™ll see a structured error like:

[Structure Violation] Missing required method 'get_bar' in class 'Foo'.

When to use CLI Mode

Use CLI Mode for automation

CLI Mode is ideal when you want to:

  • Validate modules without changing their code
  • Integrate checks in CI/CD pipelines
  • Enforce contracts in external packages
  • Run batch validations over multiple files

Import Contract Syntax

An ImportSpy contract is a YAML file that describes:

  • The structure expected in the calling module (classes, methods, variablesโ€ฆ)
  • The runtime and system environment where the module is allowed to run
  • The required environment variables and optional secrets

This contract is parsed into a SpyModel, which is then compared against the actual runtime and importing module.


โœ… Overview

Hereโ€™s a minimal but complete contract:

filename: extension.py
variables:
  - name: engine
    value: docker
classes:
  - name: Plugin
    methods:
      - name: run
        arguments:
          - name: self
deployments:
  - arch: x86_64
    systems:
      - os: linux
        pythons:
          - version: 3.12
            interpreter: CPython

๐Ÿ“„ filename

filename: extension.py
  • Optional.
  • Declares the filename of the module being validated.
  • Used for reference and filtering in multi-module declarations.

๐Ÿ”ฃ variables

variables:
  - name: engine
    value: docker
  • Declares top-level variables that must be present in the importing module.
  • Supports optional annotation (type hint).
  - name: debug
    annotation: bool
    value: true

๐Ÿง  functions

functions:
  - name: run
    arguments:
      - name: self
      - name: config
        annotation: dict
    return_annotation: bool
  • Declares standalone functions expected in the importing module.
  • Use arguments and return_annotation for stricter typing.

๐Ÿงฑ classes

classes:
  - name: Plugin
    attributes:
      - type: class
        name: plugin_name
        value: my_plugin
    methods:
      - name: run
        arguments:
          - name: self
    superclasses:
      - name: BasePlugin

Each class can declare:

  • attributes: divided by type (class or instance)
  • methods: each with arguments and optional return_annotation
  • superclasses: flat list of required superclass names

๐Ÿงญ deployments

This section defines where the module is allowed to run.

deployments:
  - arch: x86_64
    systems:
      - os: linux
        pythons:
          - version: 3.12.9
            interpreter: CPython
            modules:
              - filename: extension.py
                version: 1.0.0
                variables:
                  - name: author
                    value: Luca Atella

โœณ๏ธ Fields

Field Type Description
arch Enum e.g. x86_64, arm64
os Enum linux, windows, darwin
version str Python version string (3.12.4)
interpreter Enum CPython, PyPy, IronPython, etc.
modules list Repeats the structure declaration per module

This structure allows fine-grained targeting of supported environments.


๐ŸŒฑ environment

Environment variables and secrets expected on the system.

environment:
  variables:
    - name: LOG_LEVEL
      value: INFO
    - name: DEBUG
      annotation: bool
      value: true
  secrets:
    - MY_SECRET_KEY
    - DATABASE_PASSWORD
  • variables: can define name, value, and annotation
  • secrets: only their presence is verified โ€” values are never exposed

Notes

  • All fields are optional โ€” contracts can be partial
  • Field order does not matter
  • Unknown fields are ignored with a warning (not an error)

Learn more