diff --git a/.gitignore b/.gitignore
index 28b18d0..ef3447e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,5 @@ venv/
ENV/
env.bak/
venv.bak/
-.pytest_cache/
\ No newline at end of file
+.pytest_cache/
+/site/
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9b16053..b3d2025 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,33 +1,127 @@
# Contributing to ImportSpy
-🎉 Thank you for considering a contribution to **ImportSpy**! We value your efforts and welcome **issues, features, and documentation improvements**.
+🎉 Thank you for considering a contribution to **ImportSpy** — a project designed to bring structure, security, and clarity to Python imports.
+
+We welcome all kinds of contributions: new features, bug fixes, tests, documentation, and even thoughtful ideas. Your involvement makes the project better.
+
+---
## 📢 How to Contribute
-### 🔍 1. Issue Reporting
-If you find a **bug** or have a **feature request**, please open an issue:
-🔗 [GitHub Issues](https://github.com/atellaluca/ImportSpy/issues)
-
-### 🔄 2. Fork & Branching Strategy
-- **Create a fork** and work on a separate branch before submitting a pull request.
-- Use the following **branch naming conventions**:
- - **For features:** `feature/nome-feature-in-breve`
- - **For bug fixes:** `fix/bugfix-description`
- - **For documentation:** `docs/update-section`
-
-### ✅ 3. Code Style & Commit Rules
-- **Follow Conventional Commits**:
- - `feat:` → For new features
- - `fix:` → For bug fixes
- - `docs:` → For documentation changes
- - `test:` → For tests
- - `refactor:` → Code improvements without changing functionality
-
-Example:
+### 1. Open an Issue
+If you encounter a bug, have a feature suggestion, or want to propose a change, please [open an issue](https://github.com/atellaluca/ImportSpy/issues). Use clear and descriptive titles.
+
+You can use labels such as:
+- `bug`: for broken functionality
+- `enhancement`: for feature requests
+- `question`: for clarification or design discussions
+
+### 2. Fork and Branch Strategy
+
+- Create a **fork** of the repository.
+- Work in a dedicated **feature branch** off `main`.
+- Use consistent branch naming:
+
+| Purpose | Branch Name Format |
+|----------------|-------------------------------|
+| Feature | `feature/short-description` |
+| Bug fix | `fix/issue-description` |
+| Documentation | `docs/section-description` |
+| Tests | `test/feature-or-bug-name` |
+
+> 💡 Keep pull requests focused and small. This helps reviewers and speeds up merging.
+
+---
+
+## ✅ Code Standards
+
+### Python Style Guide
+ImportSpy follows:
+- [PEP8](https://peps.python.org/pep-0008/)
+- Type hints throughout the codebase
+- `black` + `ruff` for formatting and linting
+- `pydantic` for data models
+
+### Linting & Tests
+
+To run tests and lint checks:
+
```bash
-git commit -m "feat: add validation for OS compatibility"
+poetry install
+pytest
+ruff check .
+black --check .
```
-💡 **We Appreciate Every Contribution!**
+---
+
+## 📄 Commit Conventions
+
+We use **[Conventional Commits](https://www.conventionalcommits.org/)** for readable history and automatic changelog generation.
+
+| Type | Use For |
+|----------|-------------------------------|
+| `feat:` | New feature |
+| `fix:` | Bug fix |
+| `docs:` | Documentation only |
+| `refactor:` | Code change w/o new feature or fix |
+| `test:` | Adding or updating tests |
+| `chore:` | Internal tooling or CI |
+
+**Example:**
+```bash
+git commit -m "feat: support multiple Python interpreters in contract"
+```
+
+---
+
+## 🧪 Test Philosophy
+
+Tests live in `tests/validators/` and should:
+- Cover core logic (validators, spymodel, contract violations)
+- Include both positive and negative cases
+- Be fast, deterministic, and isolated
+
+---
+
+## ✍️ Docs Contributions
+
+We use **MkDocs + Material** for documentation. Docs live under:
+
+```
+docs/
+```
+
+To preview locally:
+
+```bash
+poetry install
+mkdocs serve
+```
+
+New pages should be added to `mkdocs.yml` under the right section.
+
+---
+
+## 🙌 Join the Community
+
+While we don’t yet have a Discord or forum, we encourage:
+- Sharing feedback via GitHub Issues
+- Discussing architecture via PRs and comments
+- Connecting with the maintainer via LinkedIn or GitHub
+
+---
+
+## 💬 Need Help?
+
+Open an issue with the `question` label or ping @atellaluca in your PR.
+
+---
+
+## 📜 License
+
+By contributing, you agree your work will be released under the MIT License.
+
+---
-ImportSpy is an open-source project, and every contribution—big or small—helps make it better. 🚀
\ No newline at end of file
+**Let your modules enforce their own rules — and thank you for helping ImportSpy grow!**
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c394218
--- /dev/null
+++ b/README.md
@@ -0,0 +1,178 @@
+# ImportSpy
+
+
+[](https://pypi.org/project/importspy/)
+
+[](https://github.com/atellaluca/ImportSpy/actions/workflows/python-package.yml)
+[](https://importspy.readthedocs.io/)
+
+
+
+**Runtime contract validation for Python imports.**
+_Enforce structure. Block invalid usage. Stay safe at runtime._
+
+---
+
+## 🔍 What is ImportSpy?
+
+**ImportSpy** lets your Python modules declare structured **import contracts** (via `.yml` files) to define:
+
+- What environment they expect (OS, Python version, interpreter)
+- What structure they must follow (classes, methods, variables)
+- Who is allowed to import them
+
+If the contract is not met, **ImportSpy blocks the import** — ensuring safe and predictable runtime behavior.
+
+---
+
+## ✨ Key Features
+
+- ✅ Validate imports dynamically at runtime or via CLI
+- ✅ Block incompatible usage of internal or critical modules
+- ✅ Enforce module structure, arguments, annotations
+- ✅ Context-aware: Python version, OS, architecture, interpreter
+- ✅ Human-readable YAML contracts
+- ✅ Clear, CI-friendly violation messages
+
+---
+
+## 📦 Installation
+
+```bash
+pip install importspy
+```
+
+> Requires Python 3.10+
+
+---
+
+## 📐 Architecture
+
+
+
+ImportSpy is powered by a layered introspection model (`SpyModel`), which captures:
+
+- `Runtime`: CPU architecture
+- `System`: OS and environment
+- `Python`: interpreter and version
+- `Module`: classes, functions, variables, annotations
+
+Each layer is validated against the corresponding section of your `.yml` contract.
+
+---
+
+## 📜 Example Contract
+
+```yaml
+filename: plugin.py
+variables:
+ - name: mode
+ value: production
+ annotation: str
+classes:
+ - name: Plugin
+ methods:
+ - name: run
+ arguments:
+ - name: self
+ - name: data
+ annotation: dict
+ return_annotation: None
+```
+
+---
+
+## 🔧 Modes of Use
+
+### Embedded Mode – protect your own module
+
+```python
+from importspy import Spy
+
+caller = Spy().importspy(filepath="spymodel.yml")
+caller.Plugin().run()
+```
+
+
+
+---
+
+### CLI Mode – external validation in CI
+
+```bash
+importspy -s spymodel.yml -l DEBUG path/to/module.py
+```
+
+
+
+---
+
+## 🧠 How It Works
+
+1. You define an import contract in `.yml`
+2. At runtime or via CLI, ImportSpy inspects:
+ - Who is importing the module
+ - What the system/environment looks like
+ - What the module structure provides
+3. If validation fails → the import is blocked
+4. If valid → the module runs safely
+
+---
+
+## ✅ Tech Stack
+
+- [Pydantic 2.x](https://docs.pydantic.dev) – schema validation
+- [Typer](https://typer.tiangolo.com) – CLI
+- [ruamel.yaml](https://yaml.readthedocs.io/) – YAML support
+- `inspect` + `sys` – runtime introspection
+- [Poetry](https://python-poetry.org) – dependency management
+- [Sphinx](https://www.sphinx-doc.org) + ReadTheDocs – documentation
+
+---
+
+## 📘 Documentation
+
+- **Full docs** → [importspy.readthedocs.io](https://importspy.readthedocs.io)
+- [Quickstart](https://importspy.readthedocs.io/en/latest/intro/quickstart.html)
+- [Contract syntax](https://importspy.readthedocs.io/en/latest/contracts/syntax.html)
+- [Violation system](https://importspy.readthedocs.io/en/latest/advanced/violations.html)
+- [API Reference](https://importspy.readthedocs.io/en/latest/api-reference.html)
+
+---
+
+## 🚀 Ideal Use Cases
+
+- Plugin-based frameworks (e.g., CMS, CLI, IDE)
+- CI/CD pipelines with strict integration
+- Security-regulated environments (IoT, medical, fintech)
+- Package maintainers enforcing internal boundaries
+
+---
+
+## 💡 Why It Matters
+
+Python’s flexibility comes at a cost:
+
+- Silent runtime mismatches
+- Missing methods or classes
+- Platform-dependent failures
+- No enforcement over module consumers
+
+**ImportSpy brings governance**
+to how, when, and where modules are imported.
+
+---
+
+## ❤️ Contribute & Support
+
+- ⭐ [Star on GitHub](https://github.com/atellaluca/ImportSpy)
+- 🐛 [File issues or feature requests](https://github.com/atellaluca/ImportSpy/issues)
+- 🤝 [Contribute](https://github.com/atellaluca/ImportSpy/blob/main/CONTRIBUTING.md)
+- 💖 [Sponsor on GitHub](https://github.com/sponsors/atellaluca)
+
+---
+
+## 📜 License
+
+MIT © 2024 – Luca Atella
+
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 2b554f8..0000000
--- a/README.rst
+++ /dev/null
@@ -1,190 +0,0 @@
-.. image:: https://img.shields.io/github/license/atellaluca/importspy
- :alt: License
-
-.. image:: https://img.shields.io/pypi/v/importspy
- :target: https://pypi.org/project/importspy/
- :alt: PyPI Version
-
-.. image:: https://img.shields.io/pypi/pyversions/importspy
- :alt: Supported Python Versions
-
-.. image:: https://img.shields.io/github/actions/workflow/status/atellaluca/ImportSpy/python-package.yml
- :target: https://github.com/atellaluca/ImportSpy/actions/workflows/python-package.yml
- :alt: Build Status
-
-.. image:: https://img.shields.io/readthedocs/importspy
- :target: https://importspy.readthedocs.io/
- :alt: Documentation Status
-
-.. image:: https://github.com/atellaluca/ImportSpy/blob/main/assets/importspy-banner.png
- :alt: ImportSpy – Runtime Contract Validation for Python
- :width: 500px
-
-ImportSpy
-=========
-
-Contract-based import validation for Python modules.
-
-*Runtime-safe, structure-aware, declarative.*
-
-ImportSpy allows your Python modules to define explicit **import contracts**:
-rules about where, how, and by whom they can be safely imported — and blocks any import that doesn’t comply.
-
-🔍 Key Benefits
----------------
-
-- ✅ Prevent import from unsupported environments
-- ✅ Enforce structural expectations (classes, attributes, arguments)
-- ✅ Control who can use your module and how
-- ✅ Reduce runtime surprises across CI, staging, and production
-- ✅ Define everything in readable `.yml` contracts
-
-💡 Why ImportSpy?
------------------
-
-Python is flexible, but uncontrolled imports can lead to:
-
-- 🔥 Silent runtime failures
-- 🔍 Structural mismatches (wrong or missing methods/classes)
-- 🌍 Inconsistent behavior across platforms
-- 🚫 Unauthorized usage of internal code
-
-ImportSpy offers you **runtime import governance** — clearly defined, enforced in real-time.
-
-📐 Architecture Highlight
--------------------------
-
-.. image:: https://raw.githubusercontent.com/atellaluca/ImportSpy/refs/heads/main/assets/importspy-spy-model-architecture.png
- :alt: ImportSpy, SpyModel Architecture
- :width: 830
-
-ImportSpy uses a layered model (`SpyModel`) that mirrors your execution context and module structure:
-
-- `Runtime` → defines architecture and system
-- `System` → declares OS and environment variables
-- `Python` → specifies interpreter, version, and modules
-- `Module` → lists classes, functions, variables (each represented as objects, not dicts)
-
-Each element is introspected and validated dynamically, at runtime or via CLI.
-
-📜 Contract Example
--------------------
-
-.. code-block:: yaml
-
- filename: plugin.py
- variables:
- - name: mode
- value: production
- annotation: str
- classes:
- - name: Plugin
- methods:
- - name: run
- arguments:
- - name: self
- - name: data
- annotation: dict
- return_annotation: None
-
-📦 Installation
----------------
-
-.. code-block:: bash
-
- pip install importspy
-
-✅ Requires Python 3.10+
-
-🔒 Usage Modes
---------------
-
-**Embedded Mode** – the module protects itself:
-
-.. image:: https://raw.githubusercontent.com/atellaluca/ImportSpy/refs/heads/main/assets/importspy-embedded-mode.png
- :alt: How ImportSpy Embedded Mode Works
- :width: 830
-
-.. code-block:: python
-
- from importspy import Spy
- importer = Spy().importspy(filepath="spymodel.yml")
- importer.Plugin().run()
-
-**CLI Mode** – validate externally in CI/CD:
-
-.. image:: https://raw.githubusercontent.com/atellaluca/ImportSpy/refs/heads/main/assets/importspy-works.png
- :alt: How ImportSpy CLI Mode Works
- :width: 830
-
-.. code-block:: bash
-
- importspy -s spymodel.yml -l DEBUG path/to/module.py
-
-📚 Features Overview
---------------------
-
-- ✅ Runtime validation based on import contracts
-- ✅ YAML-based, declarative format
-- ✅ Fine-grained introspection of classes, functions, arguments
-- ✅ OS, architecture, interpreter matching
-- ✅ Full error messages, CI-friendly output
-- ✅ Supports embedded or external enforcement
-- ✅ Strong internal model (`SpyModel`) powered by `pydantic`
-
-🚀 Ideal Use Cases
-------------------
-
-- 🛡️ Security-sensitive systems (finance, IoT, medical)
-- 🧩 Plugin-based architectures (CMS, CLI, extensions)
-- 🧪 CI/CD pipelines with strict integration rules
-- 🧱 Frameworks with third-party extension points
-- 📦 Package maintainers enforcing integration rules
-
-🧠 How It Works
----------------
-
-1. Define your contract in `.yml` or Python.
-2. ImportSpy loads your module and introspects its importer.
-3. Runtime environment + structure are matched against the contract.
-4. If mismatch → import blocked.
- If valid → import continues safely.
-
-🎯 Tech Stack
--------------
-
-- ✅ Pydantic 2.x – contract validation engine
-- ✅ Typer – CLI interface
-- ✅ ruamel.yaml – YAML parsing
-- ✅ inspect + sys – runtime context introspection
-- ✅ Poetry – package + dependency management
-- ✅ Sphinx + ReadTheDocs – full docs and architecture reference
-
-📘 Documentation
-----------------
-
-- 🔗 Full Docs → https://importspy.readthedocs.io/
-- 🧱 Model Overview → https://importspy.readthedocs.io/en/latest/advanced/architecture_index.html
-- 🧪 Use Cases → https://importspy.readthedocs.io/en/latest/overview/use_cases_index.html
-
-🌟 Contribute & Support
------------------------
-
-- ⭐ Star → https://github.com/atellaluca/ImportSpy
-- 🛠 Contribute via issues or PRs
-- 💖 Sponsor → https://github.com/sponsors/atellaluca
-
-🔥 **Let your modules enforce their own rules.**
-Start importing with structure.
-
-📜 License
-----------
-
-MIT © 2024 – Luca Atella
-
-.. image:: ./assets/importspy-logo.png
- :alt: ImportSpy Logo
- :width: 100px
- :align: center
-
-**ImportSpy** is an open-source project maintained with ❤️ by `Luca Atella `_.
diff --git a/assets/importspy-spy-model-architecture.png b/assets/importspy-spy-model-architecture.png
deleted file mode 100644
index f40bb25..0000000
Binary files a/assets/importspy-spy-model-architecture.png and /dev/null differ
diff --git a/diagrams/importspy-spy-model-architecture.drawio b/diagrams/importspy-spy-model-architecture.drawio
new file mode 100644
index 0000000..e33f784
--- /dev/null
+++ b/diagrams/importspy-spy-model-architecture.drawio
@@ -0,0 +1,428 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index d0c3cbf..0000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,20 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line, and also
-# from the environment for the first two.
-SPHINXOPTS ?=
-SPHINXBUILD ?= sphinx-build
-SOURCEDIR = source
-BUILDDIR = build
-
-# Put it first so that "make" without argument is like "make help".
-help:
- @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
- @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/advanced/spymodel.md b/docs/advanced/spymodel.md
new file mode 100644
index 0000000..b6ecd13
--- /dev/null
+++ b/docs/advanced/spymodel.md
@@ -0,0 +1,85 @@
+# SpyModel Architecture
+
+The **SpyModel** is the central object in ImportSpy's validation model.
+It represents both the structural definition of a Python module and the contextual constraints under which the module can be imported and executed.
+
+This hybrid role makes it a bridge between the static world of code structure and the dynamic world of runtime validation.
+
+---
+
+## Overview
+
+ImportSpy introduces the concept of **import contracts**: structured `.yml` files that describe how a Python module is expected to behave, what it provides, and where it is valid.
+
+The SpyModel is the object-oriented representation of these contracts. It is composed of two main aspects:
+
+- **Structural metadata**: variables, functions, classes, attributes, and type annotations
+- **Deployment constraints**: supported architectures, operating systems, interpreters, Python versions, environment variables, and secrets
+
+This combination allows precise control over when and where a module can be imported.
+
+---
+
+## Structural Layer
+
+The structural part of a SpyModel describes the internal shape of the Python module:
+
+- **filename**: the name of the `.py` file
+- **variables**: global variables defined at module level, including optional type and value
+- **functions**: standalone functions, with argument names, types, and return annotations
+- **classes**: including attributes (class-level and instance-level), methods, and inheritance
+
+Each of these elements is validated against what the contract expects.
+This ensures that a module importing another can rely on a well-defined structure.
+
+---
+
+## Deployment Layer
+
+The second part of the model defines where and under which conditions the module is considered valid.
+
+This is handled by the `deployments` field, a list of accepted runtime configurations.
+Each deployment includes:
+
+- **arch**: CPU architecture (e.g., `x86_64`, `ARM`)
+- **systems**: operating systems supported (`linux`, `windows`, `macos`)
+ - Each system includes:
+ - **environment**:
+ - **variables**: required environment variables and their expected values
+ - **secrets**: variable names that must exist, without checking their value
+ - **pythons**: accepted Python interpreters and versions, each with:
+ - a list of expected **modules**, including their own structure
+
+This allows defining highly specific constraints such as:
+
+- “This plugin can only be used on Linux x86_64 with Python 3.12.9”
+- “This module requires `MY_SECRET_KEY` to be present in the environment”
+
+---
+
+## Schema Overview
+
+The following UML diagram summarizes the structure of the SpyModel and its relationships:
+
+
+
+Each node represents a data structure used during validation.
+The model is hierarchical: from deployments down to classes and attributes, every element is traceable and verifiable.
+
+---
+
+## Design Rationale
+
+The SpyModel is designed to be:
+
+- **Declarative**: the contract is expressed in data, not code logic
+- **Versionable**: stored as YAML, the contract can be committed to Git
+- **Composable**: it supports multiple deployment targets and alternative environments
+- **Predictable**: ensures that structural mismatches are detected early
+
+By separating structure from logic, ImportSpy enables a contract-driven development workflow.
+This is particularly useful in plugin frameworks, controlled environments, or distributed systems where consistency across modules and contexts is critical.
+
+---
+
+ImportSpy treats contracts as **first-class citizens**. The SpyModel is the embodiment of this philosophy: a transparent, enforceable, and structured declaration of what a module requires and provides.
diff --git a/docs/advanced/violations.md b/docs/advanced/violations.md
new file mode 100644
index 0000000..badc68a
--- /dev/null
+++ b/docs/advanced/violations.md
@@ -0,0 +1,165 @@
+# Violation System
+
+The Violation System in **ImportSpy** is responsible for surfacing clear, contextual, and actionable errors when a Python module fails to comply with its declared **import contract**.
+
+Rather than raising generic Python exceptions, this subsystem transforms validation failures into precise, domain-specific diagnostics that are structured, explainable, and safe to expose in both development and production contexts.
+
+---
+
+## Purpose
+
+In modular and plugin-based systems, structural mismatches and runtime incompatibilities can lead to subtle bugs, hard crashes, or silent failures.
+
+The Violation System serves as a **contract enforcement layer** that:
+
+- Captures detailed context about the failure (module, scope, variable, system)
+- Distinguishes between **missing**, **mismatched**, and **invalid** values
+- Produces **human-readable** error messages tailored to developers and CI pipelines
+- Structures error reporting consistently across validation layers (module, runtime, environment, etc.)
+
+---
+
+## Core Concepts
+
+### 1. `ContractViolation` Interface
+
+An abstract base that defines the required interface for all contract violations. It ensures consistency across the different scopes (variables, functions, systems, etc.).
+
+```python
+class ContractViolation(ABC):
+ @property
+ @abstractmethod
+ def context(self) -> str
+
+ @abstractmethod
+ def label(self, spec: str) -> str
+
+ @abstractmethod
+ def missing_error_handler(self, spec: str) -> str
+
+ @abstractmethod
+ def mismatch_error_handler(self, spec: str) -> str
+
+ @abstractmethod
+ def invalid_error_handler(self, spec: str) -> str
+```
+
+### 2. `BaseContractViolation`
+
+A reusable abstract class that implements common logic for generating violation messages (e.g. missing values, mismatches, invalid values) across all scopes.
+It uses templates from `Errors` to construct consistent, high-fidelity diagnostics.
+
+```text
+Example output:
+[Module Validation Error]: Variable 'API_KEY' is missing. - Declare it in the expected module.
+```
+
+---
+
+## Specific Violation Classes
+
+Each domain (Variable, Function, Runtime, etc.) has its own implementation:
+
+| Class | Purpose |
+|--------------------------|----------------------------------------------------------|
+| `VariableContractViolation` | Handles variable mismatches or missing values |
+| `FunctionContractViolation` | Detects function mismatches and signature issues |
+| `ModuleContractViolation` | Validates filename and version consistency |
+| `RuntimeContractViolation` | Validates system architectures (`x86_64`, `arm64`, etc.)|
+| `SystemContractViolation` | Checks operating system and environment variable match |
+| `PythonContractViolation` | Validates interpreter and Python version compatibility |
+
+Each one specializes the `.label()` method to return error-specific context (e.g., "Environment variable `MY_SECRET` is missing in production runtime").
+
+---
+
+## Dynamic Payload Injection via `Bundle`
+
+Each violation operates with a shared mutable dictionary-like object called a **`Bundle`**:
+
+```python
+@dataclass
+class Bundle(MutableMapping):
+ state: Optional[dict[str, Any]] = field(default_factory=dict)
+```
+
+It serves two purposes:
+
+- Collect **contextual information** at validation time (e.g., variable name, expected type)
+- Dynamically populate **templated error messages** using the `Errors` constant map
+
+This design allows error messages to adapt to the specific failure, with no hardcoding.
+
+---
+
+## Error Categorization
+
+ImportSpy distinguishes between three primary error types:
+
+| Category | Description |
+|--------------|--------------------------------------------------------------------------|
+| `MISSING` | A required entity (class, method, variable) was not found |
+| `MISMATCH` | An entity exists but its value, annotation, or structure differs |
+| `INVALID` | A value exists but does not belong to the allowed set (e.g., OS types) |
+
+The `Errors` constant defines templates for all categories, per context:
+
+```yaml
+"MISSING":
+ "module":
+ "template": "Expected module '{label}' is missing"
+ "solution": "Add the module to your import path"
+```
+
+---
+
+## Engineering Highlights
+
+- **Encapsulation**: All formatting, message construction, and error typing is abstracted out of validators.
+- **Separation of Concerns**: Validators focus only on logic, while violations handle messaging.
+- **Templated Errors**: All violations draw from `Errors`, ensuring uniformity and easier localization or branding.
+- **Composable Context**: The `Bundle` allows rich diagnostics without tight coupling between layers.
+
+---
+
+## Example Usage
+
+```python
+from importspy.violation_systems import VariableContractViolation
+
+bundle = Bundle()
+bundle["expected_variable"] = "API_KEY"
+
+raise ValueError(
+ VariableContractViolation(
+ scope="module",
+ context="MODULE_CONTEXT",
+ bundle=bundle
+ ).missing_error_handler("entity")
+)
+```
+
+Produces:
+
+```text
+[Module Validation]: Variable 'API_KEY' is missing - Declare it in the target module.
+```
+
+---
+
+## Future Extensions
+
+The Violation System is designed to be:
+
+- Extensible with new scopes (`Decorators`, `ReturnTypes`, `Dependencies`)
+- Pluggable with i18n/l10n systems
+- Renderable in **structured JSON** for machine processing in DevOps pipelines
+
+---
+
+## Summary
+
+The Violation System is not just error handling — it is ImportSpy’s **engine of clarity**.
+It converts abstract contract mismatches into structured, interpretable diagnostics that empower developers to catch errors before runtime.
+
+By modeling validation feedback as first-class entities, ImportSpy enables precise governance across modular codebases.
diff --git a/docs/api-reference.md b/docs/api-reference.md
new file mode 100644
index 0000000..c0ccbf1
--- /dev/null
+++ b/docs/api-reference.md
@@ -0,0 +1,119 @@
+# API Reference
+
+This section documents the public Python API exposed by ImportSpy, organized by module.
+All components listed here are available when you install the package.
+
+## `importspy.s`
+
+::: importspy.s
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.models`
+
+::: importspy.models
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.validators`
+
+::: importspy.validators
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.violation_systems`
+
+::: importspy.violation_systems
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.persistences`
+
+::: importspy.persistences
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.cli`
+
+::: importspy.cli
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.constants`
+
+::: importspy.constants
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.config`
+
+::: importspy.config
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.log_manager`
+
+::: importspy.log_manager
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.utilities.module_util`
+
+::: importspy.utilities.module_util
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.utilities.runtime_util`
+
+::: importspy.utilities.runtime_util
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.utilities.system_util`
+
+::: importspy.utilities.system_util
+ handler: python
+ options:
+ show_source: false
+
+---
+
+## `importspy.utilities.python_util`
+
+::: importspy.utilities.python_util
+ handler: python
+ options:
+ show_source: false
diff --git a/docs/assets/apple-touch-icon.png b/docs/assets/apple-touch-icon.png
new file mode 100644
index 0000000..98a6468
Binary files /dev/null and b/docs/assets/apple-touch-icon.png differ
diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico
new file mode 100644
index 0000000..f09007e
Binary files /dev/null and b/docs/assets/favicon.ico differ
diff --git a/assets/importspy-banner.png b/docs/assets/importspy-banner.png
similarity index 100%
rename from assets/importspy-banner.png
rename to docs/assets/importspy-banner.png
diff --git a/assets/importspy-embedded-mode.png b/docs/assets/importspy-embedded-mode.png
similarity index 100%
rename from assets/importspy-embedded-mode.png
rename to docs/assets/importspy-embedded-mode.png
diff --git a/assets/importspy-logo.png b/docs/assets/importspy-logo.png
similarity index 100%
rename from assets/importspy-logo.png
rename to docs/assets/importspy-logo.png
diff --git a/docs/assets/importspy-spy-model-architecture.png b/docs/assets/importspy-spy-model-architecture.png
new file mode 100644
index 0000000..35b8a46
Binary files /dev/null and b/docs/assets/importspy-spy-model-architecture.png differ
diff --git a/assets/importspy-works.png b/docs/assets/importspy-works.png
similarity index 100%
rename from assets/importspy-works.png
rename to docs/assets/importspy-works.png
diff --git a/docs/contracts/examples.md b/docs/contracts/examples.md
new file mode 100644
index 0000000..393dede
--- /dev/null
+++ b/docs/contracts/examples.md
@@ -0,0 +1,168 @@
+# Contract Examples
+
+This page provides complete examples of import contracts (`.yml` files) supported by ImportSpy.
+
+Each example demonstrates how to declare structural expectations and runtime constraints for a Python module using the SpyModel format.
+
+---
+
+## Basic Module Contract
+
+This example defines a simple module called `plugin.py` with one variable, one class, and one method.
+
+```yaml
+filename: plugin.py
+variables:
+ - name: mode
+ value: production
+ annotation: str
+classes:
+ - name: Plugin
+ methods:
+ - name: run
+ arguments:
+ - name: self
+ return_annotation: None
+deployments:
+ - arch: x86_64
+ systems:
+ - os: linux
+ pythons:
+ - version: 3.11
+ interpreter: CPython
+```
+
+---
+
+## Function With Typed Arguments
+
+This contract describes a module where the `analyze` function requires two arguments with specific types.
+
+```yaml
+filename: analyzer.py
+functions:
+ - name: analyze
+ arguments:
+ - name: self
+ - name: data
+ annotation: list[str]
+ - name: verbose
+ annotation: bool
+ return_annotation: dict
+deployments:
+ - arch: x86_64
+ systems:
+ - os: linux
+ pythons:
+ - version: 3.12
+ interpreter: CPython
+```
+
+---
+
+## Class With Attributes and Methods
+
+This contract defines a module that exposes a `TaskManager` class with attributes and a method.
+
+```yaml
+filename: manager.py
+classes:
+ - name: TaskManager
+ attributes:
+ - name: tasks
+ annotation: list[str]
+ - name: state
+ annotation: str
+ methods:
+ - name: reset
+ arguments:
+ - name: self
+ return_annotation: None
+deployments:
+ - arch: arm64
+ systems:
+ - os: darwin
+ environment:
+ variables:
+ - name: MODE
+ value: development
+ annotation: str
+ pythons:
+ - version: 3.11
+ interpreter: CPython
+```
+
+---
+
+## Runtime-Only Validation
+
+This contract enforces only environmental and interpreter constraints — no structural validation.
+
+```yaml
+deployments:
+ - arch: x86_64
+ systems:
+ - os: linux
+ pythons:
+ - version: 3.10
+ interpreter: CPython
+```
+
+---
+
+## Multiple Deployments
+
+If your module supports multiple platforms, you can define multiple `deployments`.
+
+```yaml
+filename: plugin.py
+classes:
+ - name: Plugin
+ methods:
+ - name: run
+ arguments:
+ - name: self
+deployments:
+ - arch: x86_64
+ systems:
+ - os: linux
+ pythons:
+ - version: 3.11
+ interpreter: CPython
+ - arch: arm64
+ systems:
+ - os: darwin
+ pythons:
+ - version: 3.12
+ interpreter: CPython
+```
+
+---
+
+## Using Environment Variables
+
+This example enforces that an environment variable is set and has a given type.
+
+```yaml
+filename: checker.py
+deployments:
+ - arch: x86_64
+ systems:
+ - os: linux
+ environment:
+ variables:
+ - name: DEBUG
+ value: "true"
+ annotation: str
+ pythons:
+ - version: 3.10
+ interpreter: CPython
+```
+
+---
+
+## Related Topics
+
+- [Contract Syntax](syntax.md)
+- [Violation System](../advanced/violations.md)
+- [SpyModel Architecture](../advanced/spymodel.md)
diff --git a/docs/contracts/syntax.md b/docs/contracts/syntax.md
new file mode 100644
index 0000000..ce65c89
--- /dev/null
+++ b/docs/contracts/syntax.md
@@ -0,0 +1,214 @@
+# Contract Syntax
+
+ImportSpy contracts are written in YAML and describe the **structure and runtime expectations** of a Python module. These contracts can be embedded or validated externally (e.g., in CI/CD), and act as **import-time filters** for enforcing compatibility and intent.
+
+A contract defines:
+- What variables, classes, and functions a module must expose
+- What runtime conditions are required (OS, architecture, Python version, etc.)
+- How strict or flexible the structure must be
+
+This document explains the full syntax supported in `.yml` contract files.
+
+---
+
+## Top-Level Structure
+
+Every `.yml` contract is structured as follows:
+
+```yaml
+filename: plugin.py
+version: "1.2.3"
+
+variables:
+ - name: MODE
+ value: production
+ annotation: str
+
+functions:
+ - name: initialize
+ arguments:
+ - name: self
+ - name: config
+ annotation: dict
+ return_annotation: None
+
+classes:
+ - name: Plugin
+ attributes:
+ - name: settings
+ value: default
+ annotation: dict
+ type: instance
+ methods:
+ - name: run
+ arguments:
+ - name: self
+ return_annotation: None
+ superclasses:
+ - BasePlugin
+
+deployments:
+ - arch: x86_64
+ systems:
+ - os: linux
+ environment:
+ variables:
+ - name: IMPORTSPY_ENABLED
+ value: true
+ annotation: bool
+ pythons:
+ - version: "3.11"
+ interpreter: CPython
+ modules:
+ - filename: plugin.py
+ version: "1.2.3"
+```
+
+---
+
+## Fields Explained
+
+### `filename`
+The name of the target Python file or module this contract applies to.
+
+### `version`
+Optional string that defines the expected version of the module. Can be used to pin specific builds or releases.
+
+---
+
+## `variables`
+
+Declares **global or module-level variables** the importer must provide.
+
+```yaml
+variables:
+ - name: DEBUG
+ value: true
+ annotation: bool
+```
+
+Each variable entry supports:
+- `name` (**required**): Variable name
+- `value` (optional): Expected value
+- `annotation` (optional): Expected type annotation as string (e.g., `"str"`, `"dict"`)
+
+---
+
+## `functions`
+
+Specifies required functions in the importing module.
+
+```yaml
+functions:
+ - name: load_config
+ arguments:
+ - name: path
+ annotation: str
+ return_annotation: dict
+```
+
+Each function supports:
+- `name`: The function's name
+- `arguments`: List of required arguments (each with optional `annotation`)
+- `return_annotation`: Expected return type (as string)
+
+---
+
+## `classes`
+
+Defines required class structures.
+
+```yaml
+classes:
+ - name: Plugin
+ attributes:
+ - name: config
+ annotation: dict
+ value: {}
+ type: instance
+ methods:
+ - name: execute
+ arguments:
+ - name: self
+ superclasses:
+ - BasePlugin
+```
+
+A class may include:
+- `name`: Class name
+- `attributes`: A list of attributes exposed by the class
+ - `type`: Can be `"instance"` or `"class"` to indicate attribute level
+- `methods`: Required method declarations (same format as top-level `functions`)
+- `superclasses`: Optional list of superclass names expected
+
+> Attributes are matched on name, annotation, and (if provided) value.
+
+---
+
+## `deployments`
+
+This section defines **runtime constraints** in which the import is valid. It allows validation based on:
+
+- Architecture
+- Operating system
+- Environment variables
+- Python version and interpreter
+- Declared modules
+
+```yaml
+deployments:
+ - arch: x86_64
+ systems:
+ - os: linux
+ environment:
+ variables:
+ - name: MODE
+ value: prod
+ annotation: str
+ pythons:
+ - version: "3.10"
+ interpreter: CPython
+ modules:
+ - filename: plugin.py
+```
+
+### Deployment fields:
+
+| Field | Description |
+|--------------|---------------------------------------------|
+| `arch` | CPU architecture (e.g. `x86_64`, `arm64`) |
+| `systems.os` | Operating system (e.g. `linux`, `windows`) |
+| `environment.variables` | Required runtime env variables |
+| `pythons.version` | Required Python version (string) |
+| `pythons.interpreter` | Interpreter (e.g., `CPython`) |
+| `modules` | Specific modules to check (with filename/version) |
+
+> Note: all conditions are **AND-combined** within a deployment block.
+
+---
+
+## Matching Rules
+
+ImportSpy uses a strict structural validator. Here are some notes:
+
+- Variables, functions, and methods are matched **by name**.
+- Annotations are matched **as plain strings** – no semantic typing or runtime evaluation.
+- If an annotation is omitted, it is **not enforced**.
+- Superclasses are checked only **by name**, not by inheritance tree resolution.
+
+---
+
+## Best Practices
+
+- Use consistent annotations: `"str"`, `"dict"`, `"list"`, etc.
+- Prefer matching exact function signatures for critical plugins
+- Define environment constraints only when needed (e.g., `IMPORTSPY_MODE=prod`)
+- Use `modules.filename` to enforce versioning in multi-plugin systems
+
+---
+
+## Related Sections
+
+- [Contract Examples](examples.md)
+- [SpyModel Architecture](../advanced/spymodel.md)
+- [Contract Violations](../errors/contract-violations.md)
diff --git a/docs/errors/contract-violations.md b/docs/errors/contract-violations.md
new file mode 100644
index 0000000..3d6c308
--- /dev/null
+++ b/docs/errors/contract-violations.md
@@ -0,0 +1,85 @@
+# Contract Violations
+
+When an import contract is not satisfied, **ImportSpy** blocks the import and raises a detailed error message.
+These violations are central to the library's purpose: enforcing predictable, secure, and valid module usage across Python runtimes.
+
+## How Violations Work
+
+Every time a module is imported using ImportSpy (either in **embedded** or **CLI** mode), the system performs deep introspection and validation checks.
+
+If something does not match the declared contract (`.yml`), ImportSpy will:
+
+1. **Capture the context** (e.g., `MODULE`, `CLASS`, `RUNTIME`, etc.)
+2. **Identify the type** of error:
+ - `missing`: required element is absent
+ - `mismatch`: expected vs actual values differ
+ - `invalid`: unexpected or disallowed value found
+3. **Generate a structured error message** including:
+ - a human-readable message
+ - exact label of the failing entity
+ - possible solutions or corrective actions
+
+These violations are raised as `ValueError`, but contain detailed introspection metadata under the hood.
+
+---
+
+## Error Categories
+
+ImportSpy organizes violations into **logical layers**, based on what is being validated:
+
+| Layer | Validator Class | Violation Raised |
+|-------------------|--------------------------|------------------------------------------|
+| Architecture/OS | `RuntimeValidator` | `RuntimeContractViolation` |
+| OS / Environment | `SystemValidator` | `SystemContractViolation` |
+| Python Interpreter| `PythonValidator` | `PythonContractViolation` |
+| Module File | `ModuleValidator` | `ModuleContractViolation` |
+| Class Structure | `ClassValidator` | `ModuleContractViolation (CLASS_CONTEXT)` |
+| Functions | `FunctionValidator` | `FunctionContractViolation` |
+| Variables / Args | `VariableValidator` | `VariableContractViolation` |
+
+Each of these violations inherits from `BaseContractViolation`, which provides:
+- A consistent interface for labeling (`.label()`)
+- Templated messages for each category
+- A `Bundle` object used to inject dynamic context into the error
+
+---
+
+## Error Message Anatomy
+
+A full ImportSpy violation message looks like this:
+
+```
+[MODULE] Expected variable `timeout: int` not found in `my_module.py`
+→ Please add the variable or update your contract.
+```
+
+Each message consists of:
+- `[CONTEXT]`: tells where the error occurred
+- **Label**: dynamically generated from the contract structure
+- **Expected/Actual**: shown for mismatch/invalid errors
+- **Solution**: human-readable advice from the YAML spec
+
+---
+
+## Debugging Tips
+
+- Use `-l DEBUG` when invoking ImportSpy via CLI to see exact comparison steps.
+- Violations are deterministic and reproducible. If one fails in CI, it will fail locally too.
+- You can inspect the violation context by capturing the `ValueError` and logging its message.
+
+---
+
+## 📋 Contract Violation Table
+
+Below is a comprehensive list of all possible error messages emitted by ImportSpy:
+
+--8<-- "errors/error_table.md"
+
+---
+
+## Related Topics
+
+- [Contract Syntax](../contracts/syntax.md)
+- [Embedded Mode](../modes/embedded.md)
+- [CLI Mode](../modes/cli.md)
+- [SpyModel Architecture](../advanced/spymodel.md)
diff --git a/docs/errors/error-table.md b/docs/errors/error-table.md
new file mode 100644
index 0000000..0760d81
--- /dev/null
+++ b/docs/errors/error-table.md
@@ -0,0 +1,10 @@
+| Category | Context | Error Message |
+|------------|---------------|---------------|
+| `missing` | `runtime` | The runtime `CPython 3.12` is declared but missing. Ensure it is properly defined and implemented. |
+| | `environment` | The environment variable `DEBUG` is declared but missing. Ensure it is properly defined and implemented. |
+| | `module` | The variable `plugin_name` in module `extension.py` is declared but missing. Ensure it is properly defined and implemented. |
+| | `class` | The method `run` in class `Plugin` is declared but missing. Ensure it is properly defined and implemented. |
+| `mismatch` | `runtime` | The runtime `CPython 3.12` does not match the expected value. Expected: `CPython 3.11`, Found: `CPython 3.12`. Check the value and update the contract or implementation accordingly. |
+| | `environment` | The environment variable `LOG_LEVEL` does not match the expected value. Expected: `'INFO'`, Found: `'DEBUG'`. Check the value and update the contract or implementation accordingly. |
+| | `class` | The class attribute `engine` in class `Extension` does not match the expected value. Expected: `'docker'`, Found: `'podman'`. Check the value and update the contract or implementation accordingly. |
+| `invalid` | `class` | The argument `msg` of method `send` has an invalid value. Allowed values: `[str, None]`, Found: `42`. Update the value to one of the allowed options. |
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..44540e4
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,139 @@
+# ImportSpy
+
+**Context-aware import validation for Python modules**
+
+ImportSpy is an open-source Python library that introduces a robust mechanism to control and validate how and when modules are imported. At its core, it relies on versioned, declarative **import contracts** — written in YAML — which describe what a module expects from its execution context and its importer.
+
+It brings **modularity**, **predictability**, and **security** to Python ecosystems.
+
+---
+
+## What is an Import Contract?
+
+An import contract is a `.yml` file that defines:
+
+- The expected **structure** of a module: functions, classes, arguments, annotations, variables
+- The allowed **environments**: OS, architecture, Python version, interpreter
+- Optional conditions on runtime **environment variables** or **superclasses**
+
+If these conditions are not met, ImportSpy can stop the import and raise a detailed, structured error — before any runtime failure can occur.
+
+---
+
+## Key Features
+
+- YAML-based **import contracts**
+- Embedded and CLI-based **validation**
+- Structural validation of **variables**, **functions**, **classes**
+- Runtime checks for **OS**, **architecture**, **Python version**, **interpreter**
+- Contract-driven plugin validation for **secure extensibility**
+- Clear, explainable **error reporting** on mismatch, missing, or invalid usage
+- Fully integrable in **CI/CD pipelines**
+
+---
+
+## Use Cases
+
+ImportSpy is built for teams that need:
+
+- **Plugin-based architectures** with strict interface enforcement
+- **Runtime protection** against incompatible environments
+- Early validation in **DevSecOps** or **regulatory** pipelines
+- Defensive boundaries between **internal components**
+- **Automated structure verification** during deployment
+
+---
+
+## Example: Embedded Mode
+
+```python
+from importspy import Spy
+
+caller = Spy().importspy(filepath="contracts/spymodel.yml")
+caller.MyPlugin().run()
+```
+
+---
+
+## Example: CLI Mode
+
+```bash
+importspy src/mymodule.py -s contracts/spymodel.yml --log-level DEBUG
+```
+
+---
+
+## Project Structure
+
+ImportSpy is built around 3 key components:
+
+- `SpyModel`: represents the structural and runtime definition of a module
+- `Spy`: the validation engine that compares real vs expected modules
+- `Violation System`: formal system for raising errors with human-readable messages
+
+---
+
+## Documentation Overview
+
+### 👣 Get Started
+
+- [Quickstart](intro/quickstart.md)
+- [Install](intro/install.md)
+- [Overview](intro/overview.md)
+
+### ⚙️ Modes of Operation
+
+- [Embedded Mode](modes/embedded.md)
+- [CLI Mode](modes/cli.md)
+
+### 📄 Import Contracts
+
+- [Contract Syntax](contracts/syntax.md)
+- [SpyModel Specification](advanced/spymodel.md)
+
+### 🧠 Validation Engine
+
+- [Violation System](advanced/violations.md)
+- [Contract Violations](errors/contract-violations.md)
+- [Error Table](errors/error-table.md)
+
+### 📦 Use Cases
+
+- [Plugin-based Architectures](use_cases/index.md)
+
+### 📘 API Reference
+
+- [API Docs](api-reference.md)
+
+---
+
+## Architecture Diagram
+
+
+
+---
+
+## Why ImportSpy?
+
+Python’s import system is powerful, but not context-aware. ImportSpy solves this by adding a **layer of structural governance** and **runtime filtering**.
+
+This makes it ideal for:
+
+- Plugin systems
+- Isolated runtimes
+- Package compliance
+- Security-aware applications
+- CI enforcement of expected module interfaces
+
+---
+
+## Sponsorship & Community
+
+If ImportSpy is useful in your infrastructure, help us grow by:
+
+- [Starring the project on GitHub](https://github.com/your-org/importspy)
+- [Becoming a GitHub Sponsor](https://github.com/sponsors/your-org)
+
+---
+
+> ImportSpy is more than a validator — it's a contract of trust between Python modules.
diff --git a/docs/intro/install.md b/docs/intro/install.md
new file mode 100644
index 0000000..0b9e24a
--- /dev/null
+++ b/docs/intro/install.md
@@ -0,0 +1,46 @@
+# Installation
+
+You can install ImportSpy directly from [PyPI](https://pypi.org/project/importspy/) using `pip`.
+
+---
+
+## Basic installation
+
+```bash
+pip install importspy
+```
+
+This installs both the core runtime and the command-line interface (CLI), allowing you to use ImportSpy in both **Embedded Mode** and **CLI Mode**.
+
+---
+
+## Minimum requirements
+
+- **Python 3.8 or higher**
+- Supported operating systems:
+ - Linux
+ - macOS
+ - Windows
+- Compatible with CPython and alternative interpreters (e.g. IronPython), if declared in the contract
+
+---
+
+## Updating ImportSpy
+
+To upgrade to the latest version:
+
+```bash
+pip install --upgrade importspy
+```
+
+---
+
+## Verify installation
+
+To confirm that ImportSpy is correctly installed and the CLI is available:
+
+```bash
+importspy --help
+```
+
+You should see the full list of command-line options and usage instructions.
diff --git a/docs/intro/overview.md b/docs/intro/overview.md
new file mode 100644
index 0000000..a9f8892
--- /dev/null
+++ b/docs/intro/overview.md
@@ -0,0 +1,74 @@
+# What is ImportSpy?
+
+**ImportSpy** is a Python library that brings context awareness to the most fragile point in the lifecycle of a module: its import.
+
+It lets developers declare explicit import contracts — versionable `.yml` files that describe under which conditions a module can be imported. These contracts are validated at runtime, ensuring that the importing environment (and optionally the importing module) matches the declared requirements.
+
+If the conditions are not satisfied, the import fails immediately, with a detailed and structured error message.
+
+---
+
+## Why use ImportSpy?
+
+Python offers no built-in mechanism to control how and where a module can be imported. In modular systems, plugin frameworks, and regulated environments, this leads to:
+
+- Unexpected runtime errors
+- Hard-to-diagnose misconfigurations
+- Fragile CI/CD workflows
+
+ImportSpy solves this by introducing **import-time validation** based on:
+
+- Runtime environment (OS, CPU, Python version, interpreter)
+- Required environment variables and secret presence
+- Structural expectations of the importing module (classes, methods, types…)
+
+This results in safer, more predictable imports — whether you're building a plugin system, enforcing architectural rules, or protecting critical components.
+
+---
+
+## How does it work?
+
+ImportSpy uses a **declarative import contract** — a `.yml` file written by the developer — to define expected conditions.
+
+At runtime, this contract is parsed into a structured Python object called a **SpyModel**, which is used internally for validation.
+
+Depending on the operation mode (Embedded or CLI), ImportSpy:
+
+- Validates the runtime and system environment
+- Optionally inspects the module that is importing the protected one
+- Enforces declared constraints on structure, type annotations, variable values, and more
+
+If all constraints are respected, the import succeeds.
+If not, a descriptive error is raised (e.g. `ValueError`, `ImportSpyViolation`).
+
+---
+
+## Key features
+
+- ✅ Declarative, versionable `.yml` import contracts
+- 🧠 Runtime validation of OS, CPU architecture, Python version/interpreter
+- 🔍 Structural checks on the importing module (classes, methods, attributes, types)
+- 🔐 Validation of required environment variables and secrets (presence only)
+- 🚀 Dual operation modes: Embedded Mode and CLI Mode
+- 🧪 Full integration with CI/CD pipelines
+- 📋 Structured, human-readable error reports with actionable messages
+
+---
+
+## Who is it for?
+
+ImportSpy is designed for:
+
+- Plugin frameworks that enforce structural compliance
+- Systems with strict version, runtime or security constraints
+- Teams building modular Python applications
+- Projects that need to validate import-time compatibility
+- Open-source maintainers looking to define and enforce import boundaries
+
+---
+
+## What’s next?
+
+- [→ Install ImportSpy](install.md)
+- [→ Try a minimal working example](quickstart.md)
+- [→ Learn about the operation modes](../modes/embedded.md)
diff --git a/docs/intro/quickstart.md b/docs/intro/quickstart.md
new file mode 100644
index 0000000..93c984f
--- /dev/null
+++ b/docs/intro/quickstart.md
@@ -0,0 +1,98 @@
+# Quickstart
+
+This quickstart shows how to use **ImportSpy in Embedded Mode** to protect a Python module from being imported in an invalid context.
+
+---
+
+## Step 1 — Install ImportSpy
+
+If you haven’t already:
+
+```bash
+pip install importspy
+```
+
+---
+
+## Step 2 — Create a contract (`spymodel.yml`)
+
+This file defines the conditions under which your module can be imported.
+For example, it can require specific Python versions, operating systems, or structure in the calling module.
+
+```yaml
+filename: plugin.py
+classes:
+ - name: Plugin
+ methods:
+ - name: run
+ arguments:
+ - name: self
+ return_annotation:
+deployments:
+ - arch: x86_64
+ systems:
+ - os: linux
+ pythons:
+ - version: 3.12
+ interpreter: CPython
+```
+
+Save this file as `spymodel.yml`.
+
+---
+
+## Step 3 — Protect your module
+
+Here’s how to use ImportSpy inside the module you want to protect (e.g. `plugin.py`):
+
+```python
+# plugin.py
+from importspy import Spy
+
+caller = Spy().importspy(filepath="spymodel.yml")
+
+# Call something from the importer (for example)
+caller.MyPlugin().run()
+```
+
+This checks the current environment and the module that is importing `plugin.py`.
+If it doesn’t match the contract, ImportSpy raises an error and blocks the import.
+
+---
+
+## Step 4 — Create an importer
+
+Write a simple module that tries to import `plugin.py`.
+
+```python
+# main.py
+class MyPlugin:
+ def run(self):
+ print("Plugin running")
+
+import plugin
+```
+
+---
+
+## Step 5 — Run it
+
+If the environment and structure of `main.py` match the contract, the import will succeed:
+
+```bash
+python main.py
+```
+
+Otherwise, you'll get a clear and structured error like:
+
+```
+[Structure Violation] Missing required class 'Plugin' in caller module.
+```
+
+---
+
+## Next steps
+
+- Learn more about [Embedded Mode](../modes/embedded.md)
+- Explore [CLI Mode](../modes/cli.md) for validating modules from the outside
+- Dive into [contract syntax](../contracts/syntax.md) to write more advanced rules
diff --git a/docs/make.bat b/docs/make.bat
deleted file mode 100644
index 747ffb7..0000000
--- a/docs/make.bat
+++ /dev/null
@@ -1,35 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=source
-set BUILDDIR=build
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
- echo.
- echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
- echo.installed, then set the SPHINXBUILD environment variable to point
- echo.to the full path of the 'sphinx-build' executable. Alternatively you
- echo.may add the Sphinx directory to PATH.
- echo.
- echo.If you don't have Sphinx installed, grab it from
- echo.https://www.sphinx-doc.org/
- exit /b 1
-)
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
-
-:end
-popd
diff --git a/docs/modes/cli.md b/docs/modes/cli.md
new file mode 100644
index 0000000..25515e7
--- /dev/null
+++ b/docs/modes/cli.md
@@ -0,0 +1,276 @@
+# 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
+
+```bash
+importspy extensions.py -s spymodel.yml -l WARNING
+```
+
+### CLI options
+
++-----------------------------------------------------------------------------+
+| Flag | Description |
+|--------------------|--------------------------------------------------------|
+| `-s, --spymodel` | Path to the import contract `.yml` file |
+| `-l, --log-level` | Logging verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` |
+| `-v, --version` | Show ImportSpy version |
+| `--help` | Show help 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
+
+=== "extensions.py"
+
+```python
+--8<-- "examples/plugin_based_architecture/pipeline_validation/extensions.py"
+```
+
+=== "spymodel.yml"
+
+```yaml
+--8<-- "examples/plugin_based_architecture/pipeline_validation/spymodel.yml"
+```
+
+### 🔍 Run validation
+
+```bash
+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
+
+!!! tip "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:
+
+```yaml
+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
+
+```yaml
+filename: extension.py
+```
+
+- Optional.
+- Declares the filename of the module being validated.
+- Used for reference and filtering in multi-module declarations.
+
+---
+
+## 🔣 variables
+
+```yaml
+variables:
+ - name: engine
+ value: docker
+```
+
+- Declares top-level variables that must be present in the importing module.
+- Supports optional `annotation` (type hint).
+
+```yaml
+ - name: debug
+ annotation: bool
+ value: true
+```
+
+---
+
+## 🧠 functions
+
+```yaml
+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
+
+```yaml
+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.
+
+```yaml
+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.
+
+```yaml
+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
+
+- [Contract syntax](../contracts/syntax.md)
+- [Contract violations](../errors/contract-violations.md)
diff --git a/docs/modes/embedded.md b/docs/modes/embedded.md
new file mode 100644
index 0000000..c5a2d88
--- /dev/null
+++ b/docs/modes/embedded.md
@@ -0,0 +1,74 @@
+# Embedded Mode
+
+In Embedded Mode, ImportSpy is embedded directly into the module you want to protect.
+When that module is imported, it inspects the runtime environment and the importing module.
+If the context doesn't match the declared contract, the import fails with a structured error.
+
+---
+
+## How it works
+
+By using `Spy().importspy(...)`, a protected module can validate:
+
+- The **runtime** (OS, Python version, architecture…)
+- The **caller module’s structure** (classes, methods, variables, annotations…)
+
+If validation passes, the module returns a reference to the caller.
+If not, the import is blocked and an exception is raised (e.g. `ValueError` or custom error class).
+
+---
+
+## Real-world example: plugin-based architecture
+
+Let’s walk through a complete example.
+This simulates a plugin framework that wants to validate the structure of external plugins at import time.
+
+### Project structure
+
+```
+external_module_compliance/
+├── extensions.py # The plugin (caller)
+├── package.py # The protected framework
+├── plugin_interface.py # Base interface for plugins
+└── spymodel.yml # The import contract
+```
+
+---
+
+### 🧩 Source files
+
+=== "package.py"
+
+```python
+--8<-- "examples/plugin_based_architecture/external_module_compliance/package.py"
+```
+
+=== "extensions.py"
+
+```python
+--8<-- "examples/plugin_based_architecture/external_module_compliance/extensions.py"
+```
+
+=== "spymodel.yml"
+
+```yaml
+--8<-- "examples/plugin_based_architecture/external_module_compliance/spymodel.yml"
+```
+
+---
+
+## When to use Embedded Mode
+
+Use this mode when:
+
+- You want to **protect a module** from being imported incorrectly
+- You’re building a **plugin system** and expect structural consistency from plugins
+- You want to **fail fast** in invalid environments
+- You need to enforce custom logic during `import` without modifying the caller
+
+---
+
+## Learn more
+
+- [Contract syntax](../contracts/syntax.md)
+- [Contract violations](../errors/contract-violations.md)
diff --git a/docs/overrides/extra.html b/docs/overrides/extra.html
new file mode 100644
index 0000000..d9dfd47
--- /dev/null
+++ b/docs/overrides/extra.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index 1777ce4..0000000
--- a/docs/requirements.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-sphinx>=8.1.3
-sphinx-tabs>=3.4.7
-furo>=2024.8.6
-sphinx-basic-ng>=1.0.0b2
-sphinxcontrib-applehelp>=2.0.0
-sphinxcontrib-devhelp>=2.0.0
-sphinxcontrib-htmlhelp>=2.1.0
-sphinxcontrib-jsmath>=1.0.1
-sphinxcontrib-qthelp>=2.0.0
-sphinxcontrib-serializinghtml>=2.0.0
diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css
deleted file mode 100644
index 616a96f..0000000
--- a/docs/source/_static/custom.css
+++ /dev/null
@@ -1,10 +0,0 @@
-.wy-nav-content {
- max-width: 100% !important;
- width: 100% !important;
-}
-/*
-
-.wy-side-nav-search {
- max-width: 20% !important;
-}
-*/
\ No newline at end of file
diff --git a/docs/source/advanced/advanced_index.rst b/docs/source/advanced/advanced_index.rst
deleted file mode 100644
index 8eb3642..0000000
--- a/docs/source/advanced/advanced_index.rst
+++ /dev/null
@@ -1,43 +0,0 @@
-Advanced Topics & Internals of ImportSpy
-========================================
-
-Welcome to the advanced section of ImportSpy’s documentation — built for developers, integrators, and contributors who want to **go beyond usage** and dive into **how ImportSpy works under the hood**.
-
-Whether you're building runtime enforcement pipelines, customizing structural validators, or embedding ImportSpy into multi-tenant plugin architectures, this section provides the **deep technical foundation** to unlock ImportSpy's full capabilities.
-
-🧠 Who This Is For
--------------------
-
-- Engineers building **custom validation flows**
-- Contributors exploring the **internal mechanics** of ImportSpy
-- Teams integrating ImportSpy into **CI/CD, containers, and plugin frameworks**
-- Architects enforcing **organization-wide import policies**
-
-🔍 What You’ll Explore
------------------------
-
-This section is structured into two complementary areas:
-
-🏗️ **Architectural Internals**
- - A deep technical exploration of ImportSpy’s runtime model, validation stack, and modular design.
- - Learn how ImportSpy inspects environments, builds validation contexts, and enforces contracts in both embedded and CLI modes.
-
-🛠️ **Extension & Integration Points**
- - Discover how to write custom validators, extend `SpyModel`, inject runtime policies, or build tooling on top of ImportSpy’s API.
- - Ideal for integrating with internal frameworks, policy engines, or advanced CI/CD pipelines.
-
-📚 **API Reference**
- - Browse a fully documented catalog of internal components:
- - `SpyModel`, `Function`, `Attribute`, `Deployment`, `Validator`, etc.
- - Includes type annotations, usage patterns, and extension strategies.
-
-This section balances **low-level documentation** with **real-world extensibility guidance**.
-
-.. toctree::
- :maxdepth: 2
- :caption: Explore the Internals
-
- architecture_index
- api_reference_index
-
-🚀 Whether you're enforcing security boundaries or writing custom validators, this section is your blueprint for building with — and on top of — ImportSpy.
diff --git a/docs/source/advanced/api_reference/api_core.rst b/docs/source/advanced/api_reference/api_core.rst
deleted file mode 100644
index afab174..0000000
--- a/docs/source/advanced/api_reference/api_core.rst
+++ /dev/null
@@ -1,104 +0,0 @@
-Core Engine: Classes, Controllers, and Contracts
-================================================
-
-This section documents the **core subsystems of ImportSpy** — the internal machinery that powers its runtime validation engine, CLI interface, and contract execution model.
-
-These APIs are essential for:
-
-- 🧠 Understanding how validation requests are orchestrated
-- 🔄 Hooking into the enforcement lifecycle
-- 🛠 Extending ImportSpy for custom validation, logging, or policy enforcement
-
-Each class below plays a **central role in ImportSpy’s internal flow**, and is fully documented for integration and contribution use cases.
-
-Spy Class 🕵️♂️
-^^^^^^^^^^^^^^^^^
-
-The `Spy` class is the **central controller** of ImportSpy’s validation pipeline.
-
-It handles:
-
-- Contract parsing and validation
-- Import-time introspection of the calling environment
-- Runtime orchestration for embedded validation
-- Entry point for dynamic execution enforcement
-
-.. autoclass:: importspy.s.Spy
- :members:
- :undoc-members:
- :show-inheritance:
-
-Contract Parsers 💾
-^^^^^^^^^^^^^^^^^^^^
-
-Import contracts are defined externally as `.yml` files and parsed into structured models.
-
-- `Parser` is the abstract interface that defines contract loading behavior.
-- `YamlParser` is the default parser implementation supporting YAML-based contracts.
-- `handle_persistence_error` is a decorator for consistent exception wrapping and traceability.
-
-.. autoclass:: importspy.persistences.Parser
- :members:
- :undoc-members:
- :show-inheritance:
-
-.. autoclass:: importspy.persistences.YamlParser
- :members:
- :undoc-members:
- :show-inheritance:
-
-.. autofunction:: importspy.persistences.handle_persistence_error
-
-.. autoclass:: importspy.persistences.PersistenceError
- :members:
- :undoc-members:
- :show-inheritance:
-
-Log Manager 📝
-^^^^^^^^^^^^^^^
-
-The `LogManager` provides **structured logging** across both CLI and embedded modes.
-It supports log-level control (`DEBUG`, `INFO`, `ERROR`, etc.) and unified message formatting.
-
-.. autoclass:: importspy.log_manager.LogManager
- :members:
- :undoc-members:
- :show-inheritance:
-
-Error Messaging ⚠️
-^^^^^^^^^^^^^^^^^^^^
-
-The `Errors` class contains standardized error templates used across ImportSpy.
-It defines **consistent, user-facing messages** for contract violations, misconfigurations, and environment mismatches.
-
-.. autoclass:: importspy.errors.Errors
- :members:
- :undoc-members:
- :show-inheritance:
-
-Constants 📌
-^^^^^^^^^^^^
-
-All shared constants, labels, and tags used by the validation engine, parsers, and CLI.
-These are used to maintain **naming consistency** across internal modules.
-
-.. autoclass:: importspy.constants.Constants
- :members:
- :undoc-members:
- :show-inheritance:
-
-Configuration ⚙️
-^^^^^^^^^^^^^^^^
-
-The `Config` class defines **runtime settings and execution context** for ImportSpy.
-
-It allows developers to:
-
-- Customize behavior based on validation mode
-- Register external contract paths
-- Modify enforcement flags dynamically
-
-.. autoclass:: importspy.config.Config
- :members:
- :undoc-members:
- :show-inheritance:
diff --git a/docs/source/advanced/api_reference/api_models.rst b/docs/source/advanced/api_reference/api_models.rst
deleted file mode 100644
index 00334a8..0000000
--- a/docs/source/advanced/api_reference/api_models.rst
+++ /dev/null
@@ -1,106 +0,0 @@
-Model Layer: SpyModel & Contract Validation System
-==================================================
-
-At the heart of ImportSpy’s compliance framework lies the `SpyModel`:
-a fully structured representation of how a Python module **should behave** across different runtime environments.
-
-ImportSpy does not merely analyze code — it validates whether a module conforms to a **contractual definition**
-that includes its **structure**, **runtime expectations**, and **execution constraints**.
-
-🔍 Whether you’re operating in **embedded mode** or running validations via the **CLI**,
-`SpyModel` is the foundation on which all validation logic is built.
-
-Validation Modes Supported 🧭
------------------------------
-
-Import contracts defined via `.yml` files (or SpyModel objects in code) are evaluated in:
-
-- **Embedded Mode** 🔌
- Modules protect themselves by invoking `Spy().importspy()` and enforcing contracts on their importers.
-
-- **External (CLI) Mode** 🛠️
- Used in pipelines or audits to validate a target module before execution or integration.
-
-Both workflows rely on **SpyModel-based comparison** between expected and actual module states.
-
-SpyModel Class 🏗️
--------------------
-
-The `SpyModel` is a high-level, Pydantic-based model that transforms an import contract into a validated runtime object.
-
-It defines:
-
-- 🧱 Structural rules → Expected classes, attributes, methods, return types
-- 🧪 Runtime rules → Supported OS, CPU architectures, Python interpreters
-- 🔐 Environment rules → Required environment variables and submodule dependencies
-
-.. autoclass:: importspy.models.SpyModel
- :members:
- :undoc-members:
- :show-inheritance:
-
-Model Subcomponents 📦
-^^^^^^^^^^^^^^^^^^^^^^^^
-
-`SpyModel` is composed of granular submodels that represent the contract’s declarative schema:
-
-- `Function` → Represents function name, arguments, and return annotations
-- `Attribute` → Captures class or global variables (with value, type, and scope)
-- `Class` → Groups attributes and methods, along with expected inheritance
-- `Module` → Represents nested modules inside deployments
-- `Python`, `System`, `Deployment` → Define runtime matrix for cross-platform validation
-
-.. autoclass:: importspy.models.Function
- :members:
- :undoc-members:
-
-.. autoclass:: importspy.models.Attribute
- :members:
- :undoc-members:
-
-.. autoclass:: importspy.models.Argument
- :members:
- :undoc-members:
-
-.. autoclass:: importspy.models.Class
- :members:
- :undoc-members:
-
-.. autoclass:: importspy.models.Module
- :members:
- :undoc-members:
-
-.. autoclass:: importspy.models.Python
- :members:
- :undoc-members:
-
-.. autoclass:: importspy.models.System
- :members:
- :undoc-members:
-
-.. autoclass:: importspy.models.Runtime
- :members:
- :undoc-members:
-
-Validator Interface ✅
-----------------------
-
-ImportSpy includes a pluggable validator system that compares:
-
-- The `SpyModel` contract (expected)
-- The actual runtime snapshot of the importing environment
-
-Validators are executed as part of a pipeline that checks:
-
-- ✔️ Function and method presence
-- ✔️ Signature alignment and argument types
-- ✔️ Class structure and attribute correctness
-- ✔️ Deployment compatibility and environment config
-- ✔️ Interpreter and version compliance
-
-To explore how validators are defined and chained:
-
-.. toctree::
- :maxdepth: 1
-
- api_validators
diff --git a/docs/source/advanced/api_reference/api_utilities.rst b/docs/source/advanced/api_reference/api_utilities.rst
deleted file mode 100644
index 2002420..0000000
--- a/docs/source/advanced/api_reference/api_utilities.rst
+++ /dev/null
@@ -1,114 +0,0 @@
-Utilities & Mixins: Internal Tools for Reflection and Runtime Enforcement
-=========================================================================
-
-ImportSpy’s validation engine is powered by a suite of **utility classes** and **mixins**
-that enable deep introspection of modules, environments, and Python runtimes.
-These components provide the **mechanical backbone** for runtime analysis, structural extraction,
-and platform compatibility checks across both **embedded** and **external** validation modes.
-
-This layer is not typically exposed to end-users—but is invaluable for contributors,
-integrators, and advanced developers extending ImportSpy’s logic or building custom tooling.
-
-Utility Modules ⚙️
-------------------
-
-Each utility module encapsulates a specific aspect of **runtime introspection**, enabling:
-
-- 🔍 Extraction of class/function signatures, annotations, and globals
-- 🧠 Detection of system identity (OS, architecture, interpreter, etc.)
-- 🔐 Compliance checks for cross-platform and interpreter-specific constraints
-- ⚙️ Lightweight, cached evaluation of runtime conditions
-
-These utilities are **orchestrated automatically** by the validation pipeline but can also
-be used independently to write tools or perform standalone validations.
-
-ModuleUtil – Structural Reflection 🧱
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This utility performs deep reflection on a Python module, extracting:
-
-- Public/private classes
-- Methods and their argument signatures
-- Attribute values and types
-- Class hierarchies and superclasses
-- Function return annotations
-
-.. autoclass:: importspy.utilities.module_util.ModuleUtil
- :members:
- :undoc-members:
- :show-inheritance:
-
-SystemUtil – OS & Environment Detection 🌐
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Gathers platform metadata such as:
-
-- OS name (`linux`, `windows`, etc.)
-- Hostname, architecture
-- Environment variable inspection and resolution
-
-.. autoclass:: importspy.utilities.system_util.SystemUtil
- :members:
- :undoc-members:
- :show-inheritance:
-
-PythonUtil – Interpreter Validation 🐍
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Identifies the active interpreter and Python version with semantic normalization.
-Also verifies whether the environment satisfies version-based or interpreter-based constraints
-declared in the import contract.
-
-.. autoclass:: importspy.utilities.python_util.PythonUtil
- :members:
- :undoc-members:
- :show-inheritance:
-
-RuntimeUtil – Hardware & Architecture Awareness 🧬
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Provides a detailed overview of:
-
-- CPU architecture (`x86_64`, `arm64`, etc.)
-- Runtime compatibility with deployment targets
-- Cross-architecture filtering logic for contract enforcement
-
-.. autoclass:: importspy.utilities.runtime_util.RuntimeUtil
- :members:
- :undoc-members:
- :show-inheritance:
-
-Mixin Components 🔁
--------------------
-
-ImportSpy also uses **mixins** to modularize logic that applies across validators and inspectors
-without duplicating functionality or creating deep class hierarchies.
-
-These reusable components inject specialized logic into validation classes where needed.
-
-AnnotationValidatorMixin 🏷️
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Ensures that function signatures and variable annotations match the declared expectations.
-It supports:
-
-- Basic type matching (`str`, `int`, etc.)
-- Optional and generic annotations
-- Graceful fallback for missing or untyped values
-
-.. autoclass:: importspy.mixins.annotations_validator_mixin.AnnotationValidatorMixin
- :members:
- :undoc-members:
- :show-inheritance:
-
-📌 Tip:
--------
-
-While these components are internal, they can be extended or overridden when customizing
-ImportSpy’s validation strategy for highly specific use cases (e.g., custom deployment platforms,
-corporate runtime wrappers, or secure import enforcement).
-
-For more on customizing validation behavior, see:
-
-- :doc:`../architecture/architecture_design_decisions`
-- :doc:`api_validators`
diff --git a/docs/source/advanced/api_reference/api_validators.rst b/docs/source/advanced/api_reference/api_validators.rst
deleted file mode 100644
index 39384f6..0000000
--- a/docs/source/advanced/api_reference/api_validators.rst
+++ /dev/null
@@ -1,154 +0,0 @@
-The Validation Engine
-=====================
-
-ImportSpy’s validation system is a modular, extensible framework designed to enforce the integrity
-and runtime compatibility of any Python module protected by an **Import Contract**.
-Whether used in **embedded validation** (inside the module) or in **external CLI mode**,
-this engine guarantees that no module is loaded in an unauthorized or structurally inconsistent environment.
-
-Core Validator Pipeline ⚙️
---------------------------
-
-At the center of the engine is the `SpyModelValidator`, a coordinator that orchestrates multiple specialized validators.
-Each validator is responsible for comparing part of the actual runtime context or module structure against its declared expectations.
-
-This pipeline enforces:
-
-- ✅ Structural integrity
-- ✅ Environment compatibility
-- ✅ Runtime reproducibility
-- ✅ Compliance with declared variables and system settings
-
-.. note::
- All validators are executed **at runtime**, just before the module is made accessible to the importing code.
-
-Validation Modes Supported
----------------------------
-
-The validation engine operates seamlessly across both execution modes:
-
-- **🧬 Embedded Mode** — The module validates its own importer at the moment it is loaded.
-- **🛠️ External Mode (CLI)** — The module is validated *before* execution begins, often in CI/CD or static validation workflows.
-
-SpyModelValidator 🧠
----------------------
-
-This is the **entry point** to the validation pipeline. It receives both:
-
-- The expected structure (parsed from a `.yml` contract or inline SpyModel), and
-- The actual runtime data (extracted via introspection)
-
-It dispatches these to domain-specific validators, collects results, and raises structured errors on failure.
-
-.. autoclass:: importspy.validators.spymodel_validator.SpyModelValidator
- :members:
- :undoc-members:
- :show-inheritance:
-
-Structural Validators 🔎
-------------------------
-
-These validators inspect the internal structure of the target module:
-
-AttributeValidator 🔤
-^^^^^^^^^^^^^^^^^^^^^
-
-- Ensures global/module/class attributes exist
-- Validates `type`, `value`, and `scope` (class vs instance)
-- Supports default values and optional annotations
-
-.. autoclass:: importspy.validators.attribute_validator.AttributeValidator
- :members:
- :undoc-members:
- :show-inheritance:
-
-FunctionValidator 🛠️
-^^^^^^^^^^^^^^^^^^^^^
-
-- Verifies function/method presence
-- Checks signatures and return annotations
-- Detects function mismatches across versions or overrides
-
-.. autoclass:: importspy.validators.function_validator.FunctionValidator
- :members:
- :undoc-members:
- :show-inheritance:
-
-ArgumentValidator 🎛️
-^^^^^^^^^^^^^^^^^^^^^^
-
-- Validates function arguments against contract declarations
-- Supports type annotations and default value checks
-- Ensures complete function interface compliance
-
-.. autoclass:: importspy.validators.argument_validator.ArgumentValidator
- :members:
- :undoc-members:
- :show-inheritance:
-
-Environment & Runtime Validators 🌍
------------------------------------
-
-These components validate that the system attempting to import the module is authorized:
-
-ModuleValidator 📦
-^^^^^^^^^^^^^^^^^^
-
-- Validates module metadata (`filename`, `version`, global `variables`)
-- Applies naming constraints defined in contracts
-
-.. autoclass:: importspy.validators.module_validator.ModuleValidator
- :members:
- :undoc-members:
- :show-inheritance:
-
-SystemValidator 🖥️
-^^^^^^^^^^^^^^^^^^^
-
-- Verifies OS name and version compatibility
-- Enforces required `env` variables (e.g., `API_KEY`, `DEPLOY_REGION`)
-- Useful for containerized and multi-host setups
-
-.. autoclass:: importspy.validators.system_validator.SystemValidator
- :members:
- :undoc-members:
- :show-inheritance:
-
-PythonValidator 🐍
-^^^^^^^^^^^^^^^^^^
-
-- Checks that the interpreter matches contract constraints
-- Supports semantic Python version matching
-- Verifies implementation type (`CPython`, `PyPy`, `IronPython`)
-
-.. autoclass:: importspy.validators.python_validator.PythonValidator
- :members:
- :undoc-members:
- :show-inheritance:
-
-RuntimeValidator 🚀
-^^^^^^^^^^^^^^^^^^^^
-
-- Validates CPU architecture (`x86_64`, `ARM64`, etc.)
-- Filters deployments that must only run on specific hardware targets
-- Useful for embedded devices, edge computing, and platform-specific plugins
-
-.. autoclass:: importspy.validators.runtime_validator.RuntimeValidator
- :members:
- :undoc-members:
- :show-inheritance:
-
-Extending the Engine 🧩
------------------------
-
-Want to add your own validator?
-
-- Subclass any validator listed here
-- Implement the `.validate()` interface
-- Register it manually via `SpyModelValidator` or contract-driven hooks
-
-This makes ImportSpy ideal for **internal compliance layers**, **custom rule sets**, or **secure enterprise environments**.
-
-See also:
-
-- :doc:`api_models` for understanding SpyModel structure
diff --git a/docs/source/advanced/api_reference_index.rst b/docs/source/advanced/api_reference_index.rst
deleted file mode 100644
index 8ca660e..0000000
--- a/docs/source/advanced/api_reference_index.rst
+++ /dev/null
@@ -1,45 +0,0 @@
-api_reference_index
-===================
-
-API Reference: Internals & Extensibility
-----------------------------------------
-
-This section provides a **complete reference guide** to ImportSpy's internal API — designed for developers and contributors who want to:
-
-- 🔍 Understand how ImportSpy operates under the hood
-- 🛠️ Extend its validation logic with custom models and validators
-- ⚙️ Integrate runtime enforcement into existing architectures
-
-Whether you're writing plugins, debugging structural mismatches, or integrating ImportSpy into a CI/CD pipeline, this reference exposes all the essential **building blocks** behind the framework.
-
-🧩 What You'll Find Inside
----------------------------
-
-🔹 **Core API**
- The runtime logic powering ImportSpy’s contract enforcement.
- Includes import interceptors, validation orchestration, and execution gating.
-
-🔹 **Model Layer**
- Formal Pydantic-based representations of everything from modules and attributes
- to Python interpreters, environments, and deployment matrices.
-
-🔹 **Utility Layer**
- Introspection helpers for analyzing imports, resolving dependencies,
- reading metadata, or reflecting on runtime state.
-
-📚 Each module in this section is fully documented with:
-- Class definitions
-- Method signatures
-- Expected behavior
-- Extension guidance
-- Real-world usage examples
-
-.. toctree::
- :maxdepth: 2
- :caption: API Modules
-
- api_reference/api_core
- api_reference/api_models
- api_reference/api_utilities
-
-🧠 Use this reference to go beyond configuration — and shape ImportSpy around your architecture, policies, and execution model.
diff --git a/docs/source/advanced/architecture/architecture_design_decisions.rst b/docs/source/advanced/architecture/architecture_design_decisions.rst
deleted file mode 100644
index f79d5e7..0000000
--- a/docs/source/advanced/architecture/architecture_design_decisions.rst
+++ /dev/null
@@ -1,150 +0,0 @@
-Design Principles Behind ImportSpy
-==================================
-
-ImportSpy was built to answer a fundamental question in dynamic Python environments:
-
-🔐 *"How can we guarantee that modules are imported only under the conditions they were designed for?"*
-
-Unlike traditional linters or static analyzers, ImportSpy enforces **live structural contracts** at the moment of import. This section details the architectural decisions that enable ImportSpy to operate securely, predictably, and scalably across both **runtime validation** and **automated pipelines**.
-
-Why Runtime Validation? 🧠
---------------------------
-
-Python is dynamic. That’s its strength—and its risk.
-
-Most tools operate **before execution** (e.g. `mypy`, `flake8`), but these tools can’t:
-
-- Detect runtime-only configurations (e.g. `os.environ`, `importlib`).
-- Block a plugin from loading in an unauthorized host.
-- Enforce interpreter or architecture constraints **at runtime**.
-
-✨ **ImportSpy validates code *as it’s being imported***—right where behavior matters.
-It defers enforcement to the **moment of execution**, where guarantees can be *proven*.
-
-Why Validate the Importing Environment? 🔄
-------------------------------------------
-
-ImportSpy inverts the typical validation direction.
-
-Instead of saying:
-
-> “This plugin must look like X.”
-
-It asks:
-
-> “Is the context trying to use this plugin *safe enough* to do so?”
-
-Modules don’t just exist—they **run somewhere**.
-By inspecting the **caller**, ImportSpy ensures:
-
-- Plugins are loaded only in verified systems.
-- The host respects the structure, env vars, interpreter, etc.
-- No unauthorized module can silently consume sensitive logic.
-
-📌 **This is fundamental to plugin security, cross-runtime compliance, and containerized deployments.**
-
-Why Declarative Import Contracts? 📜
-------------------------------------
-
-Validation logic shouldn’t live inside Python files.
-
-Instead, ImportSpy uses **external YAML contracts** that describe:
-
-- Expected runtime constraints (e.g. `os: linux`, `python: 3.12`)
-- Structural contracts (functions, classes, attributes)
-- Deployment variation (e.g. different rules for Windows vs Linux)
-
-These contracts are parsed into a runtime `SpyModel`—an internal abstraction built with Pydantic.
-
-Benefits:
-
-- ✅ Easy to version
-- ✅ Works across both embedded and CLI validation
-- ✅ Enables testing, linting, and reuse
-- ✅ No invasive logic in the codebase
-
-Why Python Introspection? 🔍
-----------------------------
-
-ImportSpy is powered by Python’s built-in runtime introspection features:
-
-- `inspect.stack()` to find the caller
-- `getmembers()`, `isfunction()`, `isclass()` to rebuild module structure
-- `sys`, `platform`, and `os` to gather system metadata
-
-This allows ImportSpy to:
-
-- Mirror the structure of any module
-- Dynamically analyze real-time execution state
-- Validate *what’s really there*—not what’s assumed
-
-By using native reflection instead of static assumptions, ImportSpy gains **flexibility and truthfulness**.
-
-Why Two Validation Modes? ⚙️
------------------------------
-
-ImportSpy supports two usage models:
-
-### 1. Embedded Mode (Validation from inside the module)
-
-- Great for plugins and reusable components
-- Self-protecting modules: reject imports from unverified hosts
-- Guarantees runtime safety at every import
-
-### 2. External Mode (Validation from CI/CD or CLI)
-
-- Ideal for pre-deployment validation
-- Works well in test automation, pipelines, and secure releases
-- Ensures modules are structurally valid before execution
-
-Both use the same contract schema.
-Both use the same engine.
-Both improve trust.
-
-Why Block on Failure? 🚫
-------------------------
-
-ImportSpy adopts a **zero-trust default**:
-
-- ❌ Invalid environment? → Raise `ValidationError`
-- ❌ Mismatched structure? → Abort import
-- ✅ Fully compliant? → Proceed as normal
-
-This prevents:
-
-- Unexpected side effects
-- Code being “partially valid”
-- Runtime surprises in production
-
-Errors are detailed, categorized, and explain *what failed, where, and why*.
-
-Performance Tradeoffs & Optimizations 🧮
-----------------------------------------
-
-Runtime validation adds overhead. But ImportSpy minimizes it through:
-
-- 🧠 **Caching** of introspection results
-- 🔍 **Selective analysis** (skip unused layers)
-- 🧱 **Lazy evaluation** of module components
-- 📉 **Short-circuiting** at first contract breach
-
-Result: validation is fast enough for real-time enforcement—even inside plugins.
-
-Core Design Principles 🧭
---------------------------
-
-These ideas guide ImportSpy’s architecture:
-
-- **Declarative-first** – Let contracts define validation, not Python logic.
-- **Zero-trust imports** – Always verify before executing.
-- **Context-aware validation** – Enforce structure *and* environment.
-- **Cross-environment readiness** – Design for CI, containers, local, and cloud.
-- **Developer ergonomics** – Errors are clear. Contracts are readable. Setup is fast.
-
-Next Steps 🔬
--------------
-
-To see these principles in action:
-
-- Dive into :doc:`architecture_runtime_analysis` → How runtime environments are captured
-- Or explore :doc:`architecture_validation_engine` → How actual validation decisions are made
diff --git a/docs/source/advanced/architecture/architecture_overview.rst b/docs/source/advanced/architecture/architecture_overview.rst
deleted file mode 100644
index 4e7cfdf..0000000
--- a/docs/source/advanced/architecture/architecture_overview.rst
+++ /dev/null
@@ -1,114 +0,0 @@
-Architecture Overview
-=====================
-
-ImportSpy is a structural validation engine for Python that operates across two distinct execution models: **embedded mode** and **external (CLI) mode**.
-Its architecture is designed to adapt seamlessly to both, providing a **runtime validation system** that enforces **import contracts**—declarative YAML specifications defining how and where a module is allowed to run.
-
-This section introduces the architectural layers, flows, and principles behind ImportSpy’s execution model.
-
-Architectural Objectives
-------------------------
-
-ImportSpy is built upon four core pillars:
-
-1. **Contract-Driven Validation**
- Modules define import contracts that describe the expected runtime and structural context.
-
-2. **Zero-Trust Execution Model**
- Code is never executed unless the importing or imported module complies with declared constraints.
-
-3. **Dynamic Runtime Enforcement**
- System context is reconstructed at runtime using reflection and introspection.
-
-4. **Composable Validation Layers**
- Validation is performed in discrete phases (structure, environment, runtime, interpreter), making the architecture modular and extensible.
-
-Supported Execution Modes
---------------------------
-
-ImportSpy is dual-mode by design:
-
-🔹 **Embedded Mode** (for modules that protect themselves)
-
-- Validation is triggered **inside** the protected module.
-- The module inspects **who is importing it** and verifies the caller’s structure and runtime context.
-- Typical use case: plugins that must ensure their importing host complies with an expected contract.
-
-🔹 **External Mode** (for CI/CD or static compliance pipelines)
-
-- Validation is triggered via CLI before execution.
-- The target module is validated **from the outside**, ensuring it conforms to its declared contract.
-- Typical use case: pipeline validation of Python modules before deployment.
-
-Both modes share the same validation engine and contract semantics but differ in the **direction** of the inspection (who validates whom).
-
-Architectural Layers
---------------------
-
-The architecture of ImportSpy can be decomposed into the following logical layers:
-
-🏗️ **Context Reconstruction Layer**
- - Gathers system information from the current runtime.
- - Captures OS, Python version, architecture, interpreter, and environment variables.
-
-🔁 **SpyModel Builder**
- - Builds a structured representation of the runtime or module to validate.
- - Converts contracts and runtime state into Pydantic models.
-
-📦 **Import Contract Loader**
- - Parses the YAML `.yml` contract into a typed validation model.
- - Supports nested structures, deployment variations, and type annotations.
-
-🔍 **Validation Pipeline**
- - Compares the reconstructed runtime or module state against the contract.
- - Handles structure (functions, classes), environment (variables), and system (interpreter, OS, arch).
-
-🔐 **Enforcement & Error Handling**
- - Raises structured exceptions on failure (with detailed error classification).
- - Blocks execution in embedded mode; returns exit codes in CLI mode.
-
-Execution Flow
---------------
-
-📌 Embedded Mode:
-
-1. Module executes `Spy().importspy(...)` at the top of its source.
-2. The call stack is inspected to identify the **caller module**.
-3. A `SpyModel` of the caller is reconstructed.
-4. The module’s own contract is loaded.
-5. If the caller matches the contract, execution continues.
-6. If not, a `ValidationError` is raised and execution is blocked.
-
-📌 External Mode:
-
-1. CLI is invoked with `importspy -s contract.yml my_module.py`.
-2. `my_module.py` is dynamically loaded and introspected.
-3. Its structure is extracted: classes, functions, attributes, variables.
-4. The YAML contract is parsed into a validation model.
-5. Structural and runtime validation is performed.
-6. Success → status code 0. Failure → detailed error message and exit code 1.
-
-Illustration:
-
-.. image:: https://raw.githubusercontent.com/atellaluca/ImportSpy/main/assets/importspy-architecture.png
- :align: center
- :alt: ImportSpy Architecture Overview
-
-Why This Architecture Matters
------------------------------
-
-This architecture provides:
-
-- ✅ Full control over **execution guarantees** of Python modules
-- ✅ Runtime enforcement of **environmental and structural policies**
-- ✅ Dual-mode support for **plugin protection and CI/CD validation**
-- ✅ A uniform validation model across **local, container, and distributed runtimes**
-
-What’s Next?
-------------
-
-Continue with:
-
-- :doc:`architecture_runtime_analysis` → How ImportSpy reconstructs runtime environments
-- :doc:`architecture_validation_engine` → The core validation logic and error system
-- :doc:`architecture_design_decisions` → Design trade-offs, limitations, and rationale
diff --git a/docs/source/advanced/architecture/architecture_runtime_analysis.rst b/docs/source/advanced/architecture/architecture_runtime_analysis.rst
deleted file mode 100644
index f4527e0..0000000
--- a/docs/source/advanced/architecture/architecture_runtime_analysis.rst
+++ /dev/null
@@ -1,125 +0,0 @@
-architecture_runtime_analysis
-=============================
-
-Understanding Runtime Analysis in ImportSpy
--------------------------------------------
-
-ImportSpy doesn’t guess — it **knows exactly who’s importing your code, from where, and how**.
-
-Its runtime analysis engine reconstructs the **real-time execution context** surrounding an import and evaluates whether it complies with the expectations declared in an **Import Contract**.
-
-This section explains how ImportSpy leverages Python's introspection system to **enforce validation dynamically**, across both embedded and CLI modes.
-
-🧠 What Makes ImportSpy Runtime-Aware?
----------------------------------------
-
-At the heart of ImportSpy is a fundamental insight:
-
-> A Python module isn’t just *defined* — it’s *executed in context.*
-
-ImportSpy inspects that context to answer questions like:
-
-- Who is importing this code?
-- Is the environment approved (OS, Python version, interpreter)?
-- Are expected environment variables and metadata in place?
-- Does the runtime architecture match the contract?
-
-Instead of assuming compliance, ImportSpy **validates the reality of execution** — and blocks code that violates it.
-
-🧱 Key Layers of Runtime Analysis
-----------------------------------
-
-Here’s how ImportSpy turns Python’s dynamic nature into a validation pipeline:
-
-1️⃣ **Call Stack Introspection**
- - Uses `inspect.stack()` to trace back to the module attempting the import.
- - Identifies the **caller module**, not just the callee.
-
-2️⃣ **Context Extraction**
- - Gathers system metadata, including:
- - OS (Linux, macOS, Windows)
- - CPU architecture (e.g. `x86_64`, `arm64`)
- - Python version and interpreter (CPython, PyPy, IronPython)
- - Environment variables (e.g., `API_KEY`, `STAGE`)
- - Installed module structure (classes, functions, globals)
-
-3️⃣ **SpyModel Construction**
- - Dynamically builds an internal `SpyModel` from the importing context.
- - Matches structure and environment against the import contract.
-
-4️⃣ **Validation Decision**
- - Compares expected constraints from YAML or Python object.
- - Raises `ValidationError` if mismatches are found — or returns control if all checks pass.
-
-🔍 Core Python Tools Behind the Magic
---------------------------------------
-
-ImportSpy uses only built-in Python modules — no black magic, just introspection:
-
-- `inspect.stack()` – call stack tracing
-- `inspect.getmodule()` – resolve module context
-- `platform.system()`, `platform.machine()` – OS and architecture
-- `sys.version_info`, `platform.python_implementation()` – Python version and interpreter
-- `os.environ` – environment variable resolution
-- `getmembers()` – dynamic class/function structure extraction
-
-These are the building blocks behind ImportSpy’s runtime truth-checking engine.
-
-⚙️ Embedded vs External Mode: Runtime Differences
---------------------------------------------------
-
-| Mode | Validated Context | Typical Use Case |
-|-----------------|----------------------------|---------------------------------------------------|
-| **Embedded** | The **importer** of a module | Plugin architectures, sandbox validation |
-| **External** | The **module itself** | CI pipelines, security audits, pre-release checks |
-
-Both modes rely on the **same runtime model**, but invert the direction of validation.
-
-✅ Embedded Mode Example:
-- `my_plugin.py` calls `import core_module.py`
-- Inside `core_module`, validation ensures `my_plugin` is allowed to import it.
-
-✅ CLI Mode Example:
-- You run `importspy -s contract.yml my_plugin.py`
-- ImportSpy checks if `my_plugin` complies with its declared structure and runtime constraints.
-
-🚀 Why Runtime Analysis Changes the Game
------------------------------------------
-
-ImportSpy’s runtime model enables features few tools can offer:
-
-- **Import-time contract enforcement** — with precise control over OS, interpreter, architecture
-- **Real context validation** — no assumptions, just introspection
-- **Full plugin safety** — modules can reject untrusted importers
-- **CI/CD guarantees** — validate module deployment conditions at build time
-
-Python’s flexibility is often seen as a liability — ImportSpy turns it into an *auditable gate*.
-
-⚡ Performance Considerations
------------------------------
-
-Runtime analysis has a cost — but ImportSpy minimizes it through:
-
-- **Lazy evaluation** — modules are only analyzed when loaded
-- **Context caching** — previously computed SpyModels are reused
-- **Selective enforcement** — system modules are skipped unless explicitly targeted
-
-Validation takes milliseconds, not seconds — even in dynamic plugin workflows.
-
-🔐 Final Takeaway
-------------------
-
-ImportSpy’s runtime analysis engine turns **introspection into validation**.
-
-By deeply understanding **who is importing what, from where, and under which conditions**, ImportSpy enforces:
-
-✅ Structural correctness
-✅ Environmental compliance
-✅ Runtime safety — across interpreters, containers, and pipelines
-
-Whether you’re building a plugin system, securing a package, or hardening your CI,
-ImportSpy gives you the tools to **intercept, introspect, and enforce — right at import time.**
-
-Next:
-- :doc:`architecture_validation_engine` → See how the validator pipeline executes
-- :doc:`architecture_design_decisions` → Understand the rationale behind ImportSpy’s runtime-first approach
diff --git a/docs/source/advanced/architecture/architecture_validation_engine.rst b/docs/source/advanced/architecture/architecture_validation_engine.rst
deleted file mode 100644
index 681bbe5..0000000
--- a/docs/source/advanced/architecture/architecture_validation_engine.rst
+++ /dev/null
@@ -1,117 +0,0 @@
-The Validation Engine: Import-Time Assurance for Python
-=======================================================
-
-At the center of ImportSpy lies its **validation engine** — a layered, runtime-first mechanism designed to make sure that:
-
-✅ Code only runs in verified environments
-✅ Structure and behavior match declared expectations
-✅ Unauthorized imports are blocked at the boundary
-
-Unlike static linters or test suites, ImportSpy runs at **import time**, ensuring that modules are **never executed unless compliant** — a zero-trust posture for the Python ecosystem.
-
-🎯 What the Validation Engine Actually Does
--------------------------------------------
-
-The validation engine intercepts import events and answers:
-
-- Is the importing environment trusted?
-- Is the runtime (OS, architecture, interpreter) allowed?
-- Does the structure of the module match what was promised?
-- Are declared environment variables, dependencies, and APIs present?
-
-It acts like a **runtime compliance firewall** — catching issues before a single line of code is executed.
-
-📦 Core Pipeline Stages
-------------------------
-
-Whether in **embedded** mode, ImportSpy uses the same five-stage validation pipeline:
-
-1️⃣ **Import Interception**
- - Uses stack inspection (`inspect.stack`) to trace the importing module.
- - Determines the precise origin of the import.
-
-2️⃣ **Context Modeling (SpyModel Construction)**
- - Builds a full runtime profile:
- - OS, CPU architecture
- - Python version and interpreter
- - Environment variables
- - Nested module dependencies
-
-3️⃣ **Structural Validation**
- - Analyzes the module’s actual structure (via `inspect`, `ast`, `getmembers`)
- - Compares it against the declared contract:
- - Classes and superclasses
- - Function names, signatures, return types
- - Global variables, attributes, and annotations
-
-4️⃣ **Contract Evaluation**
- - Evaluates the runtime `SpyModel` against the declared import contract (in YAML or Python).
- - Uses typed validators to match expected values — with support for optional vs required fields.
-
-5️⃣ **Enforcement & Feedback**
- - ✅ If all checks pass, control is returned to the caller.
- - ❌ If validation fails:
- - Raise `ValidationError` with structured diagnostics
- - Provide exact mismatch detail (missing method, wrong version, etc.)
- - Halt execution unless soft mode is enabled
-
-🔍 Modular Validation Subsystems
----------------------------------
-
-ImportSpy’s engine is composed of distinct layers, each with its own responsibility:
-
-🔹 **Import Interceptor**
- Detects runtime context at the moment of import. Gathers caller identity and call stack.
-
-🔹 **SpyModel Generator**
- Constructs a normalized model from dynamic runtime inputs. Represents the environment as data.
-
-🔹 **Validator Stack**
- Runs a pipeline of validators, including:
- - Structural validators (classes, functions, attributes)
- - Environmental validators (OS, Python, architecture)
- - Context validators (importer identity, variables, contract location)
-
-🔹 **Report Engine**
- Formats failure messages and traces:
- - Uses centralized error codes
- - Offers developer-facing hints and CI-friendly logs
-
-🔹 **Resolution Manager** (Planned)
- In future releases, this will support:
- - Auto-suggestions for mismatches
- - Soft warnings for dry runs
- - Contract diffing and explainability tools
-
-⚙️ Optimizing for Runtime Performance
---------------------------------------
-
-Validation must be precise — but also fast. ImportSpy uses:
-
-- **Lazy Evaluation** – modules are only analyzed when accessed.
-- **Context Caching** – avoids recomputing runtime metadata.
-- **Selective Enforcement** – skips system libraries and only enforces contracts for targeted modules.
-- **Failure Short-Circuiting** – stops on the first critical violation unless configured otherwise.
-
-In most use cases, validation completes in under 50ms — fast enough for production use, even inside plugin systems.
-
-🔐 Why This Matters
---------------------
-
-Python offers no guardrails by default. Anyone can import anything, in any context.
-
-ImportSpy's validation engine creates those guardrails by:
-
-✅ Binding module behavior to structural truth
-✅ Locking execution to trusted environments
-✅ Giving developers and systems **predictable, explainable outcomes**
-
-It’s the difference between _hoping your module runs correctly_ and _knowing that it only ever runs under the right conditions._
-
-📘 Next Steps
--------------
-
-Continue exploring the architecture:
-
-- :doc:`architecture_runtime_analysis` → See how execution context is captured
-- :doc:`architecture_design_decisions` → Understand the philosophy behind runtime validation
diff --git a/docs/source/advanced/architecture_index.rst b/docs/source/advanced/architecture_index.rst
deleted file mode 100644
index edeb632..0000000
--- a/docs/source/advanced/architecture_index.rst
+++ /dev/null
@@ -1,64 +0,0 @@
-ImportSpy Architecture
-======================
-
-The Internal Blueprint of Runtime Validation 🧠
------------------------------------------------
-
-ImportSpy is more than a module linter — it is an **import-time enforcement layer**
-that introduces structural awareness and compliance validation directly into the Python runtime.
-
-This section explores **how ImportSpy works under the hood**, breaking down its core architecture
-into modular layers that combine **dynamic reflection**, **declarative contracts**, and **runtime interception**.
-
-Why Architecture Matters
--------------------------
-
-In a Python ecosystem where:
-
-- Modules are shared across microservices and containers,
-- Plugins are authored by third parties,
-- Deployments span heterogeneous systems,
-
-...you need more than just "tests". You need a **validation engine** that adapts at runtime.
-
-ImportSpy was designed to:
-
-- 🛡️ **Enforce predictable structure** in external modules
-- 🧩 **Capture and interpret runtime conditions** dynamically
-- 🔒 **Prevent misaligned or unauthorized integrations**
-
-It introduces formal boundaries where Python has none.
-
-What You'll Learn in This Section 📚
-------------------------------------
-
-This section explains how ImportSpy brings **declarative rigor to dynamic Python environments**.
-
-You’ll explore:
-
-- ✅ The **layered architecture** that enables flexible yet strict validation
-- ✅ The **rationale behind each design decision** — from using YAML contracts to stack inspection
-- ✅ The **engine that drives compliance enforcement**, based on Pydantic and reflection
-- ✅ The **runtime analyzer** that reconstructs execution environments
-- ✅ The **performance patterns** that make ImportSpy usable even at scale
-
-.. toctree::
- :maxdepth: 2
-
- architecture/architecture_overview
- architecture/architecture_design_decisions
- architecture/architecture_validation_engine
- architecture/architecture_runtime_analysis
-
-Who Is This For?
-----------------
-
-Whether you're:
-
-- a **developer** embedding ImportSpy in a plugin framework,
-- a **security engineer** hardening Python execution boundaries,
-- or a **contributor** improving contract modeling,
-
-this section will give you the architectural grounding to wield ImportSpy **confidently and effectively**.
-
-Ready to look inside the engine? Let’s go. 🚀
diff --git a/docs/source/beginner/beginner_index.rst b/docs/source/beginner/beginner_index.rst
deleted file mode 100644
index c072445..0000000
--- a/docs/source/beginner/beginner_index.rst
+++ /dev/null
@@ -1,64 +0,0 @@
-Beginner Guide to ImportSpy
-===========================
-
-👋 Welcome to the **Beginner Guide** for ImportSpy!
-
-This section is built for developers who are **new to ImportSpy** and want to build a **deep and practical understanding**
-of how it works—from its internal architecture to the powerful Python concepts it builds upon.
-
-ImportSpy is more than just a validation tool—it’s a teaching opportunity.
-By understanding its foundations, you’ll not only use it better, but you’ll also sharpen your overall Python skills.
-
-What You’ll Learn in This Guide 📚
-----------------------------------
-
-This guide is designed to help you understand **how ImportSpy works under the hood**,
-by introducing you to the core technologies and design principles it leverages.
-
-🧠 Topics include:
-
-- **🔧 Managing ImportSpy with Poetry**
- Understand how ImportSpy is structured, installed, and maintained using [**Poetry**](https://python-poetry.org/),
- a modern dependency manager and project builder for Python.
-
-- **🔍 Python Reflection & Introspection**
- Learn how ImportSpy uses Python’s dynamic features like `inspect`, `importlib`, and stack frames
- to reconstruct runtime context and validate imports on the fly.
-
-- **📐 Pydantic and Data Modeling**
- Explore how ImportSpy uses **Pydantic** to define and validate import contracts,
- turning YAML declarations into structured, type-safe models that enforce correctness.
-
-Who Should Read This Guide? 🎯
-------------------------------
-
-You’ll benefit most from this guide if:
-
-✅ You’re **new to ImportSpy** and want to understand how it really works
-✅ You’re interested in Python's **runtime system, reflection, and data validation**
-✅ You want to learn how **modern tooling like Poetry and Pydantic** can help you write better software
-
-How to Use This Guide 🛠️
--------------------------
-
-Each section in this guide is designed to be self-contained, but together they provide a **progressive learning path**.
-We recommend reading them in order, especially if you're new to:
-
-- Poetry and dependency management in Python
-- Reflection and runtime validation
-- Declarative contracts and schema-driven design
-
-Each topic includes:
-
-- ✅ Clear explanations
-- 💡 Real-world examples
-- 🧠 Best practices you can apply to your own projects
-
-.. toctree::
- :maxdepth: 2
-
- poetry_basics
- python_reflection
- pydantic_in_importspy
-
-🎉 Let’s get started—build your knowledge and unlock the full power of ImportSpy!
diff --git a/docs/source/beginner/poetry_basics.rst b/docs/source/beginner/poetry_basics.rst
deleted file mode 100644
index 24268b9..0000000
--- a/docs/source/beginner/poetry_basics.rst
+++ /dev/null
@@ -1,146 +0,0 @@
-Using Poetry with ImportSpy
-===========================
-
-Poetry is the **official packaging and dependency management tool** used in ImportSpy.
-It ensures reproducibility, streamlines development workflows, and enables better collaboration.
-This guide will help you understand how to use Poetry within ImportSpy’s ecosystem and learn why it’s essential.
-
-Why Poetry?
------------
-
-Poetry offers a modern alternative to legacy tools like `pip`, `setup.py`, and `requirements.txt`.
-It provides:
-
-- ✅ **Isolated virtual environments** with automatic activation
-- ✅ **Declarative dependency management** via `pyproject.toml`
-- ✅ **Lockfile consistency** with `poetry.lock`
-- ✅ **Integrated build and publishing workflow**
-- ✅ **Support for multiple dependency groups** (dev, docs, ci, etc.)
-
-Installing Poetry
------------------
-
-You can install Poetry with the official script:
-
-.. code-block:: bash
-
- curl -sSL https://install.python-poetry.org | python3 -
-
-Verify installation:
-
-.. code-block:: bash
-
- poetry --version
-
-Setting Up ImportSpy
---------------------
-
-1. Clone the repository:
-
- .. code-block:: bash
-
- git clone https://github.com/atellaluca/importspy.git
- cd importspy
-
-2. Install all project dependencies:
-
- .. code-block:: bash
-
- poetry install
-
-3. Activate the virtual environment (optional):
-
- .. code-block:: bash
-
- poetry shell
-
-Dependency Management
----------------------
-
-Add dependencies:
-
-.. code-block:: bash
-
- poetry add pydantic
- poetry add --group dev pytest
-
-Remove dependencies:
-
-.. code-block:: bash
-
- poetry remove pydantic
-
-Update dependencies:
-
-.. code-block:: bash
-
- poetry update # Update all
- poetry update pydantic # Update a specific one
-
-Best practice:
-✅ Always commit `poetry.lock` to your VCS to ensure reproducibility.
-
-Understanding the `pyproject.toml`
-----------------------------------
-
-.. code-block:: toml
-
- [tool.poetry]
- name = "importspy"
- version = "0.2.0"
- description = "A validation and compliance framework for Python modules."
- authors = ["Luca Atella "]
-
- [tool.poetry.dependencies]
- python = "^3.10"
- pydantic = "^2.9.2"
-
- [tool.poetry.group.dev.dependencies]
- pytest = "^8.3.3"
-
- [tool.poetry.group.docs.dependencies]
- sphinx = "^7.2"
- furo = "^2024.8.6"
-
- [tool.poetry.scripts]
- importspy = "importspy.cli:validate"
-
-To run CLI commands defined in the `pyproject.toml`:
-
-.. code-block:: bash
-
- poetry run importspy --help
-
-Versioning and Releases
------------------------
-
-ImportSpy follows Semantic Versioning (SemVer).
-You can bump versions like this:
-
-.. code-block:: bash
-
- poetry version patch | minor | major
-
-Build and publish (requires authentication):
-
-.. code-block:: bash
-
- poetry build
- poetry publish
-
-Exporting Requirements
-----------------------
-
-If you need a `requirements.txt` (e.g., for Docker or legacy tooling):
-
-.. code-block:: bash
-
- poetry export -f requirements.txt --output requirements.txt
-
-Next Steps
-----------
-
-Now that you’ve configured Poetry, continue learning about ImportSpy’s internals:
-
-- :doc:`python_reflection`
-- :doc:`pydantic_in_importspy`
diff --git a/docs/source/beginner/pydantic_in_importspy.rst b/docs/source/beginner/pydantic_in_importspy.rst
deleted file mode 100644
index aca6355..0000000
--- a/docs/source/beginner/pydantic_in_importspy.rst
+++ /dev/null
@@ -1,126 +0,0 @@
-Pydantic in ImportSpy
-======================
-
-Why Pydantic Matters for ImportSpy 🧠
--------------------------------------
-
-ImportSpy uses **Pydantic** as the foundation for its validation engine, enabling it to model and enforce strict structural and environmental expectations.
-
-In a dynamic language like Python, where anything can change at runtime, Pydantic provides **deterministic enforcement** of expected module attributes, function signatures, return types, and environment variables.
-
-By wrapping all validation logic in **Pydantic-based models**, ImportSpy transforms flexible contracts into **strict runtime guards**.
-
-Core Advantages:
-
-- ✅ Declarative schemas that model module structure and runtime constraints.
-- ✅ Precise, readable errors that help developers fix violations quickly.
-- ✅ Built-in support for complex types, enums, environment parsing, and more.
-
-How Pydantic Is Used in ImportSpy 🔍
-------------------------------------
-
-All import contracts (`.yml`) are parsed and converted into nested **Pydantic models** during runtime or CLI validation. These models serve as the "expected shape" against which a module or runtime is validated.
-
-Each layer of the import contract is mapped to a Pydantic model:
-
-- A class like `Extension` in a plugin? → `ClassModel`.
-- A function like `add_extension(msg: str) -> str`? → `FunctionModel`.
-- An interpreter requirement? → `InterpreterModel`.
-- OS/environment constraints? → `SystemModel`.
-
-.. code-block:: python
-
- from pydantic import BaseModel
- from typing import List, Optional
-
- class MethodModel(BaseModel):
- name: str
- arguments: List[str]
- return_annotation: Optional[str] = None
-
- class ClassModel(BaseModel):
- name: str
- methods: List[MethodModel]
-
-Validation Example 🧪
-----------------------
-
-Here’s a simplified validation use case.
-
-.. code-block:: python
-
- class PluginContract(BaseModel):
- filename: str
- classes: List[ClassModel]
-
- contract = PluginContract(
- filename="extension.py",
- classes=[
- ClassModel(
- name="Extension",
- methods=[
- MethodModel(name="add_extension", arguments=["self", "msg"], return_annotation="str")
- ]
- )
- ]
- )
-
-Now at runtime, if a module lacks that method or returns the wrong type, ImportSpy fails **before** execution.
-
-Runtime Failures Are Structured ⚠️
-----------------------------------
-
-Pydantic errors are deeply integrated with ImportSpy’s logging and debugging layers:
-
-.. code-block:: json
-
- [
- {
- "loc": ["classes", 0, "methods", 0, "return_annotation"],
- "msg": "str type expected",
- "type": "type_error.str"
- }
- ]
-
-This means: no silent failures, no vague logs.
-You know *exactly* what’s missing, and where.
-
-Benefits Beyond Type Checking ✅
---------------------------------
-
-- 🧩 **Cross-layer schema validation**: classes within modules, methods within classes, etc.
-- 🛡️ **Zero-Trust enforcement**: if something’s missing, execution is blocked.
-- 🔄 **Reusable contract definitions**: models are consistent across embedded and CLI mode.
-- 📖 **Documentation as code**: import contracts double as machine- and human-readable specs.
-
-Advanced Use: Dynamic Constraints
----------------------------------
-
-Want to block execution in certain Python versions? Or only allow certain interpreters?
-
-Pydantic makes it easy to write declarative rules:
-
-.. code-block:: python
-
- from pydantic import BaseModel, validator
-
- class PythonRuntime(BaseModel):
- version: str
-
- @validator("version")
- def must_be_310_or_higher(cls, v):
- if v < "3.10":
- raise ValueError("Python version must be >= 3.10")
- return v
-
-Conclusion 🎯
--------------
-
-Pydantic is not just a convenience in ImportSpy — it’s the **core engine** behind runtime validation.
-
-It provides a robust layer to define, enforce, and debug structural rules with confidence.
-
-Next steps:
-
-- :doc:`python_reflection` — Learn how ImportSpy introspects code dynamically.
-- https://docs.pydantic.dev/ — Go deeper into advanced Pydantic use cases.
diff --git a/docs/source/beginner/python_reflection.rst b/docs/source/beginner/python_reflection.rst
deleted file mode 100644
index 72e4c84..0000000
--- a/docs/source/beginner/python_reflection.rst
+++ /dev/null
@@ -1,121 +0,0 @@
-Understanding Python Reflection in ImportSpy
-============================================
-
-Why Reflection Matters 🪞
---------------------------
-
-Python's reflection capabilities allow code to inspect, analyze, and interact with itself at runtime.
-This is central to how **ImportSpy** validates modules dynamically — it doesn't just look at source code,
-it actively examines **what exists and how it behaves** at the moment of import.
-
-In a system where plugins or modules are loosely coupled, this allows ImportSpy to:
-
-- Validate structural expectations (`classes`, `functions`, `attributes`).
-- Detect runtime constraints (`interpreter`, `version`, `environment`).
-- Prevent unexpected or unauthorized imports.
-
-Core Python Reflection Tools 🔍
--------------------------------
-
-ImportSpy uses several key components of Python’s reflection toolbox:
-
-**1. `inspect`** — Runtime introspection
-
-.. code-block:: python
-
- import inspect
-
- def foo(): pass
-
- print(inspect.isfunction(foo)) # True
- print(inspect.getmembers(foo)) # List all members of the function object
-
-**2. `getattr` / `hasattr` / `setattr`** — Attribute access and mutation
-
-.. code-block:: python
-
- class User: name = "Alice"
-
- u = User()
- print(getattr(u, "name")) # "Alice"
- print(hasattr(u, "email")) # False
- setattr(u, "email", "a@example.com") # Dynamically add attribute
-
-**3. `importlib`** — Dynamic module loading
-
-.. code-block:: python
-
- import importlib
-
- mod = importlib.import_module("math")
- print(mod.sqrt(16)) # 4.0
-
-These techniques allow ImportSpy to analyze **any arbitrary Python module** during validation.
-
-How ImportSpy Uses Reflection 🧠
---------------------------------
-
-ImportSpy doesn’t hardcode validation rules into your code.
-Instead, it reads a YAML contract, parses it into a structured `SpyModel`, and:
-
-1. **Intercepts the importing context**
- → via `inspect.stack()` to determine *who* is importing the validated module.
-
-2. **Loads the target module**
- → via `importlib` or by extracting from `sys.modules`.
-
-3. **Validates its structure**
- → using `inspect.getmembers()` to check for methods, annotations, and base classes.
-
-4. **Checks runtime environment**
- → including Python version, interpreter type, and required variables.
-
-This **dynamic, contract-driven validation** is only possible thanks to Python's reflective architecture.
-
-Reflection in Embedded Mode vs CLI Mode 🔁
-------------------------------------------
-
-In **Embedded Mode**, reflection is used by the validated module itself:
-
-- It calls `Spy().importspy(...)`
-- Uses `inspect.stack()` to identify the **caller**
-- Then validates that external environment using reflection
-
-In **CLI Mode**, reflection is applied directly to the target file:
-
-- `importspy -s contract.yml module.py`
-- ImportSpy dynamically loads and introspects the module
-- Checks all runtime constraints before it can be deployed
-
-Best Practices & Pitfalls ⚠️
-----------------------------
-
-Reflection is powerful — but should be used wisely:
-
-✅ **Cache inspection results** to avoid repeat analysis
-❌ Avoid calling unknown or unsafe methods with `getattr()` blindly
-✅ Combine with type checks (`callable`, `isinstance`) before execution
-❌ Don’t mutate live objects unless you're in full control
-
-Example: safe method invocation
-
-.. code-block:: python
-
- if hasattr(module, "run") and callable(module.run):
- module.run()
-
-Takeaway 🧠
------------
-
-Reflection is what makes ImportSpy possible.
-
-By using `inspect`, `importlib`, and Python’s runtime model, ImportSpy can:
-
-- Enforce validation without altering your code
-- Dynamically adapt to different environments
-- Offer a robust, runtime-safe contract enforcement system
-
-Explore more:
-
-- :doc:`pydantic_in_importspy`
-- `https://docs.python.org/3/library/inspect.html`
diff --git a/docs/source/conf.py b/docs/source/conf.py
deleted file mode 100644
index bd90af1..0000000
--- a/docs/source/conf.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# Configuration file for the Sphinx documentation builder.
-#
-# For the full list of built-in configuration values, see the documentation:
-# https://www.sphinx-doc.org/en/master/usage/configuration.html
-
-# -- Project information -----------------------------------------------------
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
-
-project = 'ImportSpy'
-copyright = '2024, Luca Atella'
-author = 'Luca Atella'
-release = '0.3.3'
-
-# -- General configuration ---------------------------------------------------
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
-
-extensions = [
- 'sphinx.ext.napoleon',
- 'sphinx.ext.autodoc',
- 'sphinx.ext.viewcode',
- 'sphinx_tabs.tabs',
-]
-
-templates_path = ['_templates']
-exclude_patterns = []
-
-
-
-# -- Options for HTML output -------------------------------------------------
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
-
-html_theme = "furo"
-html_static_path = ['_static']
-html_css_files = ['custom.css']
-
-html_theme_options = {
-
-}
-
-html_theme_options["footer_icons"] = [
- {
- "name": "GitHub",
- "url": "https://github.com/atellaluca/ImportSpy",
- "html": """
-
- """,
- "class": "",
- },
- {
- "name": "PyPI",
- "url": "https://pypi.org/project/ImportSpy/",
- "html": """
-
- """,
- "class": "",
- }
-]
diff --git a/docs/source/get_started.rst b/docs/source/get_started.rst
deleted file mode 100644
index 76e1b5a..0000000
--- a/docs/source/get_started.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-Get Started with ImportSpy 🚀
-=============================
-
-Welcome to the official **Get Started** guide for ImportSpy!
-
-This section is your hands-on introduction to ImportSpy’s capabilities and philosophy.
-If you're building systems with **plugins**, **microservices**, **modular designs**, or simply want **more control over how your code is imported**, you're in the right place.
-
-ImportSpy brings structure and validation to Python's dynamic import system — allowing you to **enforce strict module contracts**, detect **runtime mismatches**, and ensure that every imported module behaves exactly as expected.
-
-What You’ll Learn 📘
----------------------
-
-This guide walks you through everything you need to **set up, understand, and use ImportSpy effectively**, including:
-
-- 🧰 **Installation**
- Learn how to install ImportSpy using your preferred package manager and verify your setup.
-
-- 🔍 **Example Overview**
- Explore a real-world use case built around a **plugin-based architecture**, and understand how ImportSpy adds reliability to it.
-
-- 📜 **Import Contracts in YAML**
- Understand how ImportSpy uses `.yml` contracts to define what an external module must look like: classes, methods, attributes, environments, and more.
-
-- ⚙️ **Code Walkthrough**
- Dive into a fully working example and see how ImportSpy enforces contracts dynamically at runtime.
-
-- 🧪 **Validation in Action**
- Run the example locally and observe how ImportSpy validates external modules and reports violations clearly and precisely.
-
-Is This Guide for You?
------------------------
-
-Absolutely — if any of these apply to you:
-
-- ✅ You’ve never used ImportSpy before and want a guided path
-- ✅ You work with Python plugins, extensions, or modular projects
-- ✅ You want to ensure that external code is predictable and secure
-- ✅ You're interested in enforcing structure and compliance at runtime
-
-What You’ll Need 🛠️
---------------------
-
-- Basic familiarity with Python and importing modules
-- **Python 3.10 or later** installed on your machine
-- A terminal or shell to execute ImportSpy from the command line (optional for CLI mode)
-- A willingness to explore and experiment!
-
-Let’s Build Your First Validated Project ✅
--------------------------------------------
-
-In the next steps, you’ll learn how to:
-
-1. Install ImportSpy in seconds
-2. Define your first import contract
-3. Validate an external module in both embedded and CLI mode
-4. Understand how ImportSpy reacts to structural mismatches
-5. Integrate it into a real project
-
-By the end of this guide, you’ll have a **fully functional ImportSpy environment**, understand the power of import contracts, and feel confident applying them in your own architecture.
-
-Let’s get started!
-
-.. toctree::
- :maxdepth: 2
-
- get_started/installation
- get_started/example_overview
- get_started/examples/plugin_based_architecture/index
diff --git a/docs/source/get_started/example_overview.rst b/docs/source/get_started/example_overview.rst
deleted file mode 100644
index f9c815c..0000000
--- a/docs/source/get_started/example_overview.rst
+++ /dev/null
@@ -1,65 +0,0 @@
-ImportSpy Examples: Real-World Scenarios in Action 🚀
-=====================================================
-
-Welcome to the **Examples** section of ImportSpy — where theory meets practice.
-
-In this space, you'll explore **runnable, real-world demonstrations** showing how ImportSpy helps enforce **structural validation**, **interface consistency**, and **runtime compliance** in modular Python systems.
-
-Whether you're working with plugins, pipelines, APIs, or layered architectures, these examples provide **blueprints you can adapt** to your own projects.
-
-Why Examples Matter 🧩
------------------------
-
-Modern Python applications are highly dynamic.
-But that flexibility comes with risks: unexpected behaviors, silent failures, and integration mismatches.
-
-ImportSpy gives you a way to bring **formal guarantees** into the dynamic world of imports.
-Here, you'll see exactly how it works — with practical, minimal, and extensible examples.
-
-How to Use These Examples ⚙️
-------------------------------
-
-Each example in this section is:
-
-- ✅ Self-contained and ready to run
-- ✅ Designed around real architectural patterns
-- ✅ Focused on one validation principle at a time
-- ✅ Ideal for experimentation and adaptation
-
-To try them:
-
-1. Ensure you have **ImportSpy installed**
- .. code-block:: bash
-
- pip install importspy
-
-2. Choose an example that matches your context
-3. Run it locally
-4. Modify the contract or the code and observe the validation outcomes
-5. Learn how ImportSpy blocks invalid imports and reinforces structural safety
-
-Available Example 💡
----------------------
-
-The first complete walkthrough is:
-
-📦 :doc:`examples/plugin_based_architecture/index`
-
-This scenario demonstrates:
-
-- Defining import contracts
-- Handling plugin structure enforcement
-- Using both **embedded** and **CLI** validation modes
-- Running validation in a pipeline context
-
-Coming Soon ✨
---------------
-
-We're actively expanding this section with more examples, including:
-
-- API structure validation (FastAPI, Flask)
-- Cross-service contract enforcement in microservices
-- Schema enforcement in data pipelines
-- Security policies for runtime imports
-
-Want to contribute your own? Reach out or open a PR on GitHub!
diff --git a/docs/source/get_started/examples/plugin_based_architecture/external_module_compilance.rst b/docs/source/get_started/examples/plugin_based_architecture/external_module_compilance.rst
deleted file mode 100644
index 6753fc9..0000000
--- a/docs/source/get_started/examples/plugin_based_architecture/external_module_compilance.rst
+++ /dev/null
@@ -1,114 +0,0 @@
-External Module Compliance (Embedded Mode Example)
-==================================================
-
-This example demonstrates one of ImportSpy’s most powerful features:
-**embedded validation**, where a module being imported can validate **who is importing it**.
-
-Unlike traditional tools that validate their own structure, ImportSpy allows a module to **control its consumers** —
-ensuring that only fully compliant modules can interact with it.
-
-Why This Matters 🔐
---------------------
-
-In plugin architectures, dynamic systems, or modular platforms, core components are often imported by untrusted or external code.
-Without structural guarantees, this opens the door to:
-
-- Runtime crashes from missing methods
-- Silent logic errors due to incompatible extensions
-- Unpredictable behaviors across environments
-
-ImportSpy solves this by allowing the core module to define a **YAML-based contract**, and reject importers that don’t match.
-
-Use Cases ✅
-~~~~~~~~~~~~
-
-- Plugin systems with strict APIs
-- Modular backends with third-party integration
-- Secure extensions and validation gateways
-- Projects needing **controlled extensibility** from external modules
-
-Project Structure 📁
----------------------
-
-.. code-block::
-
- external_module_compliance/
- ├── extension.py # External module trying to import the core
- ├── package.py # Core module protected by ImportSpy
- ├── plugin_interface.py # Shared interface definition
- └── spymodel.yml # Structural contract for external validation
-
-How It Works 🧠
-----------------
-
-1. `extension.py` tries to import `package.py`
-2. Inside `package.py`, ImportSpy runs in **embedded mode**:
- .. code-block:: python
-
- caller_module = Spy().importspy(filepath="spymodel.yml")
-
-3. The contract in `spymodel.yml` defines what `extension.py` must contain (e.g., classes, methods, variables)
-4. If the contract is satisfied:
- - `caller_module` is assigned to `extension.py`
- - The validated importer can be used directly, like:
- `caller_module.Foo().get_bar()`
-5. If not, ImportSpy raises an error and **prevents usage of the module**.
-
-Run the Example ▶️
---------------------
-
-From the root of the project, run:
-
-.. code-block:: bash
-
- cd examples/plugin_based_architecture/external_module_compliance
- python extension.py
-
-Expected Output:
-
-.. code-block:: text
-
- Foobar
-
-This means:
-
-- The importer (`extension.py`) passed validation
-- The core module (`package.py`) verified its importer before doing anything
-- You now have **runtime-level confidence** in how the system integrates
-
-Simulating a Failure ❌
-------------------------
-
-To see ImportSpy in action, try this:
-
-1. Open `spymodel.yml`
-2. Modify a method name (e.g., `add_extension` → `add_extension_WRONG`)
-3. Run the example again:
-
-.. code-block:: bash
-
- python extension.py
-
-Expected output:
-
-.. code-block:: text
-
- ValueError: Missing method in class Extension: 'add_extension_WRONG'. Ensure it is defined.
-
-🛑 This is **real-time structural enforcement**.
-The module is immediately blocked for violating the import contract.
-
-Key Takeaways 🧩
------------------
-
-- ImportSpy’s **embedded mode** empowers a module to **control who is allowed to import it**
-- It guarantees that plugins, extensions, or third-party modules conform to the contract before any code runs
-- The returned `caller_module` gives you full access to the validated importer — just like any other module object
-- This pattern is ideal when **predictability, structure, and security** are non-negotiable
-
-Next Steps 🔄
--------------
-
-- Try editing the contract and module to explore different validations
-- Combine this with :doc:`pipeline_validation` to enforce contracts in CI/CD pipelines
-- Read more about embedded mode in :doc:`../../../overview/understanding_importspy/embedded_mode`
diff --git a/docs/source/get_started/examples/plugin_based_architecture/index.rst b/docs/source/get_started/examples/plugin_based_architecture/index.rst
deleted file mode 100644
index ef1c92a..0000000
--- a/docs/source/get_started/examples/plugin_based_architecture/index.rst
+++ /dev/null
@@ -1,79 +0,0 @@
-Plugin-Based Architecture: Example Suite Overview
-=================================================
-
-Welcome to the **Plugin-Based Architecture** examples for ImportSpy.
-This section showcases how ImportSpy can be integrated into real-world modular systems to ensure **structural integrity**, **runtime compatibility**, and **import-time compliance**.
-
-Why Plugins Need Validation 🧩
-------------------------------
-
-Modern applications are often built around **plugin systems**, **modular services**, or **runtime extensions** —
-components that are loaded dynamically and sometimes authored externally.
-
-Without strict validation, these integrations can lead to:
-
-- ❌ Unexpected runtime errors
-- ❌ Silent logic bugs due to mismatched interfaces
-- ❌ Security vulnerabilities in dynamic loading scenarios
-
-ImportSpy solves this by enforcing **formal contracts** — ensuring that every module that is imported or interacted with follows a precise structure and runtime context.
-
-What You'll Learn Here 🎯
---------------------------
-
-In this section, you’ll explore two complementary validation modes:
-
-.. list-table::
- :widths: 25 75
- :header-rows: 1
-
- * - Validation Mode
- - Description
- * - Embedded Mode
- - Validation is performed **inside the core module** (e.g. `package.py`) that is being imported.
- When an external module (like `extension.py`) imports it, the core validates the importer.
- Ideal for secure plugin frameworks, APIs, or modular applications.
- * - CLI Mode
- - Validation is performed **externally via the command line**, using `importspy -s contract.yml module.py`.
- Perfect for CI/CD, static enforcement, or pre-deployment checks.
-
-How to Run the Examples 🛠️
----------------------------
-
-Make sure ImportSpy is installed:
-
-.. code-block:: bash
-
- pip install importspy
-
-Then:
-
-- 🧪 **Embedded Validation**
- .. code-block:: bash
-
- cd examples/plugin_based_architecture
- python extension.py
-
-- 🧪 **CLI Validation**
- .. code-block:: bash
-
- cd examples/plugin_based_architecture
- importspy -s spymodel.yml extension.py
-
-Try editing the modules or the contract and rerun the validations —
-you’ll see how ImportSpy detects mismatches immediately.
-
-Ready to Dive In? 🚀
---------------------
-
-These examples provide a practical foundation for using ImportSpy in your own architecture.
-They demonstrate not just how validation works, but **where it fits** in modern Python workflows.
-
-Navigate to a specific mode to explore:
-
-.. toctree::
- :maxdepth: 1
- :caption: Validation Modes
-
- external_module_compilance
- pipeline_validation
diff --git a/docs/source/get_started/examples/plugin_based_architecture/pipeline_validation.rst b/docs/source/get_started/examples/plugin_based_architecture/pipeline_validation.rst
deleted file mode 100644
index 6c0099d..0000000
--- a/docs/source/get_started/examples/plugin_based_architecture/pipeline_validation.rst
+++ /dev/null
@@ -1,93 +0,0 @@
-Pipeline Validation (CLI Mode Example)
-======================================
-
-This example demonstrates how to use **ImportSpy** in **CLI mode** to validate a Python module against a declared import contract.
-
-In this scenario, validation is **external and decoupled** — the module being validated has no awareness of ImportSpy.
-This makes it ideal for **CI/CD pipelines**, **automated pre-deployment checks**, or **manual compliance validation** during code review.
-
-Why This Mode Is Powerful 🎯
-----------------------------
-
-Unlike embedded mode (where the validated module uses ImportSpy internally),
-CLI mode allows you to **treat validation as an independent, enforceable policy**.
-
-This is especially useful when:
-
-- You’re validating **third-party plugins or contributors' code**
-- You want **full separation of concerns** between business logic and validation
-- You’re integrating ImportSpy into **automated pipelines**
-
-Project Structure 📁
----------------------
-
-.. code-block::
-
- pipeline_validation/
- ├── extension.py # The module to validate
- ├── plugin_interface.py # Shared base class expected by the contract
- └── spymodel.yml # Contract declaring expected structure and runtime
-
-How It Works ⚙️
-----------------
-
-1. The contract in `spymodel.yml` defines the structure, environment, and runtime context expected from `extension.py`
-2. ImportSpy is invoked from the command line to **validate `extension.py` against the contract**
-3. If validation passes ✅, the pipeline continues
- If it fails ❌, the pipeline halts with an explicit error
-
-Running the Example ▶️
------------------------
-
-First, make sure ImportSpy is installed:
-
-.. code-block:: bash
-
- pip install importspy
-
-Then run:
-
-.. code-block:: bash
-
- cd examples/plugin_based_architecture/pipeline_validation
- importspy -s spymodel.yml extension.py
-
-If the module matches the contract, you’ll see something like:
-
-.. code-block:: text
-
- ✅ Validation passed: extension.py complies with contract.
-
-If it fails, you’ll get a detailed, actionable error:
-
-.. code-block:: text
-
- ❌ Validation failed
-
- Reason:
- Missing attribute 'instance' in class 'Extension': extension_instance_name_WRONG
-
-Try Breaking It 🔧
--------------------
-
-To see the validator in action:
-
-1. Open `spymodel.yml`
-2. Change an attribute, method, or variable name (e.g., `add_extension` → `add_extension_WRONG`)
-3. Run the command again
-4. ImportSpy will immediately detect the structural mismatch and explain why
-
-Key Takeaways 💡
------------------
-
-- **CLI mode** is perfect for validating modules *before execution*
-- You can enforce architectural contracts without modifying the validated code
-- Works seamlessly in CI/CD pipelines, GitHub Actions, or any build process
-- Makes **structural integrity** a core part of your development workflow
-
-What’s Next?
--------------
-
-- Try integrating this step into your CI/CD pipeline (e.g., GitHub Actions or GitLab CI)
-- Explore :doc:`external_module_compilance` to learn how embedded mode complements this approach
-- Read more about CLI mode in :doc:`../../../overview/understanding_importspy/external_mode`
diff --git a/docs/source/get_started/installation.rst b/docs/source/get_started/installation.rst
deleted file mode 100644
index 9e21d28..0000000
--- a/docs/source/get_started/installation.rst
+++ /dev/null
@@ -1,87 +0,0 @@
-Installation Guide
-==================
-
-Welcome to the **Installation Guide** for ImportSpy.
-This section will walk you through setting up ImportSpy in your environment — quickly, cleanly, and with confidence.
-
-ImportSpy is designed to be lightweight and easy to integrate into any Python project that values **runtime validation**, **structural compliance**, and **predictable imports**.
-
-System Requirements 📌
------------------------
-
-Before you begin, make sure your development environment meets the following requirements:
-
-- **Python 3.10 or later**
- ImportSpy relies on modern Python features and guarantees compatibility only from version 3.10 onward.
-
-- **pip (latest version)**
- To ensure smooth installation and dependency resolution.
-
-- **Virtual Environment (Recommended)**
- While optional, using a virtual environment is best practice for avoiding dependency conflicts and ensuring isolation.
-
-Installing ImportSpy ⚙️
-------------------------
-
-1. **Create and Activate a Virtual Environment**
-
- While not mandatory, we strongly recommend installing ImportSpy in a virtual environment:
-
- .. tabs::
-
- .. tab:: macOS / Linux
-
- .. code-block:: bash
-
- python3 -m venv venv
- source venv/bin/activate
-
- .. tab:: Windows
-
- .. code-block:: bash
-
- python -m venv venv
- .\venv\Scripts\activate
-
- Once activated, your terminal should indicate that the environment is active.
-
-2. **Install ImportSpy with pip**
-
- Now install ImportSpy directly from PyPI:
-
- .. code-block:: bash
-
- pip install importspy
-
- This command will install the latest stable version of ImportSpy and all required dependencies.
-
-Verifying the Installation ✅
-------------------------------
-
-To confirm that ImportSpy is correctly installed and ready to use, run:
-
-.. code-block:: bash
-
- importspy --version
-
-If everything is set up correctly, the terminal will display the current version of ImportSpy.
-
-Troubleshooting Tips 🧯
-------------------------
-
-If something goes wrong:
-
-- Ensure you're using **Python 3.10+**
-- Activate your virtual environment before running `pip install`
-- If needed, upgrade pip:
- .. code-block:: bash
- python -m pip install --upgrade pip
-
-You're Ready to Go 🎉
-----------------------
-
-That’s it! You’re now ready to start using ImportSpy to enforce module validation in your projects.
-
-Continue to the next section to explore a working example and see ImportSpy in action:
-
-📎 :doc:`example_overview`
diff --git a/docs/source/index.rst b/docs/source/index.rst
deleted file mode 100644
index 1f15e31..0000000
--- a/docs/source/index.rst
+++ /dev/null
@@ -1,74 +0,0 @@
-Welcome to ImportSpy 🔎
-========================
-
-**ImportSpy** is a contract-based runtime validation framework that transforms how Python modules interact—making those interactions **predictable, secure, and verifiable**.
-It empowers developers to define, enforce, and validate **import contracts** that describe exactly how a module should behave when it is imported, under specific runtime conditions.
-
-Whether you're working with **plugin-based systems**, **microservices**, or **cross-platform applications**, ImportSpy gives you **full control over integration boundaries**.
-It ensures that the modules importing your code—or the ones you're importing—adhere to **explicit structural and environmental rules**, avoiding silent failures, runtime crashes, or unpredictable behavior.
-
-🔐 ImportSpy is not just about validation—it’s about **bringing discipline and clarity to the most dynamic part of Python: the import system**.
-
-Why ImportSpy? 🚀
-------------------
-
-- **🧩 Bring Structure to Dynamic Systems**
- Enforce well-defined contracts on imported modules: classes, methods, variables, versions, OS, interpreters, and more.
-
-- **🔍 Runtime-Aware Validation**
- Validate modules **based on actual runtime context**—OS, CPU architecture, Python interpreter, and version.
-
-- **🔌 Built for Plugin Ecosystems**
- Protect core logic from integration errors in environments where dynamic loading is common.
-
-- **🧪 Two Powerful Modes**
- In **embedded mode**, validate external modules *that import your code*, enforcing structure and context dynamically.
- In **CLI mode**, validate any Python module against a contract—ideal for CI/CD pipelines and automated checks.
-
-- **📜 Self-Documenting Contracts**
- The `.yml` contract files double as **live documentation**, formalizing how modules are expected to behave.
-
-What You'll Learn From This Documentation 📖
---------------------------------------------
-
-This guide is designed to help you:
-
-- Understand how ImportSpy works and **why it exists**
-- Learn how to **define and apply import contracts**
-- Explore **real-world use cases** across validation, compliance, CI/CD, security, and IoT integration
-- Navigate through **beginner-friendly training material** that introduces reflection, Pydantic, Poetry, and more
-- Dive into the **internals** of ImportSpy with detailed API references and architectural insights
-- Discover how to **support or sponsor the project** to help it grow
-
-How to Navigate This Documentation 🧭
--------------------------------------
-
-- **👋 New to ImportSpy?** → Start with **Get Started** to see how it works, step by step.
-- **📚 Want to understand the bigger picture?** → Visit the **Overview** section to explore the vision, story, and use cases.
-- **🧠 Curious about internals?** → Explore **Advanced Documentation** for architecture, runtime analysis, and API design.
-- **🎓 Need a learning space?** → Head to the **Beginner Section** to explore tools and practices relevant to ImportSpy.
-- **💼 Interested in supporting ImportSpy?** → Visit the **Sponsorship** section to learn how to get involved.
-
-Let’s build Python software that’s not just flexible, but also **reliable, validated, and future-proof**.
-**Welcome to the new standard for structural integration in Python.**
-
-.. toctree::
- :maxdepth: 2
- :caption: 📌 Core Documentation
-
- vision
- overview
- get_started
- sponsorship
-
-.. toctree::
- :maxdepth: 2
- :caption: 🎓 Beginner Resources
-
- beginner/beginner_index
-
-.. toctree::
- :maxdepth: 2
- :caption: 🧠 Advanced Topics
-
- advanced/advanced_index
diff --git a/docs/source/overview.rst b/docs/source/overview.rst
deleted file mode 100644
index be78402..0000000
--- a/docs/source/overview.rst
+++ /dev/null
@@ -1,72 +0,0 @@
-Overview of ImportSpy
-======================
-
-Welcome to the **ImportSpy Overview** — a complete starting point for understanding the *why*, *how*, and *where* of this project.
-
-ImportSpy was born from a clear need:
-> How can we bring **predictability**, **security**, and **structural clarity** to Python’s dynamic import system?
-
-This section explores not only how ImportSpy works, but also **why it exists**, the **real-world problems it solves**, and **what principles it’s built upon**. Whether you’re a developer, architect, or security engineer, this is where your journey begins.
-
-What You’ll Find in This Section 📖
------------------------------------
-
-This overview is structured into three key parts, each with a distinct purpose:
-
-The Story Behind ImportSpy
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-At its core, ImportSpy is more than just a tool — it’s the result of a **personal journey**.
-Created by Luca Atella as a response to burnout and routine, ImportSpy emerged from a need to **reclaim joy and meaning in development**.
-This section tells that story — not for sentiment, but to show that **structure and purpose can coexist in software**.
-*It reminds us that even small tools, built from a place of passion, can change the way we work.*
-
-📄 :doc:`overview/story`
-
-Use Cases in the Real World
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-ImportSpy is used wherever **modular boundaries need to be enforced** — from plugin ecosystems to CI/CD pipelines.
-This section presents **detailed, practical examples** that show how ImportSpy prevents:
-
-- Misaligned structures in dynamically loaded components
-- Security flaws from unvalidated external modules
-- Runtime instability across architectures or Python environments
-
-Use cases include:
-
-- ✅ **IoT and platform-specific integration**
-- ✅ **Validation & structural integrity in plugin systems**
-- ✅ **Security enforcement through runtime checks**
-- ✅ **Regulatory compliance for mission-critical modules**
-
-📄 :doc:`overview/use_cases_index`
-
-Understanding ImportSpy’s Core
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This is your **deep dive** into the internal logic and architecture of ImportSpy.
-
-You’ll learn:
-
-- What an **import contract** really is, and how to write one
-- How the **Spy validation flow** works from import interception to enforcement
-- The difference between **embedded mode** and **CLI mode**
-- How runtime context (OS, Python version, architecture) plays a role
-- How errors are reported and how to fix them
-- Best practices for **CI/CD pipelines and integration at scale**
-
-*This section turns concepts into confidence, and makes ImportSpy a natural extension of your development process.*
-
-📄 :doc:`overview/understanding_importspy_index`
-
-Let’s get started by exploring the motivation, capabilities, and inner workings of ImportSpy —
-and discover how it can help you build Python software that’s **modular, compliant, and future-ready**.
-
-.. toctree::
- :maxdepth: 2
- :caption: 🔍 Overview
-
- overview/story
- overview/use_cases_index
- overview/understanding_importspy_index
diff --git a/docs/source/overview/story.rst b/docs/source/overview/story.rst
deleted file mode 100644
index 6e1e141..0000000
--- a/docs/source/overview/story.rst
+++ /dev/null
@@ -1,91 +0,0 @@
-From Burnout to Reinvention: The Birth of ImportSpy
-===================================================
-
-In the relentless world of software development, the passion that once sparked creativity
-can slowly be consumed by **repetition, stagnation, and burnout**.
-What begins as a thrilling craft may turn into an **unfulfilling routine**,
-where problem-solving is replaced by **rote tasks and organizational complexity**.
-
-**ImportSpy was born from this very struggle.**
-It is more than just a validation framework—it’s the product of a **deeply personal journey**,
-a project that transformed frustration into clarity and reignited a **genuine love for meaningful software**.
-
-Losing Passion in a World of Repetition
----------------------------------------
-
-For **Luca Atella**, creator of ImportSpy, programming was never just a job.
-Like many developers, he started young—captivated by the magic of **turning logic into solutions**,
-and the joy of **creating something from nothing**.
-
-But over time, that joy began to fade.
-The once-exciting world of coding gave way to **monotony and constraint**.
-Innovation was replaced by **repetitive cycles, boilerplate maintenance, and uninspiring work**.
-
-At just **24 years old**, Luca made a bold move: he walked away from a stable but soulless job.
-A leap into uncertainty, driven by the realization that working without passion was no longer sustainable.
-But quitting wasn’t the solution—it was just the start of something deeper:
-**a period of rediscovery, doubt, and the difficult question—how do you fall in love with your craft again?**
-
-Rebuilding Passion, One Line at a Time
---------------------------------------
-
-The answer wasn’t to abandon programming, but to **reclaim it with intention**.
-Luca didn’t need to leave software behind—he needed to return to it **on his own terms**.
-
-Instead of chasing trends or reacting to deadlines, he built something that truly mattered.
-The idea behind ImportSpy was simple, yet radical:
-
-**What if validation wasn’t just a chore?
-What if it could be elegant, structural, and empowering?**
-
-That idea became the foundation of ImportSpy.
-More than just a tool, it became a **manifesto**—a new way to bring structure to modular software
-while rebuilding the joy of craftsmanship.
-
-More Than Code: A Community-Driven Journey
-------------------------------------------
-
-What began as a **personal experiment** quickly evolved into something greater.
-ImportSpy isn’t just a validation framework—it’s a **testament to the resilience of developers**
-who refuse to let their creativity be buried under routine.
-
-Luca’s path mirrors that of many developers trapped in roles that stifle innovation—
-questioning their purpose in an industry that often rewards **velocity over clarity,
-deadlines over design, and output over impact**.
-
-But ImportSpy offers another way.
-
-It’s a **reminder** that:
-
-- **Code should empower, not frustrate.**
-- **Learning should be embraced—even when messy.**
-- **Passion projects can reshape careers, communities, and industries.**
-
-ImportSpy represents a **new perspective**—one that values **structure, clarity, and the joy of building software that just works**.
-
-Why ImportSpy Matters
----------------------
-
-ImportSpy isn’t just another dev tool—it’s a **statement**.
-
-- A statement that **modular software deserves structural validation and compliance by design**.
-- A statement that **developers need tools that empower their workflows, not complicate them**.
-- A statement that **side projects born from burnout can become catalysts for innovation**.
-
-Technically, ImportSpy ensures **stability, predictability, and architectural rigor** across your modules.
-But its real power lies in its **philosophy**:
-**build with purpose, validate with precision, and code with renewed passion**.
-
-Join the Movement
------------------
-
-If you’ve ever felt **burned out, stuck, or disconnected** from your code,
-know that you’re **not alone**.
-
-ImportSpy is more than a framework—it’s a **community-driven project** built on the belief that
-software should be **precise, predictable, and a joy to create**.
-
-This project is a **tribute to all developers** reclaiming their craft and striving to build **better, smarter software**.
-Because even the smallest ideas, when built with clarity, can have a **massive impact**.
-
-**🔹 Reclaim your passion. Build with confidence. Join the movement.**
diff --git a/docs/source/overview/understanding_importspy/ci_cd_integration.rst b/docs/source/overview/understanding_importspy/ci_cd_integration.rst
deleted file mode 100644
index 14fd2cd..0000000
--- a/docs/source/overview/understanding_importspy/ci_cd_integration.rst
+++ /dev/null
@@ -1,135 +0,0 @@
-CI/CD Integration
-=================
-
-Modern CI/CD pipelines are powerful — but also fragile.
-
-Between dynamic environments, dependency drift, and plugin chaos,
-it’s easy for code to pass local tests and **still fail at runtime**.
-
-ImportSpy brings a layer of **predictability, structural assurance**, and **contract enforcement**
-to your automated workflows — making sure that every module behaves the way it should,
-in the environment where it’s going to run.
-
-Why CI/CD Needs Structural Validation
--------------------------------------
-
-Functional tests catch **what your code does**.
-ImportSpy ensures that it’s **running in the right place, with the right structure**.
-
-Without structural validation, pipelines are vulnerable to:
-
-- ❌ Hidden mismatches in **Python versions**, **interpreters**, or **platforms**
-- ❌ Missing or malformed **environment variables**
-- ❌ Untracked changes in shared modules or plugins
-- ❌ Non-compliant third-party code with unexpected APIs
-
-These failures often appear **late**, when debugging is slow and costly.
-ImportSpy helps you catch them **early**, at build time — not post-deploy.
-
-Where to Use ImportSpy in CI/CD
--------------------------------
-
-**1. During the Build Phase 🧱**
-
-Validate module structure before packaging:
-
-.. code-block:: bash
-
- importspy -s spymodel.yml mymodule.py
-
-This prevents broken contracts from ever making it into an artifact.
-
-**2. During Testing 🔬**
-
-Add ImportSpy validation before or alongside your unit tests:
-
-.. code-block:: bash
-
- importspy -s spymodel.yml -l ERROR path/to/module.py
-
-Catch unexpected mutations, missing methods, or API drift as part of CI.
-
-**3. Before Deployment 🚀**
-
-Use ImportSpy to verify:
-
-- Environment constraints (OS, Python, interpreter)
-- Runtime assumptions (env vars, module-level variables)
-- Plugin compliance across distributed services
-
-✅ If everything matches the contract, continue.
-❌ If anything is wrong, block the deploy.
-
-Supported CI/CD Platforms
---------------------------
-
-ImportSpy is CI-native and works anywhere:
-
-- **GitHub Actions**
- Add a step before your test matrix or deployment job.
-
-- **GitLab CI**
- Use it in before_script or as a job stage.
-
-- **CircleCI / Jenkins**
- Run via shell or Python-based jobs.
-
-- **Docker / Kubernetes**
- Validate plugins or runtime images before deployment.
-
-- **Legacy or VM pipelines**
- Enforce stability even in less dynamic stacks.
-
-Minimal Example for GitHub Actions
-----------------------------------
-
-.. code-block:: yaml
-
- - name: Validate Plugin
- run: |
- pip install importspy
- importspy -s spymodel.yml extension.py
-
-Any contract violations will raise a `ValueError` and halt the build.
-
-Security Benefits
-------------------
-
-ImportSpy also strengthens your **software supply chain**:
-
-- Blocks unexpected or tampered code
-- Prevents unauthorized plugin registration
-- Confirms that runtime conditions are exactly what you expect
-- Complements tools like `pip-audit`, `bandit`, or SAST engines
-
-Think of it as **import-time policy enforcement**, directly in your build.
-
-Best Practices for Integration
-------------------------------
-
-- 🔐 Treat ImportSpy as a **quality gate**, not just a linter
-- 💥 Use `-l ERROR` log level to fail fast and get clear diagnostics
-- 🔁 Keep contracts under version control with your code
-- 🧪 Validate early, not just at release
-- 🧭 Use strict contracts in production, relaxed ones in dev/test
-
-Related Topics
---------------
-
-- :doc:`contract_structure` – How to write import contracts
-- :doc:`external_mode` – Running validation externally
-- :doc:`spy_execution_flow` – See what happens during validation
-
-Summary
--------
-
-ImportSpy turns fragile CI pipelines into **predictable safety systems**.
-
-It guarantees that:
-
-- ✅ Every module is structurally sound
-- ✅ Every environment matches your expectations
-- ✅ Every build is trustworthy
-
-No more surprises. No more silent regressions.
-Just clean, validated, future-proof Python — every time you deploy.
diff --git a/docs/source/overview/understanding_importspy/contract_structure.rst b/docs/source/overview/understanding_importspy/contract_structure.rst
deleted file mode 100644
index 3b47b8f..0000000
--- a/docs/source/overview/understanding_importspy/contract_structure.rst
+++ /dev/null
@@ -1,176 +0,0 @@
-Import Contract Structure
-==========================
-
-Import contracts are the foundation of how ImportSpy performs validation.
-
-They are **YAML-based configuration files** that describe both the **structure** of a Python module and the **execution environments** in which it is valid.
-
-This page provides a deep dive into the schema, semantics, and flexibility of import contracts — and how they serve as **executable specifications** for modular systems.
-
-Overview
---------
-
-Each contract is made up of two primary blocks:
-
-- **Module definition**: describes what the module must contain (e.g., classes, functions, metadata)
-- **Deployments**: lists the environments (OS, Python, interpreter) in which the module is allowed to run
-
-Contracts can define either:
-
-- **Global constraints**: structural requirements that apply to all deployments
-- **Deployment-specific overrides**: context-sensitive rules based on platform or interpreter
-
-Top-Level Schema
-----------------
-
-Here is a reference of the main fields in a contract:
-
-Top-Level Fields
-~~~~~~~~~~~~~~~~
-
-- ``filename`` *(str)*: The name of the module to validate
-- ``version`` *(str, optional)*: Expected module version (e.g., `__version__`)
-- ``variables`` *(dict)*: Top-level variables and their expected values
-- ``functions`` *(list)*: List of required functions
-- ``classes`` *(list)*: List of required classes and their details
-- ``deployments`` *(list)*: Permitted environments in which the module can be loaded
-
-Function Schema
-~~~~~~~~~~~~~~~
-
-Each function can define:
-
-- ``name``: Function name
-- ``arguments``: List of parameter names and optional annotations
-- ``return_annotation``: Optional return type annotation
-
-Class Schema
-~~~~~~~~~~~~
-
-Each class may include:
-
-- ``name``: Class name
-- ``attributes``:
- - ``type``: `"instance"` or `"class"`
- - ``name``: Attribute name
- - ``value``: Expected value
- - ``annotation``: Optional type annotation
-- ``methods``: Follows the same format as functions
-- ``superclasses``: List of required base classes
-
-Deployments Block
-------------------
-
-The ``deployments`` section defines runtime compatibility requirements:
-
-- ``arch``: CPU architecture (e.g., `x86_64`, `arm64`)
-- ``systems``: list of operating systems and environment constraints
- - ``os``: `linux`, `windows`, or `macos`
- - ``envs``: Required environment variables (`KEY: value`)
- - ``pythons``: Supported Python versions and interpreters
- - ``version``: Python version (e.g., `3.12.8`)
- - ``interpreter``: `CPython`, `PyPy`, etc.
- - ``modules``: Nested module definitions required in that context
-
-Global Module Definition (Baseline)
------------------------------------
-
-If you define a module structure **outside the `deployments:` section**,
-it acts as a **global baseline** — a structural requirement that applies to **all environments**.
-
-This section can include:
-
-- ``filename``, ``variables``, ``functions``, ``classes``
-- Shared interface contracts across platforms
-- The minimum valid structure for the module to ever be imported
-
-.. note::
- This is semantically treated as a **lower bound**:
- each deployment must satisfy the global structure plus any deployment-specific overrides.
-
-Full Example
-------------
-
-Here is a complete import contract:
-
-.. code-block:: yaml
-
- filename: extension.py
- variables:
- engine: docker
- plugin_name: plugin name
- plugin_description: 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
- - name: add_extension
- arguments:
- - name: self
- - name: msg
- annotation: str
- return_annotation: str
- - name: remove_extension
- arguments:
- - name: self
- - name: http_get_request
- arguments:
- - name: self
- superclasses:
- - Plugin
- - name: Foo
- methods:
- - name: get_bar
- arguments:
- - name: self
- deployments:
- - arch: x86_64
- systems:
- - os: windows
- pythons:
- - version: 3.12.8
- interpreter: CPython
- modules:
- - filename: extension.py
- variables:
- author: Luca Atella
- - version: 3.12.4
- modules:
- - filename: addons.py
- - interpreter: IronPython
- modules:
- - filename: addons.py
- - os: linux
- pythons:
- - version: 3.12.8
- interpreter: CPython
- modules:
- - filename: extension.py
- variables:
- author: Luca Atella
-
-Validation Behavior
---------------------
-
-- All fields are **optional**, but the **hierarchy must be respected**
-- Missing fields are simply skipped during validation
-- Order of items in lists (methods, attributes) is **not enforced**
-- Contracts are parsed into `SpyModel` objects during validation
-- Validation is consistent across both embedded and CLI modes
-
-Related Topics
---------------
-
-- :doc:`defining_import_contracts`
-- :doc:`spy_execution_flow`
-- :doc:`embedded_mode`
-- :doc:`external_mode`
diff --git a/docs/source/overview/understanding_importspy/defining_import_contracts.rst b/docs/source/overview/understanding_importspy/defining_import_contracts.rst
deleted file mode 100644
index 3c858e8..0000000
--- a/docs/source/overview/understanding_importspy/defining_import_contracts.rst
+++ /dev/null
@@ -1,156 +0,0 @@
-Defining Import Contracts
-==========================
-
-Import contracts are at the core of how ImportSpy enforces structural and runtime compliance.
-
-They are **YAML-based declarations** that describe exactly how a Python module is expected to behave — not in terms of logic, but in terms of **structure, compatibility, and execution context**.
-
-This page explains how to define contracts, what they contain, and how ImportSpy uses them to validate modules at import time.
-
-What Are Import Contracts?
---------------------------
-
-An import contract tells ImportSpy:
-
-- 🔧 What a module **must contain** (functions, classes, attributes, variables)
-- 🧠 Where it **is allowed to run** (Python version, OS, architecture, interpreter)
-- 🔐 What **runtime conditions** must be met (environment variables, deployment setups)
-
-ImportSpy uses this contract to decide:
-> “Should I allow this module to be used — or block it immediately?”
-
-Contracts are used in both validation modes:
-
-- **Embedded mode**: the validated module validates the importer at runtime
-- **CLI mode**: a module is validated externally via `importspy -s contract.yml module.py`
-
-When to Use Them
-----------------
-
-Use import contracts when:
-
-- You need to **block incompatible modules** from being loaded
-- You want to define **clear structure and interface expectations**
-- Your code must **run only on certain platforms or interpreters**
-- You’re building a system with **extensible plugins or dynamic modules**
-- You need **environment-aware validation** during CI/CD or deployment
-
-Import Contract Anatomy
-------------------------
-
-Import contracts follow a hierarchical schema, with two main areas:
-
-Structure Requirements
-~~~~~~~~~~~~~~~~~~~~~~
-
-This defines what the module must expose:
-
-- `filename`: expected module file name
-- `variables`: global-level constants or metadata
-- `functions`: expected functions (with arguments and annotations)
-- `classes`: expected classes, with methods, attributes, and superclasses
-
-Deployment Constraints
-~~~~~~~~~~~~~~~~~~~~~~
-
-This defines **where and under what conditions** the module is valid:
-
-- `arch`: expected CPU architecture (e.g., `x86_64`, `arm64`)
-- `systems`: list of supported OS/platform combinations
- - `os`: operating system (e.g., `linux`, `windows`)
- - `envs`: required environment variables
- - `pythons`: Python runtime environments
- - `version`, `interpreter` (e.g., `CPython`, `PyPy`)
- - `modules`: nested modules required in that Python env
-
-Contracts can declare **multiple deployments**, supporting flexibility while enforcing strict constraints.
-
-Baseline Constraints (Global Scope)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you define module-level fields **outside the `deployments:` section**,
-they act as **baseline constraints** that apply to **all runtime environments**.
-
-This is useful when:
-
-- You want to enforce a shared structure across multiple deployments
-- The module must always expose certain classes, functions, or variables
-- You need a "lowest common denominator" for all environments
-
-.. note::
- These top-level rules define a **lower bound** for compliance —
- each `deployment` can extend or specialize these expectations,
- but never violate or ignore the top-level contract.
-
-Minimal Example
-----------------
-
-Here’s a basic contract example:
-
-.. code-block:: yaml
-
- filename: extension.py
- variables:
- plugin_name: core
- classes:
- - name: Extension
- attributes:
- - type: class
- name: extension_name
- value: core
- methods:
- - name: __init__
- arguments:
- - name: self
- - name: run
- arguments:
- - name: self
- - name: data
- annotation: str
- return_annotation: str
- deployments:
- - arch: x86_64
- systems:
- - os: linux
- envs:
- ENV: production
- pythons:
- - version: 3.12.8
- interpreter: CPython
- modules:
- - filename: extension.py
- variables:
- author: Luca Atella
-
-This contract:
-
-- Requires a class `Extension` with specific methods and attributes
-- Enforces Linux OS, CPython 3.12.8, and a production environment
-- Declares `extension.py` as the expected module file
-- Requires a top-level variable `plugin_name`
-
-Design Principles
-------------------
-
-- Contracts are **declarative**: no logic, only structure and expectations
-- All fields are optional — but if declared, they **must be met**
-- Lists like `classes`, `methods`, and `attributes` are **order-independent**
-- Contracts are parsed once per session for performance
-- Contracts can express **baseline rules** (outside deployments) or per-environment logic
-
-Best Practices
---------------
-
-- Start minimal: validate structure first, then layer on environment constraints
-- Version your contracts alongside your code — they are **enforceable documentation**
-- Use deployments to support different runtime contexts while keeping control
-- Always define `filename` — it's the root entry point for validation
-
-What’s Next?
--------------
-
-Now that you understand how to define contracts:
-
-- See how ImportSpy executes validation in :doc:`spy_execution_flow`
-- Explore common validation patterns in the :doc:`validation_and_compliance` section
-- Learn how contracts behave in :doc:`embedded_mode` and :doc:`external_mode`
diff --git a/docs/source/overview/understanding_importspy/embedded_mode.rst b/docs/source/overview/understanding_importspy/embedded_mode.rst
deleted file mode 100644
index a6c6801..0000000
--- a/docs/source/overview/understanding_importspy/embedded_mode.rst
+++ /dev/null
@@ -1,127 +0,0 @@
-Embedded Mode
-=============
-
-Embedded mode allows a Python module to **protect itself** at runtime.
-
-Unlike external validation, where checks are triggered from outside, embedded mode runs ImportSpy **from within the module**,
-verifying whether the environment that imported it complies with a declared contract.
-
-It’s a powerful mechanism to ensure that **your module only runs in safe, predictable, and validated contexts**.
-
-What Is Embedded Validation?
------------------------------
-
-In embedded mode, the validated module:
-
-- ✅ Includes ImportSpy directly in its own code
-- ✅ Loads a local `.yml` import contract (e.g., `spymodel.yml`)
-- ✅ Introspects the caller (who is importing it)
-- ✅ Validates the **importing environment**, not itself
-- ❌ Refuses to execute if validation fails
-
-This is ideal for:
-
-- Plugins in plugin-based architectures
-- Shared packages used across teams or platforms
-- Sensitive modules with **runtime assumptions** (OS, interpreter, env vars)
-- Security-hardened components
-
-How It Works
-------------
-
-Here’s the execution flow:
-
-1. 🧠 The module runs `Spy().importspy(...)` when imported
-2. 📁 It parses its import contract (`spymodel.yml`)
-3. 👀 It introspects the **importing module** (via stack trace)
-4. 🔍 The importing context is matched against the contract:
- - OS, CPU, Python version, interpreter
- - Required env vars
- - Module structure and metadata
-5. ❌ If validation fails, a `ValueError` is raised and execution is blocked
-6. ✅ If validation passes, the importing module is returned and can be used programmatically
-
-🔒 This creates a **Zero-Trust contract gate** — your module is only usable when the importing context is compliant.
-
-Example Usage
---------------
-
-Inside your protected module (e.g., `package.py`):
-
-.. code-block:: python
-
- from importspy import Spy
- import logging
-
- caller_module = Spy().importspy(filepath="spymodel.yml", log_level=logging.DEBUG)
-
- # You now have access to the validated importer
- caller_module.Foo().get_bar()
-
-Minimal Contract Example
--------------------------
-
-Here’s a simplified import contract for embedded validation:
-
-.. code-block:: yaml
-
- filename: extension.py
- variables:
- plugin_name: my_plugin
- classes:
- - name: Extension
- methods:
- - name: run
- arguments:
- - name: self
- deployments:
- - arch: x86_64
- systems:
- - os: linux
- pythons:
- - version: 3.12.8
- interpreter: CPython
-
-This contract says:
-
-- Only modules named `extension.py`
-- With a class `Extension` containing a `run(self)` method
-- Are allowed to import this module
-- Only on Linux + x86_64 + Python 3.12.8 + CPython
-
-If even one condition is not satisfied, execution is halted immediately.
-
-Why Use Embedded Mode?
------------------------
-
-- ✅ The module validates **who is importing it**
-- ✅ Ensures runtime safety without relying on external checks
-- ✅ Makes plugins and extensions **self-defensive**
-- ✅ Protects against unverified execution contexts in dynamic systems
-- ✅ Integrates smoothly into plugin registries or dynamic loaders
-
-Best Practices
---------------
-
-- Always run embedded validation **at the top** of your module
-- Version control both the module and its contract together
-- Use detailed contracts in production, relaxed ones in dev/test
-- Log validation steps using `log_level=logging.DEBUG` for traceability
-
-Comparison to External Mode
-----------------------------
-
-Use embedded mode when:
-
-- You want **tight control over where your module is used**
-- You are building a **plugin** or **shared extension**
-- You need to **validate the importing environment**, not just structure
-
-Use :doc:`external_mode` when you want to validate a module from the outside (e.g., in CI/CD).
-
-Related Topics
---------------
-
-- :doc:`contract_structure` – Learn how to define rich, nested import contracts
-- :doc:`spy_execution_flow` – Understand how validation works under the hood
-- :doc:`external_mode` – External validation for static and pipeline use cases
diff --git a/docs/source/overview/understanding_importspy/error_handling.rst b/docs/source/overview/understanding_importspy/error_handling.rst
deleted file mode 100644
index f06649b..0000000
--- a/docs/source/overview/understanding_importspy/error_handling.rst
+++ /dev/null
@@ -1,153 +0,0 @@
-Error Handling in ImportSpy
-============================
-
-Validation errors are not failures — they are **enforced expectations**.
-
-ImportSpy treats every contract violation as a **signal**, not just a disruption.
-Its error system is designed to be **precise, informative, and traceable**,
-helping developers identify and resolve problems early, consistently, and with confidence.
-
-Why Structured Errors Matter
-----------------------------
-
-In complex Python systems, especially those using plugins, microservices, or dynamic loading,
-errors can be vague and hard to reproduce.
-
-ImportSpy solves this by generating:
-
-- 🧠 **Human-readable messages** with contextual hints
-- 🧩 **Categorized errors**, sorted by validation layer
-- 🛠️ **Diagnostic templates** that identify the cause and expected structure
-- 🔎 **Traceable exceptions**, usable in CLI, IDE, or CI pipelines
-
-Whether you’re debugging a failing import or enforcing a strict policy in production,
-ImportSpy makes validation feedback **clear, consistent, and useful**.
-
-Error Categories
------------------
-
-ImportSpy groups errors into well-defined categories to simplify resolution:
-
-Missing Elements
-~~~~~~~~~~~~~~~~
-
-Raised when a required function, class, attribute, or variable is **not present**.
-
-Example:
-`Missing method in class Extension: 'run'`
-
-Type or Value Mismatch
-~~~~~~~~~~~~~~~~~~~~~~~
-
-Triggered when types, annotations, or literal values do not match.
-
-Example:
-`Return type mismatch: expected 'str', found 'None'`
-
-Environmental Misconfiguration
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Raised when runtime assumptions are unmet, such as:
-
-- Missing environment variables
-- Incompatible OS or interpreter
-- Python version mismatch
-
-Example:
-`Missing required environment variable: API_KEY`
-
-Unsupported Runtime
-~~~~~~~~~~~~~~~~~~~
-
-Validation fails if the runtime environment does not match any declared `deployment`.
-
-Example:
-`Unsupported Python version: 3.7. Expected: 3.12.8`
-
-The goal of these categories is to **pinpoint root causes** and prevent regression over time.
-
-Reference Error Table
-----------------------
-
-All known validation errors are defined in a centralized table:
-
-.. include:: error_table.rst
-
-Each entry includes:
-
-- A symbolic error constant (e.g., `Errors.CLASS_ATTRIBUTE_MISSING`)
-- A dynamic message template
-- A short description and suggested resolution
-
-These errors are **reused consistently across embedded and CLI validation**.
-
-Enforcement Strategies
------------------------
-
-ImportSpy enforces contracts in strict mode by default:
-
-Strict Mode (Default)
-~~~~~~~~~~~~~~~~~~~~~~
-
-- ❌ Any error raises a `ValueError`
-- ⛔ Execution halts immediately
-- 🔐 Recommended for CI/CD, production, and regulated systems
-
-Soft Mode (Future Feature)
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-- ⚠️ Errors are downgraded to warnings
-- 🔁 Execution proceeds
-- 🧪 Ideal for development, onboarding, or exploratory validation
-
-Traceability and Debugging
----------------------------
-
-Every raised exception includes:
-
-- The failing **element** (function, class, attribute, etc.)
-- The **context** of the violation (e.g., deployment block or module scope)
-- The **expected vs actual** values/types
-
-Thanks to integrated logging (`LogManager`) and specific exception classes (`ValidationError`, `PersistenceError`),
-ImportSpy ensures traceability across:
-
-- Local debugging
-- Containerized runtimes
-- CI pipelines
-- Logging dashboards
-
-Developer-Focused Feedback
----------------------------
-
-Validation errors are formatted to be helpful across:
-
-- Terminal sessions and shell scripts
-- IDE consoles with embedded validation
-- CI output logs for quality gates or metrics
-
-If you're using ImportSpy in CLI mode, errors are printed with full detail —
-ready to be parsed, logged, or even turned into automated reports.
-
-Best Practices
---------------
-
-- ✅ Run with `--log-level DEBUG` to get full trace on failure
-- ✅ Keep `spymodel.yml` in version control and in sync with module changes
-- ✅ Use error messages as checklists during onboarding or code review
-- ✅ Integrate the error table into your internal docs or linting rules
-
-Errors Are Enforced Contracts
-------------------------------
-
-ImportSpy’s validation model is **contract-first** — if a rule is declared, it’s enforced.
-
-That means errors are not just problems — they’re confirmations that the system is working.
-By treating every validation failure as a source of insight, ImportSpy helps your team:
-
-- Identify problems early
-- Understand context clearly
-- Move toward stronger modularity and runtime safety
-
-📌 Errors are not interruptions.
-They are the **boundary where safety begins**.
diff --git a/docs/source/overview/understanding_importspy/error_table.rst b/docs/source/overview/understanding_importspy/error_table.rst
deleted file mode 100644
index 12f11e1..0000000
--- a/docs/source/overview/understanding_importspy/error_table.rst
+++ /dev/null
@@ -1,42 +0,0 @@
-.. list-table:: ImportSpy Validation Errors
- :widths: 30 70
- :header-rows: 1
-
- * - **Error Type**
- - **Description**
- * - `Missing Elements`
- - A required **function**, **class**, **method**, or **attribute** is not found in the module or structure defined in the import contract.
- * - `Type Mismatch`
- - A return annotation, argument type, or class attribute type does **not match** the one declared in the contract.
- * - `Value Mismatch`
- - A variable or attribute exists but has a **different value** than expected (e.g., metadata mismatch).
- * - `Function Argument Mismatch`
- - A function's arguments do **not match in name, annotation, or default values**.
- * - `Function Return Type Mismatch`
- - The return type annotation of a function differs from the contract.
- * - `Class Missing`
- - A required class is **absent** from the module.
- * - `Class Attribute Missing`
- - One or more declared **class or instance attributes** are missing.
- * - `Class Attribute Type Mismatch`
- - A class attribute exists, but its **type or annotation** differs from what is expected.
- * - `Superclass Mismatch`
- - A class does not inherit from one or more required **superclasses** as declared.
- * - `Variable Missing`
- - A required **top-level variable** (e.g., `plugin_name`) is not defined in the module.
- * - `Variable Value Mismatch`
- - A variable exists but its value does not match the one declared in the contract.
- * - `Filename Mismatch`
- - The actual filename of the module differs from the one declared in `filename`.
- * - `Version Mismatch`
- - The module’s `__version__` (if defined) differs from the expected version.
- * - `Unsupported Operating System`
- - The current OS is **not included** in the allowed platforms (e.g., Linux, Windows, macOS).
- * - `Missing Required Runtime`
- - A required **architecture, OS, or interpreter version** is not satisfied.
- * - `Unsupported Python Interpreter`
- - The current interpreter (e.g., CPython, PyPy, IronPython) is not supported by the contract.
- * - `Missing Environment Variable`
- - A declared environment variable is **not present** in the current context.
- * - `Invalid Environment Variable`
- - An environment variable exists but contains an **unexpected value**.
diff --git a/docs/source/overview/understanding_importspy/external_mode.rst b/docs/source/overview/understanding_importspy/external_mode.rst
deleted file mode 100644
index 8aee7b5..0000000
--- a/docs/source/overview/understanding_importspy/external_mode.rst
+++ /dev/null
@@ -1,119 +0,0 @@
-External Mode
-=============
-
-External mode allows you to use ImportSpy as a **standalone validator**, without embedding any logic in the module being validated.
-
-It’s ideal for teams who want to enforce structure and runtime compliance from the outside —
-during **CI checks**, **code review gates**, or **manual inspections** of dynamic modules, plugins, or extensions.
-
-What Is External Validation?
-----------------------------
-
-In this mode, ImportSpy runs from the command line and:
-
-- Loads the target module dynamically
-- Parses a separate **YAML import contract**
-- Validates the module’s **structure**, **metadata**, and **runtime compatibility**
-- Blocks execution if any contract rule is violated
-
-It’s perfect for use cases where **you don’t own the module**, or want to **validate before running anything at all**.
-
-Typical Use Cases
-------------------
-
-- ✅ Pre-deployment contract checks in CI/CD pipelines
-- ✅ Validating plugins before registering them in a host application
-- ✅ Enforcing runtime assumptions for sandboxed or remote code
-- ✅ Auditing third-party extensions for structural and environmental compliance
-
-How to Use It
--------------
-
-1. Write your import contract (usually `spymodel.yml`):
-
-.. code-block:: yaml
-
- filename: extension.py
- classes:
- - name: Extension
- methods:
- - name: run
- arguments:
- - name: self
-
-2. Run the validation using the ImportSpy CLI:
-
-.. code-block:: bash
-
- importspy -s spymodel.yml -l DEBUG extension.py
-
-This will:
-
-- Load `extension.py`
-- Parse `spymodel.yml`
-- Validate all structure, types, env vars, OS, interpreter, and Python version
-- Print any errors or mismatches to the terminal
-- Exit with an error if validation fails
-
-Full CLI Reference
--------------------
-
-.. code-block:: text
-
- Usage: importspy [OPTIONS] [MODULEPATH]
-
- CLI command to validate a Python module against a YAML-defined import contract.
-
- Arguments:
- modulepath Path to the Python module to load and validate.
-
- Options:
- --version, -v Show ImportSpy 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.
- --install-completion Install completion for the current shell.
- --show-completion Output shell snippet for autocompletion.
- --help Show this message and exit.
-
-How External Validation Works 🔍
---------------------------------
-
-Here’s what happens under the hood:
-
-1. 📥 **Contract is loaded** → Parsed from YAML into an internal `SpyModel`
-2. 🧠 **Module is dynamically loaded** → No execution is triggered, just inspection
-3. 🏗️ **Structure is reconstructed** → Classes, methods, attributes, annotations, etc.
-4. 🌐 **Runtime context is gathered** → OS, architecture, interpreter, Python version, env vars
-5. ⚖️ **Contract is evaluated** → Actual vs expected values are compared deeply
-6. ❌ **Violations are raised** → A detailed `ValueError` is thrown with full diagnostics
-
-All of this happens **before any code is executed**, ensuring a safe, validated runtime context.
-
-Best Practices 🧪
------------------
-
-- Keep `.yml` contracts **under version control**
-- Integrate into **CI/CD** to block broken modules from reaching production
-- Use `--log-level DEBUG` to get full trace information when testing
-- Validate all external plugins **before dynamic loading**
-- Combine with :doc:`contract_structure` for clean, declarative specs
-
-Comparison to Embedded Mode
-----------------------------
-
-External mode:
-
-- ✅ Validates modules **without modifying them**
-- ✅ Decouples validation logic from business logic
-- ✅ Ideal for **automated pipelines** and **security reviews**
-
-If you want the **imported module to enforce rules about its importer**,
-see :doc:`embedded_mode`.
-
-Related Topics
---------------
-
-- :doc:`contract_structure` – Full breakdown of contract syntax and nesting
-- :doc:`spy_execution_flow` – Internals of validation lifecycle
-- :doc:`embedded_mode` – For runtime protection from inside the validated module
diff --git a/docs/source/overview/understanding_importspy/integration_best_practices.rst b/docs/source/overview/understanding_importspy/integration_best_practices.rst
deleted file mode 100644
index ee975e9..0000000
--- a/docs/source/overview/understanding_importspy/integration_best_practices.rst
+++ /dev/null
@@ -1,144 +0,0 @@
-Integration Best Practices
-===========================
-
-ImportSpy is most powerful when it’s seamlessly integrated into your development lifecycle.
-It acts as a **structural firewall** — ensuring that Python modules are only executed in validated environments,
-with predictable interfaces and runtime guarantees.
-
-To get the most out of ImportSpy, it’s essential to follow practices that promote **clarity, maintainability**,
-and long-term compliance.
-
-Contract Design Principles
----------------------------
-
-A good import contract is:
-
-- 🧠 **Readable** → Easy to understand by developers and reviewers
-- 🔁 **Reusable** → Avoids repetition by isolating shared environments
-- 🔧 **Maintainable** → Easy to update as your system evolves
-- 🎯 **Targeted** → Matches how your code is actually deployed, not just idealized setups
-
-Design your contracts with these goals in mind — and treat them as part of your project’s architecture.
-
-Modularize Your Contracts 🧱
-----------------------------
-
-Avoid monolithic `.yml` files with everything mixed together.
-
-Instead:
-
-- Create **separate `deployments:` blocks** for each OS, architecture, or runtime
-- Group constraints based on **real deployment contexts** (e.g., CI, Docker, staging)
-- Keep **top-level structure global**, and specialize deeper in deployment-specific modules
-- Use **baseline declarations** to enforce a minimum structure across all environments
-
-This makes your contracts scalable, and keeps them aligned with your actual execution model.
-
-Match Contracts to Real Environments ⚙️
-----------------------------------------
-
-Don't write constraints that don't reflect reality.
-
-- ✅ In production: lock down OS, interpreter, variables, and structure
-- ⚠️ In development: relax constraints slightly for flexibility
-- 🧪 In CI: validate structure early, fail fast, and log everything
-
-ImportSpy’s power comes from accuracy — so your contract should describe **how your code really runs**, not how you wish it did.
-
-Reduce Duplication 🔁
-----------------------
-
-Avoid repeating validation rules between modules.
-
-Strategies:
-
-- Define shared `deployments:` blocks and reuse them across multiple contracts
-- Use generation tools to inject common blocks
-- Extract reusable parts (e.g., shared classes or envs) into templated components
-
-Keeping contracts DRY improves maintainability and reduces the chance of silent mismatches.
-
-Structure Contracts Clearly 🏗️
--------------------------------
-
-A recommended hierarchy:
-
-1. `filename`, `version`, and top-level `variables`
-2. `functions` (optional, with argument specs)
-3. `classes`, with:
- - `attributes` (instance/class, with optional types and values)
- - `methods`, defined like functions
- - `superclasses`
-4. `deployments`, each with:
- - `arch`, `os`, and optional `envs`
- - `pythons`, with version/interpreter/modules
- - `modules`, repeating structure at the environment level if needed
-
-This structure allows deep validation of runtime-specific behavior.
-
-Validate Where It Matters 🌍
-----------------------------
-
-ImportSpy is perfect for verifying:
-
-- Plugins or dynamic modules running in cloud or hybrid environments
-- Modules that depend on env-specific config (e.g., secrets, endpoints)
-- Microservices where drift between containers or hosts can break integrations
-
-Explicitly declare:
-
-- OS and architecture
-- Required environment variables
-- Supported Python versions and interpreters
-
-Consistency across environments starts with precise expectations.
-
-Embed ImportSpy in Your Pipeline 🧪
------------------------------------
-
-Use the CLI tool during CI builds and before deployments:
-
-.. code-block:: bash
-
- importspy -s spymodel.yml -l DEBUG path/to/module.py
-
-Fail the build if the contract isn't satisfied.
-This catches integration issues **before** they reach staging or production.
-
-Consider combining ImportSpy with:
-
-- Linting (e.g., `ruff`, `flake8`)
-- Typing (e.g., `mypy`)
-- Unit and integration tests
-- Security scanners
-
-ImportSpy adds **structural guarantees** on top of these tools.
-
-Choose Enforcement Mode Strategically 👥
-----------------------------------------
-
-ImportSpy supports strict and soft enforcement:
-
-- **Strict Mode** → Violations raise `ValueError`. Use in CI and production.
-- **Debug Logging** → Add `--log-level DEBUG` to trace without halting execution.
-- **Soft Mode** (planned) → Logs validation failures as warnings. Ideal for onboarding or dry runs.
-
-Adapt validation levels to your team's tolerance for risk and your deployment maturity.
-
-Final Advice 🎯
----------------
-
-ImportSpy is not a replacement for testing — it complements it.
-
-It ensures your modules are used **only where and how they’re meant to be**,
-preventing drift, mismatches, and unexpected runtime behavior.
-
-To integrate ImportSpy effectively:
-
-- 📁 Keep contracts clean and modular
-- 🔄 Update them alongside the code they protect
-- ⚙️ Match them to real-world runtimes
-- 🚦 Automate validation in CI/CD
-- 🔐 Use strict enforcement in trusted production pipelines
-
-ImportSpy helps you build **modular, secure, and future-proof Python systems** — one contract at a time.
diff --git a/docs/source/overview/understanding_importspy/introduction.rst b/docs/source/overview/understanding_importspy/introduction.rst
deleted file mode 100644
index 1b5dfab..0000000
--- a/docs/source/overview/understanding_importspy/introduction.rst
+++ /dev/null
@@ -1,113 +0,0 @@
-Introduction to ImportSpy
-==========================
-
-Welcome to the core introduction to **ImportSpy** —
-a validation and compliance framework that transforms Python's dynamic import system into a structured, predictable process.
-
-ImportSpy enables developers to define **executable contracts** that external modules must follow.
-These contracts describe not only a module's expected structure, but also its **execution environment**, including:
-
-- Python version
-- Interpreter type (e.g., CPython, PyPy, IronPython)
-- Operating system
-- Required classes, functions, variables
-- Environment variables and metadata
-
-If the contract is not respected — the module doesn’t load.
-It’s as simple and powerful as that.
-
-What Problem Does ImportSpy Solve? 🚧
--------------------------------------
-
-Python is known for flexibility — but that comes at a cost:
-
-- Modules can silently drift from expected interfaces
-- Plugin systems can misbehave if assumptions aren’t validated
-- Deployment environments may differ in subtle, breaking ways
-- Runtime errors often appear too late, and debugging them is slow and painful
-
-ImportSpy introduces **import-time validation**, enforcing that:
-
-✅ A module’s structure is as expected
-✅ Its runtime context matches predefined constraints
-✅ Violations are caught **before execution** begins
-
-The result? Safer systems, clearer boundaries, and predictable integrations.
-
-What Are Import Contracts? 📜
-------------------------------
-
-Import contracts are YAML-based documents that define the rules a module must follow to be considered valid.
-
-They declare:
-
-- Required classes, methods, and attributes
-- Module-level variables and metadata
-- Expected Python interpreter, version, OS, and CPU architecture
-- Environmental assumptions (e.g., required env vars)
-
-At runtime, ImportSpy parses these contracts and validates them **against the actual module and environment**.
-These contracts serve as:
-
-- 🔍 Executable specifications
-- 📖 Documentation for expected interfaces
-- 🛡️ Runtime validation logic
-
-The result is a **formal, testable boundary** between modules — especially in dynamic systems like plugin frameworks.
-
-Validation Modes Supported 🔁
-------------------------------
-
-ImportSpy supports two complementary modes of validation:
-
-.. list-table::
- :widths: 25 75
- :header-rows: 1
-
- * - Mode
- - Description
- * - Embedded Mode
- - The core module validates the structure of the **importer**.
- Useful in plugin architectures where the base module ensures it is being imported in a safe, compliant context.
- * - CLI Mode
- - Validation is performed externally via the command line.
- Ideal for pipelines, static checks, and CI/CD integration.
-
-This flexibility allows ImportSpy to adapt to **runtime and pre-deployment validation scenarios** with equal precision.
-
-What You’ll Learn in This Documentation 📘
-------------------------------------------
-
-This documentation will guide you through:
-
-- 🚀 How ImportSpy works and why it matters
-- 🛠️ How to define and apply import contracts
-- 🔄 How validation is triggered in both modes
-- 🧪 Real-world examples with plugins and pipelines
-- ⚙️ Best practices for integration in production systems
-- 🔐 Security benefits and enforcement patterns
-- 💼 How to use ImportSpy in automated CI/CD workflows
-
-Installing ImportSpy
-----------------------
-
-To get started, install ImportSpy using pip:
-
-.. code-block:: bash
-
- pip install importspy
-
-Then visit:
-
-- :doc:`../../get_started/installation` to set up your environment
-- :doc:`../../get_started/example_overview` to run your first validated example
-
-Let’s Get Started 🚀
----------------------
-
-ImportSpy turns Python’s imports into a **secure contract** — not just a hope for compatibility.
-
-By shifting validation **before execution**, it empowers developers to build modular, extensible, and production-safe Python systems.
-
-Ready to unlock the next level of confidence in your code?
-Start by defining your first import contract and let ImportSpy take care of the rest.
diff --git a/docs/source/overview/understanding_importspy/spy_execution_flow.rst b/docs/source/overview/understanding_importspy/spy_execution_flow.rst
deleted file mode 100644
index 2e6cc94..0000000
--- a/docs/source/overview/understanding_importspy/spy_execution_flow.rst
+++ /dev/null
@@ -1,115 +0,0 @@
-Spy Execution Flow
-===================
-
-At the heart of ImportSpy lies a powerful validation engine that activates the moment an import occurs.
-
-Whether you're using **embedded mode** or **CLI validation**, ImportSpy reconstructs a full picture of the runtime environment, compares it to your declared import contract, and enforces compliance **before execution begins**.
-
-This page explains, step by step, how ImportSpy processes a validation request — from **introspection to enforcement**.
-
-Overview
---------
-
-ImportSpy enforces a simple but strict rule:
-
-> ❌ If the importing environment does not match the contract, the module is blocked.
-> ✅ If the environment is compliant, execution proceeds normally.
-
-This model brings **predictability and control** to Python's otherwise dynamic import system.
-
-Execution Modes Supported
---------------------------
-
-ImportSpy works in two execution modes:
-
-- :doc:`Embedded Mode ` → The validated module runs `importspy()` internally to inspect its importer
-- :doc:`External Mode ` → Validation is triggered via CLI, often in CI/CD or static validation steps
-
-Execution Pipeline
-------------------
-
-Here’s how ImportSpy validates imports, broken down into phases:
-
-1. Detect the Importer
-~~~~~~~~~~~~~~~~~~~~~~
-
-ImportSpy uses **stack introspection** to determine which module is importing the validated one.
-This lets it establish a **validation boundary**, isolating the exact caller and its context.
-
-2. Capture Runtime Context
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Once the importer is found, ImportSpy collects runtime data:
-
-- Current OS and CPU architecture
-- Python version and interpreter type
-- Available environment variables
-- Installed Python modules and paths
-
-This snapshot is encoded into an internal `SpyModel` object, representing the actual runtime conditions.
-
-3. Parse the Contract
-~~~~~~~~~~~~~~~~~~~~~
-
-Next, ImportSpy loads and parses the YAML-based import contract (typically `spymodel.yml`).
-
-This creates a second `SpyModel` representing the **expected structure and execution environment**.
-
-4. Compare & Validate
-~~~~~~~~~~~~~~~~~~~~~
-
-ImportSpy performs a deep comparison between the **actual SpyModel** and the **expected SpyModel**.
-
-Validation includes:
-
-- Matching CPU architecture and operating system
-- Checking Python version and interpreter type
-- Verifying required environment variables
-- Matching declared functions, classes, methods, and attributes
-- Validating module variables and custom metadata
-- Checking nested modules and deployment-specific rules
-
-5. Enforce or Reject
-~~~~~~~~~~~~~~~~~~~~~
-
-- If the validation **fails**:
- - ImportSpy raises a `ValueError`
- - Detailed diagnostics are included in the exception
- - Execution is halted immediately
-
-- If the validation **passes**:
- - The validated importer is returned (in embedded mode)
- - Execution proceeds safely
-
-6. Optimize for Runtime
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-To avoid repeated validation during long-running processes or multi-import scenarios, ImportSpy uses **caching** to store validated environments.
-
-This provides fast re-entry for already-validated modules without compromising security.
-
-Security Philosophy
--------------------
-
-ImportSpy follows a **fail-fast and zero-trust model**:
-
-- 🚫 No module runs unless it satisfies all declared constraints
-- ✅ All failures are explicit and traceable
-- 🔐 This prevents silent regressions, broken interfaces, and unpredictable imports
-
-There is **no fallback behavior** — if a violation is detected, it is blocked **by design**.
-
-Best Practices
---------------
-
-- Use :doc:`embedded_mode` to validate who is importing your module
-- Use :doc:`external_mode` to validate a module before deployment
-- Always define structural + runtime constraints in your contract for full coverage
-
-Related Topics
---------------
-
-- :doc:`defining_import_contracts`
-- :doc:`contract_structure`
-- :doc:`error_handling`
-- :doc:`validation_and_compliance`
diff --git a/docs/source/overview/understanding_importspy/validation_and_compliance.rst b/docs/source/overview/understanding_importspy/validation_and_compliance.rst
deleted file mode 100644
index 74a8f56..0000000
--- a/docs/source/overview/understanding_importspy/validation_and_compliance.rst
+++ /dev/null
@@ -1,129 +0,0 @@
-Validation and Compliance in ImportSpy
-=======================================
-
-ImportSpy is more than a validator — it's a **compliance enforcement layer**
-that ensures every import happens under the right conditions.
-
-It prevents fragile integrations, runtime surprises, and architectural drift by validating
-**both the structure of modules** and **the environment they run in** — before they’re allowed to execute.
-
-Why Compliance Matters
------------------------
-
-In modern Python systems, you often deal with:
-
-- Multiple operating systems and architectures
-- Third-party plugins and dynamic module loading
-- Varying Python runtimes across dev/staging/prod
-- Environment variables that silently shape behavior
-
-Without strict validation, these differences introduce risk.
-
-ImportSpy guarantees that external modules only execute if their importing environment **matches the declared constraints** in their import contract.
-
-Multilayer Validation
-----------------------
-
-ImportSpy checks compliance at multiple levels:
-
-Execution Context
-~~~~~~~~~~~~~~~~~
-
-Before a module runs, ImportSpy validates:
-
-- Which module is trying to import it (via introspection)
-- The **OS**, **architecture**, **Python version**, and **interpreter**
-- Any required **environment variables**
-- Whether the current runtime matches one of the allowed **deployment blocks**
-
-If anything is off, validation fails — no execution occurs.
-
-Example:
-
-- ✅ Declared: CPython 3.12.8 on Linux
- ✅ Actual: CPython 3.12.8 on Linux → Allowed
-- ❌ Declared: Windows-only
- ❌ Actual: Linux → Rejected
-- ❌ Declared: Requires `PLUGIN_KEY`
- ❌ Missing from environment → Blocked
-
-Module Structure
-~~~~~~~~~~~~~~~~
-
-Structural validation includes:
-
-- Required **functions**, with specific argument names and annotations
-- Required **classes**, including attributes, methods, and superclasses
-- Top-level **variables** and their expected values
-- Submodule definitions (when declared inside a `deployment`)
-
-If a module fails to meet these expectations, it's rejected at import time.
-
-System-Level Constraints
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-ImportSpy allows contracts to specify:
-
-- Required environment variables
-- Specific OS-level deployments
-- Architecture-specific compatibility (e.g., only ARM64)
-
-This prevents issues like:
-
-- Silent misconfiguration
-- Undeclared system assumptions
-- Crashes due to missing runtime data
-
-Diagnostics and Failure Behavior
---------------------------------
-
-ImportSpy is strict: when validation fails, execution halts.
-
-But it also provides **high-quality error feedback**, including:
-
-- ❌ What went wrong (e.g., `"Missing method 'run'"`)
-- ❌ Where the mismatch occurred (e.g., `"In class Extension"`)
-- ❌ Why it’s invalid (e.g., `"Expected str, found int"`)
-
-Errors are raised as `ValueError` with clear stack traces and contract violation summaries.
-
-This fail-fast approach prevents issues from reaching production — or worse, going unnoticed.
-
-Maintaining Long-Term Compliance
----------------------------------
-
-ImportSpy helps teams enforce long-term consistency via:
-
-- **Versioned contracts** that evolve with your code
-- **Validation in CI/CD pipelines** to catch regressions early
-- **Runtime checks** in production or sandbox environments
-
-This ensures:
-
-- No configuration drift
-- No mismatches between staging and production
-- Structural integrity across deployments and updates
-
-Compliance Is Not Optional
----------------------------
-
-ImportSpy adopts a **Zero-Trust philosophy**:
-
-> ❌ No module is trusted without validation
-> ✅ No import occurs unless the environment is approved
-
-This guarantees:
-
-- Secure plugin systems
-- Stable microservice communication
-- Predictable behavior across machines and versions
-
-If you care about runtime integrity, ImportSpy turns your import logic into **an enforceable contract** — and blocks anything that breaks it.
-
-Related Topics
---------------
-
-- :doc:`spy_execution_flow`
-- :doc:`contract_structure`
-- :doc:`error_handling`
-- :doc:`integration_best_practices`
diff --git a/docs/source/overview/understanding_importspy_index.rst b/docs/source/overview/understanding_importspy_index.rst
deleted file mode 100644
index cdd1f9d..0000000
--- a/docs/source/overview/understanding_importspy_index.rst
+++ /dev/null
@@ -1,89 +0,0 @@
-Understanding ImportSpy 🔍
-==========================
-
-Welcome to the **technical heart** of ImportSpy.
-
-This section provides a full breakdown of how ImportSpy works,
-why it matters in modern modular architectures, and how you can harness its full potential.
-
-ImportSpy isn’t just a utility — it’s a **runtime contract enforcement framework**.
-It brings validation to the import system by ensuring that external modules conform to **predefined structural rules and runtime constraints**.
-Whether used in embedded or CLI mode, ImportSpy guarantees **predictability, security, and compliance** before code ever runs.
-
-Why This Section Matters ⚠️
-----------------------------
-
-Modern systems are complex.
-You rely on dynamically loaded modules, plugins, APIs, microservices — often across multiple environments.
-
-But with this flexibility comes risk:
-
-- Missing or incompatible methods, attributes, or classes
-- Subtle mismatches in Python versions, OS, or interpreters
-- Unexpected behavior caused by configuration drift or structural divergence
-- Modules that silently break contracts and cause late-stage failures
-
-ImportSpy intercepts these issues **before execution**, making validation **a first-class citizen** of your architecture.
-
-What You’ll Learn Here 📘
---------------------------
-
-This section guides you through ImportSpy’s internals, starting from high-level concepts to runtime execution flow:
-
-- :doc:`understanding_importspy/introduction`
- A clear introduction to ImportSpy’s core mission and use cases.
-
-- :doc:`understanding_importspy/defining_import_contracts`
- Learn how to describe structural and environmental expectations through YAML contracts.
-
-- :doc:`understanding_importspy/contract_structure`
- Understand the schema and semantics of a well-formed import contract.
-
-- :doc:`understanding_importspy/spy_execution_flow`
- Discover how ImportSpy intercepts imports and validates modules in real time.
-
-- :doc:`understanding_importspy/embedded_mode`
- Explore how modules can protect themselves by validating their importers at runtime.
-
-- :doc:`understanding_importspy/external_mode`
- Understand CLI-based validation and its role in automation, CI/CD, and review pipelines.
-
-- :doc:`understanding_importspy/validation_and_compliance`
- Dive into the validation engine and what it checks (types, names, annotations, OS, versions, etc).
-
-- :doc:`understanding_importspy/error_handling`
- See how ImportSpy produces actionable, clear error messages when something doesn’t match.
-
-- :doc:`understanding_importspy/integration_best_practices`
- Apply ImportSpy cleanly in real projects — plugins, microservices, libraries, or secure APIs.
-
-- :doc:`understanding_importspy/ci_cd_integration`
- Integrate ImportSpy into your continuous delivery pipeline for automated contract enforcement.
-
-Who This Is For 👨💻👩💻
----------------------------
-
-This section is written for:
-
-- **Developers** using ImportSpy in plugin-based or multi-module Python apps
-- **Architects** designing extensible, contract-driven software systems
-- **DevOps and Security Engineers** aiming to validate runtime boundaries and block unknowns
-- **Open Source Maintainers** who want to ensure compatibility across environments
-
-Ready to see how ImportSpy works under the hood?
-Let’s explore the architecture that makes dynamic imports deterministic.
-
-.. toctree::
- :maxdepth: 2
- :caption: Understanding ImportSpy:
-
- understanding_importspy/introduction
- understanding_importspy/defining_import_contracts
- understanding_importspy/contract_structure
- understanding_importspy/spy_execution_flow
- understanding_importspy/embedded_mode
- understanding_importspy/external_mode
- understanding_importspy/validation_and_compliance
- understanding_importspy/error_handling
- understanding_importspy/integration_best_practices
- understanding_importspy/ci_cd_integration
diff --git a/docs/source/overview/use_cases/use_case_compilance.rst b/docs/source/overview/use_cases/use_case_compilance.rst
deleted file mode 100644
index 05b77e9..0000000
--- a/docs/source/overview/use_cases/use_case_compilance.rst
+++ /dev/null
@@ -1,99 +0,0 @@
-Ensuring Compliance with Industry Standards
-===========================================
-
-📑 Enforcing Structural and Regulatory Conformance at the Module Level
-
-In industries governed by **strict regulations** — such as **finance**, **healthcare**, and **public sector platforms** —
-software must not only function correctly, but also **prove compliance** with internal standards and external laws.
-
-Uncontrolled imports and unverified module interactions introduce risks that go far beyond bugs:
-
-- ⚠️ **Legal exposure**, from non-compliant code paths
-- 🔓 **Security vulnerabilities**, exposing sensitive data
-- 🐞 **Operational inconsistencies**, undermining traceability and auditability
-
-Modern teams need to **enforce validation before execution**, ensuring every module behaves exactly as expected
-— across environments, platforms, and regulatory requirements.
-
-The Compliance Challenge
--------------------------
-
-A leading **healthcare SaaS provider** needed to secure their plugin system against non-compliant third-party modules.
-
-Their platform was required to uphold **HIPAA** and **GDPR** standards while supporting dynamic integration
-with third-party services that could access **sensitive medical data**.
-
-Their core concerns:
-
-- 🔍 **Unstructured interactions** with plugin modules
-- ❌ **No enforcement of structural expectations**
-- 🕵️ **Poor audit visibility** over how and when validation occurred
-
-What they needed was **automated enforcement** at import time — a guardrail to **block non-compliant code
-before it could execute**, with **evidence trails** for regulators and internal audits.
-
-How ImportSpy Solved It
-------------------------
-
-The team adopted **ImportSpy in CLI validation mode**, using **YAML-based import contracts** to:
-
-✅ **Define Compliance Constraints Declaratively**
-
-- Listed allowed **module names**, **functions**, **attributes**, and **expected annotations**
-- Enforced execution limits: **Python version**, **OS**, **interpreter**, and **environment variables**
-- Integrated contracts into the source repository as part of **version-controlled policy enforcement**
-
-✅ **Block Violations Before Runtime**
-
-- ImportSpy loaded the target module and validated it **against its declared contract**
-- On mismatch, the module was **rejected immediately**, with detailed error feedback
-- Violations raised `ValueError` exceptions, stopping non-compliant code from running
-
-✅ **Generate Audit Logs Automatically**
-
-- Each validation produced logs containing:
- - Validation time and result
- - Name of contract and validated module
- - Structural mismatches or missing components
-- Logs were parsed into **compliance dashboards** and shared with auditors
-
-Results: Measurable, Auditable Compliance
------------------------------------------
-
-Before ImportSpy:
-
-- Compliance relied on **manual reviews and inconsistent scripts**
-- Risk exposure was high due to **third-party code with unchecked access**
-- Audits were painful, requiring **manual tracing of module usage across services**
-
-After ImportSpy:
-
-✅ All imported modules were validated automatically
-✅ Violations were blocked before deployment or execution
-✅ Audit trails were generated for every contract match/failure
-✅ Compliance with **HIPAA**, **GDPR**, and internal policies was built into the lifecycle
-
-Why It Matters
---------------
-
-ImportSpy bridges the gap between **modular extensibility** and **regulatory control**.
-By moving compliance checks to the import boundary, it ensures that **only verified, policy-compliant code** can run.
-
-Whether you're building regulated cloud platforms or securing internal APIs, ImportSpy gives your team:
-
-- ✅ **Automated, declarative validation**
-- ✅ **Runtime protection against policy violations**
-- ✅ **Structured logging for audits and security reviews**
-
-Try It Yourself
----------------
-
-To validate a module before runtime, run:
-
-.. code-block:: bash
-
- importspy -s spymodel.yml your_module.py
-
-ImportSpy will block any import that doesn’t match the compliance contract — ensuring policy adherence before execution.
-
-📌 Import contracts are the new compliance policy — and ImportSpy is how you enforce them.
diff --git a/docs/source/overview/use_cases/use_case_iot_integration.rst b/docs/source/overview/use_cases/use_case_iot_integration.rst
deleted file mode 100644
index a16ed86..0000000
--- a/docs/source/overview/use_cases/use_case_iot_integration.rst
+++ /dev/null
@@ -1,112 +0,0 @@
-Ensuring Compliance in IoT Smart Home Integration
-=================================================
-
-🔌 Real-World Enforcement Across Heterogeneous Devices
-
-In the evolving world of the **Internet of Things (IoT)**, ensuring **predictable behavior** across a wide variety of devices is no small feat.
-Vendors, hardware platforms, Python runtimes, and execution environments all vary — making consistency difficult to guarantee.
-
-A leading IoT company, building a **smart home automation platform**, needed to support **third-party plugins** while maintaining **strict compliance**.
-They required enforcement of **interface contracts**, **environmental conditions**, and **runtime compatibility** across a fragmented deployment landscape.
-
-📐 System Architecture
-----------------------
-
-The platform was designed around a **plugin-based architecture** that allowed modular integration of:
-
-- Smart thermostats
-- Lighting controllers
-- Security sensors
-- Voice assistants
-- External automation services
-
-Key architectural traits:
-
-- Device drivers implemented as Python plugins.
-- Plugins communicate via a **RESTful API** layer.
-- Deployed across edge devices like **Raspberry Pi**, as well as **Kubernetes-based smart hubs**.
-- Environment setup includes:
- - **ARM/x86_64** CPUs
- - **Multiple Python versions** (3.10, 3.12) and **interpreters** (CPython, PyPy)
- - **Dockerized plugins** with injected secrets via environment variables
-
-🧩 The Compliance Problem
---------------------------
-
-Without enforcement, plugins were deployed with:
-
-- Missing functions or improperly annotated interfaces
-- Incorrect assumptions about Python version or CPU architecture
-- Misconfigured or absent environment variables (`DEVICE_TOKEN`, `API_KEY`, etc.)
-- Unvalidated structure that only failed **after** deployment
-
-These mismatches led to:
-
-- ❌ Runtime crashes in smart home hubs
-- ❌ Inconsistent API behavior
-- ❌ Security concerns due to environment misconfigurations
-- ❌ Long debugging cycles and delayed releases
-
-🛡️ ImportSpy in Action: Embedded Validation Mode
--------------------------------------------------
-
-To regain control, the team embedded **ImportSpy** directly into each plugin’s entry point:
-
-.. code-block:: python
-
- from importspy import Spy
-
- caller_module = Spy().importspy(filepath="spymodel.yml")
-
-Plugins were paired with YAML-based **import contracts** that defined strict structural and runtime constraints.
-
-Contract enforcement ensured:
-
-✅ Structural Compliance
- - Validated presence of all **required methods, attributes, and return types**
- - Prevented silent schema drift between plugin and control layer
-
-✅ Runtime Compatibility
- - Verified execution on the correct **CPU architecture** and **Python interpreter**
- - Blocked execution on unsupported hardware setups
-
-✅ Environmental Validation
- - Checked for required **env vars** (e.g., `DEVICE_TOKEN`, `PLATFORM_ENV`)
- - Rejected execution if secrets were missing or malformed
-
-✅ Deployment Readiness
- - CLI mode (`importspy -s spymodel.yml plugin.py`) used in **CI/CD pipelines**
- - Pre-deployment validation embedded into Docker build stages
- - Only validated containers promoted to **production clusters**
-
-🚀 Results in Production
--------------------------
-
-After adopting ImportSpy:
-
-- 🔒 **Plugin integrity was guaranteed pre-execution**
-- 🐛 **Edge-device errors were eliminated before rollout**
-- ⚙️ **CI pipelines caught contract violations early**
-- 🔁 **New plugins were integrated 3× faster**, with fewer regressions
-
-Before ImportSpy:
-
-- Incompatible drivers were deployed to production
-- Manual tests were required per device and platform
-- Configuration bugs were discovered too late
-
-After ImportSpy:
-
-✅ Structural drift was eliminated
-✅ Plugin execution was **bounded by contract**
-✅ IoT integration became scalable and predictable
-
-Conclusion
-----------
-
-This real-world case shows how ImportSpy enables **modular safety** in highly distributed systems.
-By turning contracts into enforcement mechanisms, it transforms each plugin into a **self-validating unit** —
-capable of **refusing to run in invalid contexts**, and ensuring that integration is both safe and scalable.
-
-📦 ImportSpy is more than validation.
-It’s **runtime insurance** for systems that must adapt — without compromising structure or control.
diff --git a/docs/source/overview/use_cases/use_case_security.rst b/docs/source/overview/use_cases/use_case_security.rst
deleted file mode 100644
index 76a2653..0000000
--- a/docs/source/overview/use_cases/use_case_security.rst
+++ /dev/null
@@ -1,120 +0,0 @@
-Strengthening Software Security with ImportSpy 🔐
-=================================================
-
-🔍 Enforcing Controlled Interactions with External Modules
-
-In security-critical software, **unregulated imports** are a gateway to vulnerabilities.
-From misconfigured plugins to dynamic imports of malicious code, **Python’s flexibility becomes a liability** without structural enforcement.
-
-Organizations operating in fields like **cybersecurity**, **finance**, and **enterprise platforms** need more than just static analysis —
-they need **runtime enforcement** that validates what is imported, how it behaves, and under which context it executes.
-
-🧨 The Problem: Invisible Risks in External Dependencies
----------------------------------------------------------
-
-A cybersecurity firm specializing in **real-time threat detection** uncovered serious risks in its plugin framework:
-
-- Internal APIs were accessible via loosely defined module boundaries.
-- External components bypassed authentication checks using dynamic imports.
-- Function contracts were silently broken after dependency upgrades.
-- No system-wide trace existed of who imported what — and under which conditions.
-
-These issues weren’t caused by malicious intent, but by the **absence of strict validation**.
-
-Without safeguards:
-
-- ⚠️ Plugins introduced **execution drift**.
-- ⚠️ Imports became **non-deterministic** across environments.
-- ⚠️ Attackers could **abuse loosely validated integrations**.
-
-🛡️ The Solution: ImportSpy Embedded + CLI Validation
------------------------------------------------------
-
-The team introduced **ImportSpy** using both:
-
-- **Embedded Mode** for real-time validation at module import time.
-- **CLI Mode** for enforcement in automated build pipelines.
-
-Each plugin and internal service was paired with a YAML-based **import contract** (`spymodel.yml`), defining strict:
-
-- Allowed functions and methods (including arguments and annotations)
-- Required attributes and class hierarchies
-- Valid operating systems, Python versions, and interpreters
-- Mandatory environment variables for secrets or context
-
-📦 Example snippet from a contract:
-
-.. code-block:: yaml
-
- filename: secure_plugin.py
- functions:
- - name: verify_signature
- arguments:
- - name: data
- annotation: bytes
- return_annotation: bool
- deployments:
- - arch: x86_64
- systems:
- - os: linux
- envs:
- SECURITY_TOKEN: required
- pythons:
- - version: 3.12.8
- interpreter: CPython
-
-⚙️ Security Mechanisms Enabled by ImportSpy
---------------------------------------------
-
-🔐 **Structural Boundary Enforcement (Embedded Mode)**
- - ImportSpy executed *inside* secure modules to inspect who was importing them.
- - If the importer didn’t match declared contracts, the execution was blocked.
- - Validations were performed **every time the module was used**, ensuring active defense.
-
-🧪 **CI/CD Enforcement (CLI Mode)**
- - ImportSpy was used in pipelines to validate plugins **before deployment**.
- - Prevented misconfigured or unauthorized code from entering production.
- - Ideal for automated checks on third-party or external codebases.
-
-🚫 **Blocking Unauthorized Imports**
- - Attempted imports from unknown modules were rejected.
- - Reflection-based imports (e.g. `importlib`, `__import__`) were intercepted if they bypassed structure.
-
-📈 **Audit-Ready Validation Logs**
- - Each validation generated:
- - Who imported the module and from where.
- - Whether all structural, runtime, and environmental constraints were satisfied.
- - A traceable record for security and compliance audits.
-
-🚀 Real Impact
---------------
-
-After adopting ImportSpy:
-
-✅ Only **pre-approved, contract-compliant modules** were allowed to interface with secure APIs
-✅ All imports were **traceable and auditable**, including dynamic execution paths
-✅ Teams could **prevent misuse of sensitive interfaces at runtime**, not just in reviews
-✅ Security incidents related to uncontrolled plugin behavior dropped to zero
-
-Before ImportSpy:
-
-- Access to internal components was based on trust, not enforcement.
-- Developers could unknowingly introduce insecure behaviors through third-party dependencies.
-- Detection of misuses happened **after the fact**, during production failures or audits.
-
-After ImportSpy:
-
-✅ Security was enforced as **a contract**, not a convention
-✅ Modules became **self-defensive**, refusing to run under unsafe conditions
-✅ Compliance teams gained **real-time insight** into software integrity
-
-Conclusion
-----------
-
-ImportSpy transforms Python’s import mechanism into a **structural firewall**,
-enforcing the principle of **Zero Trust by default**.
-
-Whether embedded in secure modules or integrated into CI/CD workflows,
-it ensures that only **authorized, structurally sound, and contextually valid** code is ever executed.
-
-🔐 With ImportSpy, your code doesn’t just run — it runs **safely, predictably, and by the rules**.
diff --git a/docs/source/overview/use_cases/use_case_validation.rst b/docs/source/overview/use_cases/use_case_validation.rst
deleted file mode 100644
index bead272..0000000
--- a/docs/source/overview/use_cases/use_case_validation.rst
+++ /dev/null
@@ -1,99 +0,0 @@
-Validating Imports in Large-Scale Architectures
-===============================================
-
-🔍 Enforcing Predictable Module Integration Across Microservices
-
-In modern software platforms, especially those built around **microservices and shared components**,
-imports can quickly become a **source of instability** if not explicitly controlled.
-
-ImportSpy addresses this challenge by enabling teams to define and enforce **import contracts**,
-bringing structure, validation, and security to large-scale Python ecosystems.
-
-The Challenge: Structural Drift at Scale
-----------------------------------------
-
-A global fintech company operating a **real-time trading platform** faced a growing problem:
-
-- Over 200 services exchanged shared modules, but **no validation existed** on what those modules should look like.
-- Developers introduced **untracked changes** to shared libraries — often without awareness of the ripple effect.
-- Bugs emerged **during runtime**, causing unpredictable behavior in APIs, logs, and financial transactions.
-- Regulatory audits revealed **unauthorized dependencies**, triggering compliance concerns.
-
-Without validation, **even a renamed method or removed class attribute** had the potential to break entire workflows
-— often in systems critical to financial accuracy and regulatory visibility.
-
-How ImportSpy Resolved the Problem
-----------------------------------
-
-The team adopted ImportSpy to introduce **contract-based validation** between services.
-
-Each service defined a **`spymodel.yml`** contract that:
-
-- ✅ Declared which modules could be imported
-- ✅ Specified required functions, classes, and their expected structure
-- ✅ Described the allowed Python version, interpreter, and OS for each deployment context
-- ✅ Enforced environmental assumptions like `env` variables and module metadata
-
-Validation was performed in two ways:
-
-- **Externally in CI/CD pipelines**, using the CLI tool
-- **Dynamically at runtime**, via embedded validation inside critical modules
-
-Core Benefits for Large-Scale Systems
---------------------------------------
-
-🔒 **Structural Enforcement, Not Just Testing**
-
-Every import was validated against the contract:
-
-- Missing functions? ❌ Blocked
-- Changed signatures? ❌ Blocked
-- Incorrect return types? ❌ Blocked
-- Drift in module metadata? ❌ Blocked
-
-🧩 **Modular Contracts per Microservice**
-
-Each team owned their own import contract, versioned alongside their codebase.
-Contracts were reviewed in pull requests, giving visibility into integration assumptions.
-
-🛑 **Fail Fast, Fail Loud**
-
-When violations occurred, ImportSpy halted execution and raised detailed errors
-before the application could misbehave.
-
-📋 **Compliance and Audit Alignment**
-
-Contracts became part of compliance reviews.
-ImportSpy ensured that:
-
-- Only approved dependencies were used
-- Environments matched what was declared
-- Drift was caught before deployment
-
-🚀 Real-World Impact
----------------------
-
-**Before ImportSpy**:
-
-- Services broke silently due to changing APIs
-- Debugging required tracing through dozens of unrelated modules
-- Compliance reports had no traceability on module-level expectations
-
-**After ImportSpy**:
-
-✅ Every shared module was paired with a structural contract
-✅ Integration bugs were detected early in CI
-✅ Teams had clear ownership and boundaries
-✅ Compliance teams had visible, testable enforcement logic
-
-Conclusion
-----------
-
-ImportSpy enabled the company to treat imports as **governed integration points**,
-not dynamic and unpredictable behaviors.
-
-It transformed their microservice architecture into a **contract-bound system**,
-where validation was continuous, clear, and automated — at runtime and in pipelines.
-
-📌 For teams operating at scale, ImportSpy brings **structure, clarity, and runtime discipline**
-to one of the most overlooked areas of Python: the import statement itself.
diff --git a/docs/source/overview/use_cases_index.rst b/docs/source/overview/use_cases_index.rst
deleted file mode 100644
index 37d4d91..0000000
--- a/docs/source/overview/use_cases_index.rst
+++ /dev/null
@@ -1,61 +0,0 @@
-Use Cases
-=========
-
-🔍 Real-World Applications of ImportSpy
-
-ImportSpy is built for **real-world modular ecosystems** — where external components, plugins, and dynamic imports
-must interact with precision, safety, and structural integrity.
-
-This section presents practical scenarios where ImportSpy ensures:
-
-- ✅ **Runtime validation** of external modules
-- ✅ **Predictable integration** across complex environments
-- ✅ **Compliance enforcement** in regulated and distributed systems
-
-Why Use Cases Matter
---------------------
-
-As modern applications adopt **microservices**, **plugin-based extensions**, and **cloud-native deployments**,
-the complexity of imports grows — and so does the risk of:
-
-- ❌ Uncontrolled module behavior
-- ❌ Silent contract violations
-- ❌ Runtime incompatibilities across environments
-
-ImportSpy brings **order, visibility, and validation** to these architectures — blocking what doesn’t belong, and allowing only compliant modules to run.
-
-What You’ll Learn Here
------------------------
-
-These case studies walk through:
-
-- **Modular IoT Environments** 🌐
- How ImportSpy validates plugins and services across **multi-device deployments** and **cross-platform runtimes**.
-
-- **Structural Validation in Dynamic Systems** 🧱
- Ensuring that plugins, modules, and APIs always match the expected **structure, behavior, and metadata**.
-
-- **Security and Runtime Threat Prevention** 🔒
- Protecting your application from **malicious imports**, **tampering**, and **unauthorized runtime mutations**.
-
-- **Regulatory Compliance and Policy Enforcement** 📋
- Using ImportSpy to meet **industry standards** by validating runtime environments, interpreters, and module structure.
-
-Use Case Preview
------------------
-
-Each case is based on **realistic implementations**, designed to help teams:
-
-- Integrate ImportSpy into **CI/CD pipelines**
-- Harden **plugin-based architectures**
-- Secure **cloud, edge, and hybrid environments**
-- Automate validation as part of **development workflows**
-
-.. toctree::
- :maxdepth: 2
- :caption: Use Cases
-
- use_cases/use_case_iot_integration
- use_cases/use_case_validation
- use_cases/use_case_security
- use_cases/use_case_compilance
diff --git a/docs/source/sponsorship.rst b/docs/source/sponsorship.rst
deleted file mode 100644
index 00f97f6..0000000
--- a/docs/source/sponsorship.rst
+++ /dev/null
@@ -1,68 +0,0 @@
-Support the ImportSpy Mission 💡
-=================================
-
-ImportSpy is more than a validation tool — it’s a call for structure, precision, and integrity in the Python ecosystem.
-By supporting ImportSpy, you’re backing a vision: one where Python modules behave as expected, where integrations are safe by design, and where developers can build **modular, reliable systems with confidence**.
-
-Whether you're an individual developer, an engineering team, or a tech leader, your support helps shape a future where **every Python import is secure, predictable, and compliant**.
-
-Why Your Support Matters 🚀
-----------------------------
-
-ImportSpy is built on open-source values: **transparency, accessibility, and community-driven evolution**.
-Your support enables us to:
-
-- **Accelerate Feature Development** 🛠️
- More funding means more focus. We can deliver powerful features, implement community requests, and evolve ImportSpy faster and more reliably.
-
-- **Expand Learning Resources** 📚
- Sponsor contributions help us invest in tutorials, video walkthroughs, onboarding guides, and examples that make ImportSpy accessible for everyone — from beginner to expert.
-
-- **Maintain Compatibility** 🔄
- Keeping up with the ever-changing Python ecosystem requires time and effort. Your support ensures ImportSpy stays compatible with new versions, platforms, and interpreters.
-
-- **Fuel Community Growth** 🌍
- From live workshops to online Q&As and collaborative initiatives — sponsors make it possible for us to build a **global, active, and inclusive developer community**.
-
-How You Can Help 💖
---------------------
-
-**⭐ Star ImportSpy on GitHub**
-Visibility matters. A single star tells the world this project matters.
-`Star the project `_
-
-**💝 Become a GitHub Sponsor**
-Help us focus full-time on building and maintaining ImportSpy.
-Your sponsorship directly funds the project's future.
-`Sponsor ImportSpy `_
-
-**📢 Spread the Word**
-Tell a friend. Mention it in your team. Share it in your community.
-Every conversation strengthens the ecosystem.
-
-**🔧 Contribute Code, Ideas, or Feedback**
-Open issues, suggest features, or submit pull requests — your voice and your code matter.
-We grow better with you involved.
-
-Thank You to Our Sponsors 💙
------------------------------
-
-Every sponsor — whether individual or organization — plays a vital role in ImportSpy’s growth.
-We are deeply grateful for your belief in our mission and your commitment to building a more structured Python ecosystem.
-
-Your sponsorship supports:
-
-- Open development
-- Better tools for the Python community
-- A culture of care, rigor, and transparency
-
-Let’s Shape the Future Together 🔭
------------------------------------
-
-As a sponsor, you’re not just funding development — you’re helping define the roadmap.
-We welcome your input on features, priorities, and directions for the future.
-
-ImportSpy exists because developers believed Python could be better.
-With your help, we can **set a new standard for what “safe imports” look like.**
-
-**🔹 Support structure. Support clarity. Support ImportSpy.**
diff --git a/docs/source/vision.rst b/docs/source/vision.rst
deleted file mode 100644
index 21c2b8d..0000000
--- a/docs/source/vision.rst
+++ /dev/null
@@ -1,104 +0,0 @@
-The Vision Behind ImportSpy
-============================
-
-ImportSpy exists to solve a simple but powerful problem:
-> How can we make dynamic Python imports **secure**, **predictable**, and **compliant**—without sacrificing flexibility?
-
-Modern Python development thrives on **modularity**, with architectures powered by **plugins, microservices, and third-party integrations**.
-But the more dynamic our systems become, the harder it is to guarantee **structural consistency**, **environmental compatibility**, and **execution safety**.
-
-ImportSpy redefines how we think about `import` in Python.
-It brings the **rigor of contracts** to the most permissive part of the language—validating structure, runtime context, and compliance **before code is allowed to execute**.
-
-What Problem Does ImportSpy Solve?
------------------------------------
-
-Too often, developers rely on:
-
-- ✅ Best practices
-- ✅ Static linters
-- ✅ Runtime trial-and-error
-- ✅ Outdated documentation
-
-…to ensure that external modules conform to expectations. But when things go wrong:
-
-- APIs silently drift
-- Plugins break in production
-- Modules fail across environments
-- Security boundaries are crossed
-
-ImportSpy addresses these gaps **by enforcing executable contracts** at runtime—**automatically** and **contextually**.
-
-The Mission
------------
-
-ImportSpy is designed to be the **compliance layer** for dynamic Python systems.
-
-Its mission is to:
-
-- 🧩 **Protect modular systems** from unpredictable imports
-- 🔒 **Prevent integration errors** before they happen
-- 🚦 **Validate structure, versioning, and runtime environment**
-- 📜 **Promote living contracts** between modules and their runtime expectations
-
-And in doing so, it helps developers build systems that are:
-
-- Easier to maintain
-- Safer to extend
-- Ready for scale
-- Aligned with compliance standards in regulated environments
-
-A Runtime Contract Philosophy
-------------------------------
-
-The vision behind ImportSpy is rooted in a new philosophy:
-
-> *“If a module must behave a certain way, let’s not hope it does — let’s validate it.”*
-
-By introducing **import contracts**, ImportSpy formalizes the structure of Python modules in YAML.
-These contracts define what’s expected:
-classes, methods, attributes, variables, Python versions, interpreters, OS targets, and more.
-
-At runtime, ImportSpy checks if those expectations are met — and if not, it blocks execution with **clear, actionable feedback**.
-
-Why This Matters
-----------------
-
-Today’s software is **distributed**, **heterogeneous**, and **highly modular**.
-Whether you’re building for:
-
-- Embedded devices and IoT
-- Plugin ecosystems
-- Regulated sectors
-- Containerized architectures
-- Cloud-based platforms
-
-…you need to know that **the code running in production is exactly what you intended to deploy**.
-
-ImportSpy gives you that guarantee.
-
-It becomes a contract enforcer for:
-
-- **Security**: detect tampering and unauthorized changes
-- **Compliance**: validate structural and environmental constraints
-- **Stability**: prevent “it worked on my machine” failures
-- **Clarity**: reduce guesswork and accelerate debugging
-
-Looking Ahead
--------------
-
-This is only the beginning.
-
-Future goals include:
-
-- ✨ Auto-generating contracts from Python modules
-- 🔁 Bi-directional validation between contracts and code
-- 🔍 Fine-grained integration with dependency graphs
-- 🧠 Enhanced static-to-runtime consistency tooling
-- 💼 First-class CI/CD and DevSecOps integrations
-
-With ImportSpy, we believe Python can be **both dynamic and dependable**.
-
-Join the movement toward **validated modularity**, and help shape a future where every import is safe, consistent, and predictable.
-
-**🔹 Structure with clarity. Import with confidence. Trust your code.**
diff --git a/docs/use_cases/index.md b/docs/use_cases/index.md
new file mode 100644
index 0000000..f72cf82
--- /dev/null
+++ b/docs/use_cases/index.md
@@ -0,0 +1,120 @@
+# Use Cases
+
+ImportSpy brings contract-based validation to dynamic Python environments, enabling control, predictability, and safety.
+
+Below are common scenarios where ImportSpy proves useful.
+
+---
+
+## Embedded Mode in Plugin Architectures
+
+In plugin-based systems, a core module often exposes an interface or expected structure that plugins must follow.
+
+With ImportSpy embedded in the core, plugins are validated **at import time** to ensure they define the required classes, methods, variables, and environment conditions.
+
+This prevents silent failures or misconfigurations by enforcing structural and runtime constraints early.
+
+!!! example "Example: Plugin Enforced at Import"
+ ```python
+ --8<-- "examples/plugin_based_architecture/package.py"
+ ```
+
+See also [Embedded Mode](../modes/embedded.md) and [Contract Syntax](../contracts/syntax.md) for YAML details.
+
+---
+
+## CLI Validation in CI/CD Pipelines
+
+In DevOps workflows or during pre-release validation, ImportSpy can be executed from the command line to ensure a Python module conforms to its declared import contract.
+
+Typical use cases:
+- Automated deployment verification
+- Open-source plugin contributions
+- Validating extension points in modular codebases
+
+!!! example "Example: Validate via CLI"
+ ```bash
+ importspy extensions.py -s spymodel.yml -l INFO
+ ```
+
+See [CLI Mode](../modes/cli.md) for full usage.
+
+---
+
+## Restricting Import Access by Runtime Context
+
+A module can refuse to be imported unless specific runtime conditions are met — such as CPU architecture, OS, Python version, or interpreter.
+
+This enables:
+- Targeted deployments (e.g., Linux-only, CPython-only)
+- Restriction to known-safe execution environments
+- Fail-fast behavior in unsupported contexts
+
+Contracts can define system constraints like:
+
+```yaml
+deployments:
+ - arch: x86_64
+ systems:
+ - os: linux
+ pythons:
+ - version: 3.12
+ interpreter: CPython
+```
+
+If the runtime doesn't match, import fails with a clear error message.
+
+---
+
+## Contract-as-Code: Executable Documentation
+
+ImportSpy contracts double as living specifications for module structure and environment assumptions.
+
+Rather than maintaining separate interface docs, a `.yml` contract acts as:
+
+- ✅ Interface specification
+- ✅ Compatibility schema
+- ✅ Runtime validator
+
+This approach improves communication in:
+- Plugin-based systems
+- Collaborative teams sharing Python APIs
+- Educational or onboarding contexts
+
+!!! example "Example Contract Snippet"
+ ```yaml
+ classes:
+ - name: Plugin
+ methods:
+ - name: run
+ arguments:
+ - name: self
+ ```
+
+Any contributor can run `importspy` or trigger the embedded validation to ensure conformance.
+
+---
+
+## Supporting Multiple Deployment Targets
+
+Modules may need to support more than one runtime environment (e.g., Linux and Windows, or Python 3.10+).
+
+ImportSpy contracts support listing **multiple valid deployments**, each with its own OS, interpreter, and version constraints.
+
+```yaml
+deployments:
+ - arch: x86_64
+ systems:
+ - os: linux
+ pythons:
+ - version: 3.10
+ - os: windows
+ pythons:
+ - version: 3.11
+```
+
+This enables the same module to be validated across CI matrices or downstream consumers with differing setups.
+
+---
+
+Together, these use cases show how ImportSpy bridges **runtime context and modular structure** through declarative contracts — empowering safer, more predictable Python architectures.
diff --git a/examples/plugin_based_architecture/external_module_compilance/package.py b/examples/plugin_based_architecture/external_module_compilance/package.py
index b1a6a70..14474df 100644
--- a/examples/plugin_based_architecture/external_module_compilance/package.py
+++ b/examples/plugin_based_architecture/external_module_compilance/package.py
@@ -6,5 +6,5 @@
__version__ = None
-caller_module = Spy().importspy(filepath="./spymodel.yml", log_level=logging.DEBUG)
+caller_module = Spy().importspy(filepath="./spymodel.yml", log_level=logging.WARN)
caller_module.Foo().get_bar()
\ No newline at end of file
diff --git a/examples/plugin_based_architecture/external_module_compilance/spymodel.yml b/examples/plugin_based_architecture/external_module_compilance/spymodel.yml
index a3cb127..cd78b33 100644
--- a/examples/plugin_based_architecture/external_module_compilance/spymodel.yml
+++ b/examples/plugin_based_architecture/external_module_compilance/spymodel.yml
@@ -34,7 +34,7 @@ classes:
arguments:
- name: self
superclasses:
- - Plugin
+ - name: Plugin
- name: Foo
attributes:
methods:
@@ -44,9 +44,9 @@ classes:
deployments:
- arch: x86_64
systems:
- - os: windows
+ - os: linux
pythons:
- - version: 3.12.8
+ - version: 3.12.9
interpreter: CPython
modules:
- filename: extension.py
@@ -63,9 +63,9 @@ deployments:
- interpreter: IronPython
modules:
- filename: addons.py
- - os: linux
+ - os: windows
pythons:
- - version: 3.12.8
+ - version: 3.12.9
interpreter: CPython
modules:
- filename: extension.py
diff --git a/examples/plugin_based_architecture/pipeline_validation/plugin_interface.py b/examples/plugin_based_architecture/pipeline_validation/plugin_interface.py
deleted file mode 100644
index cd3c151..0000000
--- a/examples/plugin_based_architecture/pipeline_validation/plugin_interface.py
+++ /dev/null
@@ -1,2 +0,0 @@
-class Plugin:
- pass
\ No newline at end of file
diff --git a/examples/plugin_based_architecture/pipeline_validation/spymodel.yml b/examples/plugin_based_architecture/pipeline_validation/spymodel.yml
index a3cb127..cd78b33 100644
--- a/examples/plugin_based_architecture/pipeline_validation/spymodel.yml
+++ b/examples/plugin_based_architecture/pipeline_validation/spymodel.yml
@@ -34,7 +34,7 @@ classes:
arguments:
- name: self
superclasses:
- - Plugin
+ - name: Plugin
- name: Foo
attributes:
methods:
@@ -44,9 +44,9 @@ classes:
deployments:
- arch: x86_64
systems:
- - os: windows
+ - os: linux
pythons:
- - version: 3.12.8
+ - version: 3.12.9
interpreter: CPython
modules:
- filename: extension.py
@@ -63,9 +63,9 @@ deployments:
- interpreter: IronPython
modules:
- filename: addons.py
- - os: linux
+ - os: windows
pythons:
- - version: 3.12.8
+ - version: 3.12.9
interpreter: CPython
modules:
- filename: extension.py
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..6fab34e
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,52 @@
+site_name: ImportSpy
+site_url: https://importspy.github.io/importspy/
+repo_url: https://github.com/importspy/importspy
+repo_name: importspy/importspy
+
+theme:
+ name: material
+ custom_dir: docs/overrides
+ favicon: assets/favicon.ico
+ logo: assets/importspy-logo.png
+ palette:
+ - scheme: default
+ primary: purple
+ accent: light blue
+ features:
+ - navigation.tabs
+ - navigation.instant
+
+nav:
+ - Home: index.md
+ - Introduction:
+ - What is ImportSpy: intro/overview.md
+ - Installation: intro/install.md
+ - Quickstart: intro/quickstart.md
+ - Operating Modes:
+ - Embedded Mode: modes/embedded.md
+ - CLI Mode: modes/cli.md
+ - YAML Contracts:
+ - Syntax: contracts/syntax.md
+ - Examples: contracts/examples.md
+ - Advanced Usage:
+# - CI/CD Integration: advanced/cicd.md
+ - SpyModel Architecture: advanced/spymodel.md
+ - Violation System: advanced/violations.md
+ - Validation & Errors: errors/contract-violations.md
+ - Use Cases: use_cases/index.md
+ - API Reference: api-reference.md
+
+plugins:
+ - search
+ - mkdocstrings:
+ handlers:
+ python:
+ options:
+ show_source: false
+
+markdown_extensions:
+ - pymdownx.snippets
+ - admonition
+ - pymdownx.tabbed:
+ alternate_style: true
+
diff --git a/poetry.lock b/poetry.lock
index b74e3f8..92785f1 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,15 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
-
-[[package]]
-name = "alabaster"
-version = "1.0.0"
-description = "A light, configurable Sphinx theme"
-optional = false
-python-versions = ">=3.10"
-files = [
- {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"},
- {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"},
-]
+# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
[[package]]
name = "annotated-types"
@@ -17,165 +6,19 @@ version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
-[[package]]
-name = "babel"
-version = "2.17.0"
-description = "Internationalization utilities"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"},
- {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"},
-]
-
-[package.extras]
-dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"]
-
-[[package]]
-name = "beautifulsoup4"
-version = "4.13.4"
-description = "Screen-scraping library"
-optional = false
-python-versions = ">=3.7.0"
-files = [
- {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"},
- {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"},
-]
-
-[package.dependencies]
-soupsieve = ">1.2"
-typing-extensions = ">=4.0.0"
-
-[package.extras]
-cchardet = ["cchardet"]
-chardet = ["chardet"]
-charset-normalizer = ["charset-normalizer"]
-html5lib = ["html5lib"]
-lxml = ["lxml"]
-
-[[package]]
-name = "certifi"
-version = "2025.4.26"
-description = "Python package for providing Mozilla's CA Bundle."
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"},
- {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"},
-]
-
-[[package]]
-name = "charset-normalizer"
-version = "3.4.2"
-description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"},
- {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"},
- {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"},
- {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"},
- {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"},
- {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"},
- {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"},
- {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"},
- {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"},
- {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"},
-]
-
[[package]]
name = "click"
version = "8.1.8"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
+groups = ["main", "dev"]
files = [
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
@@ -190,84 +33,72 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main", "dev"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
-
-[[package]]
-name = "docutils"
-version = "0.21.2"
-description = "Docutils -- Python Documentation Utilities"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"},
- {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"},
-]
+markers = {main = "platform_system == \"Windows\""}
[[package]]
name = "exceptiongroup"
-version = "1.2.2"
+version = "1.3.0"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
+markers = "python_version == \"3.10\""
files = [
- {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
- {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
+ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
+ {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
]
+[package.dependencies]
+typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""}
+
[package.extras]
test = ["pytest (>=6)"]
[[package]]
-name = "furo"
-version = "2024.8.6"
-description = "A clean customisable Sphinx documentation theme."
+name = "ghp-import"
+version = "2.1.0"
+description = "Copy your docs directly to the gh-pages branch."
optional = false
-python-versions = ">=3.8"
+python-versions = "*"
+groups = ["dev"]
files = [
- {file = "furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c"},
- {file = "furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01"},
+ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
+ {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
]
[package.dependencies]
-beautifulsoup4 = "*"
-pygments = ">=2.7"
-sphinx = ">=6.0,<9.0"
-sphinx-basic-ng = ">=1.0.0.beta2"
-
-[[package]]
-name = "idna"
-version = "3.10"
-description = "Internationalized Domain Names in Applications (IDNA)"
-optional = false
-python-versions = ">=3.6"
-files = [
- {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
- {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
-]
+python-dateutil = ">=2.8.1"
[package.extras]
-all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+dev = ["flake8", "markdown", "twine", "wheel"]
[[package]]
-name = "imagesize"
-version = "1.4.1"
-description = "Getting image size from png/jpeg/jpeg2000/gif file"
+name = "griffe"
+version = "1.10.0"
+description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.9"
+groups = ["dev"]
files = [
- {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
- {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
+ {file = "griffe-1.10.0-py3-none-any.whl", hash = "sha256:a5eec6d5431cc49eb636b8a078d2409844453c1b0e556e4ba26f8c923047cd11"},
+ {file = "griffe-1.10.0.tar.gz", hash = "sha256:7fe89ebfb5140e0589748888b99680968e5b9ef7e2dcb2b01caf87ec552b66be"},
]
+[package.dependencies]
+colorama = ">=0.4"
+
[[package]]
name = "iniconfig"
version = "2.1.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
{file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
@@ -279,6 +110,7 @@ version = "3.1.6"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
+groups = ["dev"]
files = [
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
@@ -290,12 +122,29 @@ MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
+[[package]]
+name = "markdown"
+version = "3.8.2"
+description = "Python implementation of John Gruber's Markdown."
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24"},
+ {file = "markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45"},
+]
+
+[package.extras]
+docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
+testing = ["coverage", "pyyaml"]
+
[[package]]
name = "markdown-it-py"
version = "3.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
@@ -320,6 +169,7 @@ version = "3.0.2"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.9"
+groups = ["dev"]
files = [
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
@@ -390,46 +240,200 @@ version = "0.1.2"
description = "Markdown URL utilities"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
+[[package]]
+name = "mergedeep"
+version = "1.3.4"
+description = "A deep merge function for 🐍."
+optional = false
+python-versions = ">=3.6"
+groups = ["dev"]
+files = [
+ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
+ {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
+]
+
+[[package]]
+name = "mkdocs"
+version = "1.6.1"
+description = "Project documentation with Markdown."
+optional = false
+python-versions = ">=3.8"
+groups = ["dev"]
+files = [
+ {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"},
+ {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"},
+]
+
+[package.dependencies]
+click = ">=7.0"
+colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
+ghp-import = ">=1.0"
+jinja2 = ">=2.11.1"
+markdown = ">=3.3.6"
+markupsafe = ">=2.0.1"
+mergedeep = ">=1.3.4"
+mkdocs-get-deps = ">=0.2.0"
+packaging = ">=20.5"
+pathspec = ">=0.11.1"
+pyyaml = ">=5.1"
+pyyaml-env-tag = ">=0.1"
+watchdog = ">=2.0"
+
+[package.extras]
+i18n = ["babel (>=2.9.0)"]
+min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"]
+
+[[package]]
+name = "mkdocs-autorefs"
+version = "1.4.2"
+description = "Automatically link across pages in MkDocs."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+ {file = "mkdocs_autorefs-1.4.2-py3-none-any.whl", hash = "sha256:83d6d777b66ec3c372a1aad4ae0cf77c243ba5bcda5bf0c6b8a2c5e7a3d89f13"},
+ {file = "mkdocs_autorefs-1.4.2.tar.gz", hash = "sha256:e2ebe1abd2b67d597ed19378c0fff84d73d1dbce411fce7a7cc6f161888b6749"},
+]
+
+[package.dependencies]
+Markdown = ">=3.3"
+markupsafe = ">=2.0.1"
+mkdocs = ">=1.1"
+
+[[package]]
+name = "mkdocs-get-deps"
+version = "0.2.0"
+description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file"
+optional = false
+python-versions = ">=3.8"
+groups = ["dev"]
+files = [
+ {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"},
+ {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"},
+]
+
+[package.dependencies]
+mergedeep = ">=1.3.4"
+platformdirs = ">=2.2.0"
+pyyaml = ">=5.1"
+
+[[package]]
+name = "mkdocstrings"
+version = "0.30.0"
+description = "Automatic documentation from sources, for MkDocs."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+ {file = "mkdocstrings-0.30.0-py3-none-any.whl", hash = "sha256:ae9e4a0d8c1789697ac776f2e034e2ddd71054ae1cf2c2bb1433ccfd07c226f2"},
+ {file = "mkdocstrings-0.30.0.tar.gz", hash = "sha256:5d8019b9c31ddacd780b6784ffcdd6f21c408f34c0bd1103b5351d609d5b4444"},
+]
+
+[package.dependencies]
+Jinja2 = ">=2.11.1"
+Markdown = ">=3.6"
+MarkupSafe = ">=1.1"
+mkdocs = ">=1.6"
+mkdocs-autorefs = ">=1.4"
+mkdocstrings-python = {version = ">=1.16.2", optional = true, markers = "extra == \"python\""}
+pymdown-extensions = ">=6.3"
+
+[package.extras]
+crystal = ["mkdocstrings-crystal (>=0.3.4)"]
+python = ["mkdocstrings-python (>=1.16.2)"]
+python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
+
+[[package]]
+name = "mkdocstrings-python"
+version = "1.16.12"
+description = "A Python handler for mkdocstrings."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+ {file = "mkdocstrings_python-1.16.12-py3-none-any.whl", hash = "sha256:22ded3a63b3d823d57457a70ff9860d5a4de9e8b1e482876fc9baabaf6f5f374"},
+ {file = "mkdocstrings_python-1.16.12.tar.gz", hash = "sha256:9b9eaa066e0024342d433e332a41095c4e429937024945fea511afe58f63175d"},
+]
+
+[package.dependencies]
+griffe = ">=1.6.2"
+mkdocs-autorefs = ">=1.4"
+mkdocstrings = ">=0.28.3"
+typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
+
[[package]]
name = "packaging"
version = "25.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
files = [
{file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
]
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+optional = false
+python-versions = ">=3.8"
+groups = ["dev"]
+files = [
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.3.8"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"},
+ {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"},
+]
+
+[package.extras]
+docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
+type = ["mypy (>=1.14.1)"]
+
[[package]]
name = "pluggy"
-version = "1.5.0"
+version = "1.6.0"
description = "plugin and hook calling mechanisms for python"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["dev"]
files = [
- {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
- {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
+ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
+ {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
]
[package.extras]
dev = ["pre-commit", "tox"]
-testing = ["pytest", "pytest-benchmark"]
+testing = ["coverage", "pytest", "pytest-benchmark"]
[[package]]
name = "pydantic"
-version = "2.11.4"
+version = "2.11.7"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
- {file = "pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb"},
- {file = "pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d"},
+ {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"},
+ {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"},
]
[package.dependencies]
@@ -440,7 +444,7 @@ typing-inspection = ">=0.4.0"
[package.extras]
email = ["email-validator (>=2.0.0)"]
-timezone = ["tzdata"]
+timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
[[package]]
name = "pydantic-core"
@@ -448,6 +452,7 @@ version = "2.33.2"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
{file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"},
{file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"},
@@ -555,93 +560,188 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pygments"
-version = "2.19.1"
+version = "2.19.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
+groups = ["main", "dev"]
files = [
- {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
- {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
+ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
+ {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
+[[package]]
+name = "pymdown-extensions"
+version = "10.16.1"
+description = "Extension pack for Python Markdown."
+optional = false
+python-versions = ">=3.9"
+groups = ["main", "dev"]
+files = [
+ {file = "pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d"},
+ {file = "pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91"},
+]
+
+[package.dependencies]
+markdown = ">=3.6"
+pyyaml = "*"
+
+[package.extras]
+extra = ["pygments (>=2.19.1)"]
+
[[package]]
name = "pytest"
-version = "8.3.5"
+version = "8.4.1"
description = "pytest: simple powerful testing with Python"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["dev"]
files = [
- {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
- {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
+ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"},
+ {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"},
]
[package.dependencies]
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
-iniconfig = "*"
-packaging = "*"
+colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""}
+iniconfig = ">=1"
+packaging = ">=20"
pluggy = ">=1.5,<2"
+pygments = ">=2.7.2"
tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras]
-dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
[[package]]
-name = "requests"
-version = "2.32.3"
-description = "Python HTTP for Humans."
+name = "python-dateutil"
+version = "2.9.0.post0"
+description = "Extensions to the standard Python datetime module"
optional = false
-python-versions = ">=3.8"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["dev"]
files = [
- {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
- {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
]
[package.dependencies]
-certifi = ">=2017.4.17"
-charset-normalizer = ">=2,<4"
-idna = ">=2.5,<4"
-urllib3 = ">=1.21.1,<3"
+six = ">=1.5"
-[package.extras]
-socks = ["PySocks (>=1.5.6,!=1.5.7)"]
-use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+[[package]]
+name = "pyyaml"
+version = "6.0.2"
+description = "YAML parser and emitter for Python"
+optional = false
+python-versions = ">=3.8"
+groups = ["main", "dev"]
+files = [
+ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
+ {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
+ {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
+ {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
+ {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
+ {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
+ {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
+ {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
+ {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
+ {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
+ {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
+ {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
+ {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
+ {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
+ {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
+ {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
+ {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
+ {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
+ {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
+ {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
+ {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
+ {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
+ {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
+ {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
+ {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
+ {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
+ {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
+ {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
+ {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
+ {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
+ {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
+ {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
+ {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
+ {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
+ {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
+ {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
+ {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
+ {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
+ {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
+ {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
+ {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
+ {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
+ {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
+ {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
+ {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
+ {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
+ {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
+ {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
+ {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
+ {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
+ {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
+ {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
+ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
+]
+
+[[package]]
+name = "pyyaml-env-tag"
+version = "1.1"
+description = "A custom YAML tag for referencing environment variables in YAML files."
+optional = false
+python-versions = ">=3.9"
+groups = ["dev"]
+files = [
+ {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"},
+ {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"},
+]
+
+[package.dependencies]
+pyyaml = "*"
[[package]]
name = "rich"
-version = "14.0.0"
+version = "14.1.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.8.0"
+groups = ["main"]
files = [
- {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"},
- {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"},
+ {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"},
+ {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"},
]
[package.dependencies]
markdown-it-py = ">=2.2.0"
pygments = ">=2.13.0,<3.0.0"
-typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""}
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruamel-yaml"
-version = "0.18.10"
+version = "0.18.14"
description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+groups = ["main"]
files = [
- {file = "ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1"},
- {file = "ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58"},
+ {file = "ruamel.yaml-0.18.14-py3-none-any.whl", hash = "sha256:710ff198bb53da66718c7db27eec4fbcc9aa6ca7204e4c1df2f282b6fe5eb6b2"},
+ {file = "ruamel.yaml-0.18.14.tar.gz", hash = "sha256:7227b76aaec364df15936730efbf7d72b30c0b79b1d578bbb8e3dcb2d81f52b7"},
]
[package.dependencies]
-"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""}
+"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.14\""}
[package.extras]
docs = ["mercurial (>5.7)", "ryd"]
@@ -653,6 +753,8 @@ version = "0.2.12"
description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
+markers = "platform_python_implementation == \"CPython\" and python_version < \"3.14\""
files = [
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"},
{file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"},
@@ -708,205 +810,32 @@ version = "1.5.4"
description = "Tool to Detect Surrounding Shell"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
]
[[package]]
-name = "snowballstemmer"
-version = "2.2.0"
-description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
-optional = false
-python-versions = "*"
-files = [
- {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
- {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
-]
-
-[[package]]
-name = "soupsieve"
-version = "2.7"
-description = "A modern CSS selector implementation for Beautiful Soup."
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"},
- {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"},
-]
-
-[[package]]
-name = "sphinx"
-version = "8.1.3"
-description = "Python documentation generator"
-optional = false
-python-versions = ">=3.10"
-files = [
- {file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"},
- {file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"},
-]
-
-[package.dependencies]
-alabaster = ">=0.7.14"
-babel = ">=2.13"
-colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""}
-docutils = ">=0.20,<0.22"
-imagesize = ">=1.3"
-Jinja2 = ">=3.1"
-packaging = ">=23.0"
-Pygments = ">=2.17"
-requests = ">=2.30.0"
-snowballstemmer = ">=2.2"
-sphinxcontrib-applehelp = ">=1.0.7"
-sphinxcontrib-devhelp = ">=1.0.6"
-sphinxcontrib-htmlhelp = ">=2.0.6"
-sphinxcontrib-jsmath = ">=1.0.1"
-sphinxcontrib-qthelp = ">=1.0.6"
-sphinxcontrib-serializinghtml = ">=1.1.9"
-tomli = {version = ">=2", markers = "python_version < \"3.11\""}
-
-[package.extras]
-docs = ["sphinxcontrib-websupport"]
-lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"]
-test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"]
-
-[[package]]
-name = "sphinx-basic-ng"
-version = "1.0.0b2"
-description = "A modern skeleton for Sphinx themes."
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"},
- {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"},
-]
-
-[package.dependencies]
-sphinx = ">=4.0"
-
-[package.extras]
-docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"]
-
-[[package]]
-name = "sphinx-tabs"
-version = "3.4.7"
-description = "Tabbed views for Sphinx"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "sphinx-tabs-3.4.7.tar.gz", hash = "sha256:991ad4a424ff54119799ba1491701aa8130dd43509474aef45a81c42d889784d"},
- {file = "sphinx_tabs-3.4.7-py3-none-any.whl", hash = "sha256:c12d7a36fd413b369e9e9967a0a4015781b71a9c393575419834f19204bd1915"},
-]
-
-[package.dependencies]
-docutils = "*"
-pygments = "*"
-sphinx = ">=1.8"
-
-[package.extras]
-code-style = ["pre-commit (==2.13.0)"]
-testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "pytest-regressions", "rinohtype"]
-
-[[package]]
-name = "sphinxcontrib-applehelp"
-version = "2.0.0"
-description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"},
- {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"},
-]
-
-[package.extras]
-lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
-standalone = ["Sphinx (>=5)"]
-test = ["pytest"]
-
-[[package]]
-name = "sphinxcontrib-devhelp"
-version = "2.0.0"
-description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"},
- {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"},
-]
-
-[package.extras]
-lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
-standalone = ["Sphinx (>=5)"]
-test = ["pytest"]
-
-[[package]]
-name = "sphinxcontrib-htmlhelp"
-version = "2.1.0"
-description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"},
- {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"},
-]
-
-[package.extras]
-lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
-standalone = ["Sphinx (>=5)"]
-test = ["html5lib", "pytest"]
-
-[[package]]
-name = "sphinxcontrib-jsmath"
-version = "1.0.1"
-description = "A sphinx extension which renders display math in HTML via JavaScript"
-optional = false
-python-versions = ">=3.5"
-files = [
- {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
- {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
-]
-
-[package.extras]
-test = ["flake8", "mypy", "pytest"]
-
-[[package]]
-name = "sphinxcontrib-qthelp"
-version = "2.0.0"
-description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
+name = "six"
+version = "1.17.0"
+description = "Python 2 and 3 compatibility utilities"
optional = false
-python-versions = ">=3.9"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["dev"]
files = [
- {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"},
- {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"},
+ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
+ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
]
-[package.extras]
-lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
-standalone = ["Sphinx (>=5)"]
-test = ["defusedxml (>=0.7.1)", "pytest"]
-
-[[package]]
-name = "sphinxcontrib-serializinghtml"
-version = "2.0.0"
-description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"},
- {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"},
-]
-
-[package.extras]
-lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
-standalone = ["Sphinx (>=5)"]
-test = ["pytest"]
-
[[package]]
name = "tomli"
version = "2.2.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
+groups = ["dev"]
+markers = "python_version == \"3.10\""
files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
@@ -944,64 +873,94 @@ files = [
[[package]]
name = "typer"
-version = "0.15.3"
+version = "0.15.4"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
- {file = "typer-0.15.3-py3-none-any.whl", hash = "sha256:c86a65ad77ca531f03de08d1b9cb67cd09ad02ddddf4b34745b5008f43b239bd"},
- {file = "typer-0.15.3.tar.gz", hash = "sha256:818873625d0569653438316567861899f7e9972f2e6e0c16dab608345ced713c"},
+ {file = "typer-0.15.4-py3-none-any.whl", hash = "sha256:eb0651654dcdea706780c466cf06d8f174405a659ffff8f163cfbfee98c0e173"},
+ {file = "typer-0.15.4.tar.gz", hash = "sha256:89507b104f9b6a0730354f27c39fae5b63ccd0c95b1ce1f1a6ba0cfd329997c3"},
]
[package.dependencies]
-click = ">=8.0.0"
+click = ">=8.0.0,<8.2"
rich = ">=10.11.0"
shellingham = ">=1.3.0"
typing-extensions = ">=3.7.4.3"
[[package]]
name = "typing-extensions"
-version = "4.13.2"
-description = "Backported and Experimental Type Hints for Python 3.8+"
+version = "4.14.1"
+description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["main", "dev"]
files = [
- {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"},
- {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"},
+ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"},
+ {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"},
]
+markers = {dev = "python_version == \"3.10\""}
[[package]]
name = "typing-inspection"
-version = "0.4.0"
+version = "0.4.1"
description = "Runtime typing introspection tools"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
- {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"},
- {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"},
+ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"},
+ {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"},
]
[package.dependencies]
typing-extensions = ">=4.12.0"
[[package]]
-name = "urllib3"
-version = "2.4.0"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
+name = "watchdog"
+version = "6.0.0"
+description = "Filesystem events monitoring"
optional = false
python-versions = ">=3.9"
-files = [
- {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"},
- {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"},
+groups = ["dev"]
+files = [
+ {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"},
+ {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"},
+ {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"},
+ {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"},
+ {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"},
+ {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"},
+ {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"},
+ {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"},
+ {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"},
+ {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"},
+ {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"},
+ {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"},
+ {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"},
+ {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"},
+ {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"},
+ {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"},
+ {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"},
+ {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"},
+ {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"},
+ {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"},
+ {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"},
+ {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"},
+ {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"},
+ {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"},
+ {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"},
+ {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"},
+ {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"},
+ {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"},
+ {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"},
+ {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"},
]
[package.extras]
-brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
-h2 = ["h2 (>=4,<5)"]
-socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
-zstd = ["zstandard (>=0.18.0)"]
+watchmedo = ["PyYAML (>=3.10)"]
[metadata]
-lock-version = "2.0"
+lock-version = "2.1"
python-versions = "^3.10"
-content-hash = "4821238bf1d24a0a5c5afd1d7852fd0862b7e2dfaf564a897df87c8e1310107b"
+content-hash = "4518c79cecbdff96f897154506a0a0183050a76ac4dda54278518f4629690f58"
diff --git a/pyproject.toml b/pyproject.toml
index 8b8e26a..c18b327 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,10 +1,10 @@
[tool.poetry]
name = "importspy"
-version = "0.3.3"
+version = "0.4.0"
description = "ImportSpy ensures structural integrity, runtime compliance, and security for external modules, preventing inconsistencies and enforcing controlled execution."
license = "MIT"
authors = ["Luca Atella "]
-readme = "README.rst"
+readme = "README.md"
packages = [{include = "importspy", from = "src"}]
[tool.poetry.dependencies]
@@ -12,13 +12,13 @@ python = "^3.10"
pydantic = "^2.9.2"
ruamel-yaml = "^0.18.10"
typer = "^0.15.2"
+pymdown-extensions = "^10.16.1"
[tool.poetry.group.dev.dependencies]
-furo = "^2024.8.6"
-sphinx = ">=5,<9"
pytest = "^8.3.3"
-sphinx-tabs = "^3.4.7"
+mkdocs = "^1.6.1"
+mkdocstrings = {version = "^0.30.0", extras = ["python"]}
[tool.poetry.urls]
Repository = "https://github.com/atellaluca/importspy"
diff --git a/src/importspy/cli.py b/src/importspy/cli.py
index 837fb5a..db25877 100644
--- a/src/importspy/cli.py
+++ b/src/importspy/cli.py
@@ -1,57 +1,27 @@
"""
-Command-line interface for ImportSpy import contract validation.
+Command-line interface (CLI) for validating Python modules against ImportSpy contracts.
-This module defines the CLI entry point `importspy`, which enables developers and CI/CD pipelines
-to validate Python modules against a declared import contract written in YAML format.
+This module defines the `importspy` CLI command, enabling local and automated validation
+of a Python file against a YAML-based SpyModel contract. It is designed for use in
+CI/CD pipelines, plugin systems, or developer workflows.
-Overview:
----------
-The CLI allows structural and runtime compliance checks through:
-- Dynamic import of a Python module from file.
-- Parsing of the import contract from a `.yml` file.
-- Validation of the module’s structure and metadata.
-- Clear CLI feedback with styled messages.
-- Optional log verbosity control for debugging purposes.
+Features:
+- Loads and executes the specified Python module.
+- Parses the YAML contract file describing expected structure and runtime conditions.
+- Validates that the module complies with the declared interface and environment.
+- Provides user-friendly CLI feedback, including optional logging.
-Main Command:
--------------
-- `importspy`: Validates a Python module against an import contract definition.
+Use cases:
+- Enforcing structure of external plugins before loading.
+- Automating validation in GitHub Actions or other CI tools.
+- Assuring consistency in modular libraries or educational tools.
-Decorators:
------------
-- `handle_validation_error`: Intercepts and formats validation errors
- to improve user experience from the terminal.
+Example:
+ importspy ./examples/my_plugin.py -s ./contracts/expected.yml --log-level DEBUG
-Usage Examples:
----------------
-Basic validation:
-
-.. code-block:: bash
-
- importspy ./examples/my_module.py
-
-With contract and log level:
-
-.. code-block:: bash
-
- importspy ./my_module.py --spymodel contracts/example.yml --log-level DEBUG
-
-Options:
---------
---spymodel / -s : str
- Path to the YAML file containing the import contract. Default: `spymodel.yml`.
-
---log-level / -l : str
- Log verbosity. Accepts: DEBUG, INFO, WARNING, ERROR.
-
---version / -v
- Show ImportSpy’s current version.
-
-Notes:
-------
-- Validation is handled by the `Spy` core class.
-- This command is ideal for local development, CI enforcement, or release pipelines.
-- Validation issues are surfaced through color-coded output, not raw exceptions.
+Note:
+ Validation is powered by the core `Spy` class.
+ Validation errors are caught and displayed with enhanced CLI formatting.
"""
import typer
@@ -70,17 +40,20 @@
def handle_validation_error(func):
"""
- Intercepts validation errors and formats them for CLI output.
+ Decorator that formats validation errors for CLI output.
- Provides color-coded feedback based on validation result.
+ Intercepts `ValueError` raised by the `Spy.importspy()` call and presents
+ the error reason in a readable, styled terminal message.
+
+ Used to wrap the main `importspy()` CLI command.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
- typer.echo(typer.style("✅ Module is compliant with the import contract!", fg=typer.colors.GREEN, bold=True))
+ typer.echo(typer.style("Module is compliant with the import contract.", fg=typer.colors.GREEN, bold=True))
except ValueError as ve:
- typer.echo(typer.style("❌ Module is NOT compliant with the import contract.", fg=typer.colors.RED, bold=True))
+ typer.echo(typer.style("Module is NOT compliant with the import contract.", fg=typer.colors.RED, bold=True))
typer.echo()
typer.secho("Reason:", fg="magenta", bold=True)
typer.echo(f" {typer.style(str(ve), fg='yellow')}")
@@ -97,24 +70,45 @@ class LogLevel(str, Enum):
@app.command()
@handle_validation_error
def importspy(
- version: Optional[bool] = typer.Option(
- None,
- "--version",
- "-v",
- callback=lambda value: show_version(value),
- is_eager=True,
- help="Show the version and exit."
- ),
- modulepath: Optional[str] = typer.Argument(str, help="Path to the Python module to load and validate."),
- spymodel_path: Optional[str] = typer.Option(
- "spymodel.yml", "--spymodel", "-s", help="Path to the import contract file (.yml)."
- ),
- log_level: Optional[LogLevel] = typer.Option(
- None, "--log-level", "-l", help="Log level for output verbosity."
- )
+ version: Optional[bool] = typer.Option(
+ None,
+ "--version",
+ "-v",
+ callback=lambda value: show_version(value),
+ is_eager=True,
+ help="Show the version and exit."
+ ),
+ modulepath: Optional[str] = typer.Argument(
+ str,
+ help="Path to the Python module to load and validate."
+ ),
+ spymodel_path: Optional[str] = typer.Option(
+ "spymodel.yml",
+ "--spymodel",
+ "-s",
+ help="Path to the import contract file (.yml)."
+ ),
+ log_level: Optional[LogLevel] = typer.Option(
+ None,
+ "--log-level",
+ "-l",
+ help="Log level for output verbosity."
+ )
) -> ModuleType:
"""
- CLI command to validate a Python module against a YAML-defined import contract.
+ Validates a Python module against a YAML-defined SpyModel contract.
+
+ Args:
+ version (bool, optional): Show ImportSpy version and exit.
+ modulepath (str): Path to the Python module to validate.
+ spymodel_path (str, optional): Path to the YAML contract file. Defaults to `spymodel.yml`.
+ log_level (LogLevel, optional): Set logging verbosity (DEBUG, INFO, WARNING, ERROR).
+
+ Returns:
+ ModuleType: The validated Python module (if compliant).
+
+ Raises:
+ ValueError: If the module does not conform to the contract.
"""
module_path = Path(modulepath).resolve()
module_name = module_path.stem
@@ -131,7 +125,10 @@ def importspy(
def show_version(value: bool):
"""
- Displays the current ImportSpy version and exits.
+ Displays the current version of ImportSpy and exits the process.
+
+ Args:
+ value (bool): If True, prints the version and exits immediately.
"""
if value:
typer.secho(f"ImportSpy v{__version__}", fg="cyan", bold=True)
@@ -139,6 +136,10 @@ def show_version(value: bool):
def main():
"""
- Entry point for CLI execution.
+ CLI entry point.
+
+ Executes the `importspy` Typer app, allowing CLI usage like:
+
+ $ importspy my_module.py -s my_contract.yml
"""
app()
diff --git a/src/importspy/config.py b/src/importspy/config.py
index 663a1bb..1f875b3 100644
--- a/src/importspy/config.py
+++ b/src/importspy/config.py
@@ -1,63 +1,32 @@
class Config:
+
"""
- Static configuration container for ImportSpy.
+ Static configuration for ImportSpy.
- This class centralizes all constant values used for runtime validation,
- defining supported architectures, operating systems, Python versions,
- interpreter implementations, attribute classifications, and annotation types.
- These constants ensure consistency and safety during contract enforcement
- across diverse execution environments.
+ This class defines the baseline constants used during runtime and structural
+ validation of Python modules. Values declared here represent the supported
+ options for platforms, interpreters, Python versions, class attribute types,
+ and type annotations within a SpyModel contract.
- Attributes:
- ARCH_x86_64 (str): Identifier for the x86_64 CPU architecture.
- ARCH_AARCH64 (str): Identifier for the AArch64 architecture.
- ARCH_ARM (str): Identifier for the ARM architecture.
- ARCH_ARM64 (str): Identifier for the ARM64 architecture.
- ARCH_I386 (str): Identifier for the i386 (32-bit Intel) architecture.
- ARCH_PPC64 (str): Identifier for the PowerPC 64-bit architecture.
- ARCH_PPC64LE (str): Identifier for PowerPC 64-bit Little Endian architecture.
- ARCH_S390X (str): Identifier for IBM s390x architecture.
+ These constants are used internally to validate compatibility and enforce
+ declared constraints across diverse environments.
- OS_WINDOWS (str): Identifier for Windows operating systems.
- OS_LINUX (str): Identifier for Linux operating systems.
- OS_MACOS (str): Identifier for macOS (Darwin-based) operating systems.
+ Categories:
+ ----------
+ • Architectures: CPU instruction sets (e.g. x86_64, arm64).
+ • Operating Systems: Target OS identifiers (e.g. linux, windows).
+ • Python Versions: Compatible interpreter versions.
+ • Interpreters: Supported Python implementations.
+ • Attribute Types: Class vs. instance variables.
+ • Type Annotations: Accepted runtime-compatible types.
- PYTHON_VERSION_3_13 (str): Supported Python version 3.13.
- PYTHON_VERSION_3_12 (str): Supported Python version 3.12.
- PYTHON_VERSION_3_11 (str): Supported Python version 3.11.
- PYTHON_VERSION_3_10 (str): Supported Python version 3.10.
- PYTHON_VERSION_3_9 (str): Supported Python version 3.9.
-
- INTERPRETER_CPYTHON (str): Identifier for CPython interpreter.
- INTERPRETER_PYPY (str): Identifier for PyPy interpreter.
- INTERPRETER_JYTHON (str): Identifier for Jython interpreter.
- INTERPRETER_IRON_PYTHON (str): Identifier for IronPython interpreter.
- INTERPRETER_STACKLESS (str): Identifier for Stackless Python.
- INTERPRETER_MICROPYTHON (str): Identifier for MicroPython interpreter.
- INTERPRETER_BRYTHON (str): Identifier for Brython interpreter.
- INTERPRETER_PYSTON (str): Identifier for Pyston interpreter.
- INTERPRETER_GRAALPYTHON (str): Identifier for GraalPython interpreter.
- INTERPRETER_RUSTPYTHON (str): Identifier for RustPython interpreter.
- INTERPRETER_NUITKA (str): Identifier for Nuitka interpreter.
- INTERPRETER_TRANSCRYPT (str): Identifier for Transcrypt interpreter.
-
- CLASS_TYPE (str): Label for class-level attributes in contract definitions.
- INSTANCE_TYPE (str): Label for instance-level attributes in contract definitions.
-
- ANNOTATION_INT (str): Annotation identifier for integers.
- ANNOTATION_FLOAT (str): Annotation identifier for floats.
- ANNOTATION_STR (str): Annotation identifier for strings.
- ANNOTATION_BOOL (str): Annotation identifier for booleans.
- ANNOTATION_LIST (str): Annotation identifier for generic lists.
- ANNOTATION_DICT (str): Annotation identifier for generic dictionaries.
- ANNOTATION_TUPLE (str): Annotation identifier for generic tuples.
- ANNOTATION_SET (str): Annotation identifier for sets.
- ANNOTATION_OPTIONAL (str): Annotation identifier for optional values.
- ANNOTATION_UNION (str): Annotation identifier for union types.
- ANNOTATION_ANY (str): Annotation identifier for untyped (any) values.
- ANNOTATION_CALLABLE (str): Annotation identifier for callable objects.
+ Examples:
+ ---------
+ - A contract may require `arch: x86_64` and `interpreter: CPython`.
+ - A method argument may be annotated with `Optional[str]`.
"""
+
# Supported Architectures
ARCH_x86_64 = "x86_64"
ARCH_AARCH64 = "aarch64"
@@ -111,6 +80,6 @@ class Config:
ANNOTATION_UNION = "Union"
ANNOTATION_ANY = "Any"
ANNOTATION_CALLABLE = "Callable"
- ANNOTATION_LIST = "List"
- ANNOTATION_DICT = "Dict"
- ANNOTATION_TUPLE = "Tuple"
+ ANNOTATION_LIST_TYPING = "List"
+ ANNOTATION_DICT_TYPING = "Dict"
+ ANNOTATION_TUPLE_TYPING = "Tuple"
diff --git a/src/importspy/constants.py b/src/importspy/constants.py
index 6bf93ee..28442e8 100644
--- a/src/importspy/constants.py
+++ b/src/importspy/constants.py
@@ -1,131 +1,283 @@
from .config import Config
+from enum import Enum
class Constants:
"""
- Constants used internally by ImportSpy's runtime validation engine.
+ Canonical constants used by ImportSpy's runtime validation engine.
- This class defines the canonical reference values used during import contract
- validation, including supported architectures, operating systems, Python
- interpreters, annotation types, and structural metadata keys.
+ This class acts as a reference for valid architectures, operating systems,
+ Python interpreters, structural types, and annotation labels. All values
+ defined here represent the allowed forms of metadata used to verify
+ import contracts.
- Unlike `Config`, which defines values dynamically from the runtime or user
- environment, `Constants` serves as the fixed baseline for what ImportSpy
- considers valid and contract-compliant.
+ Unlike `Config`, which reflects the current runtime, `Constants` provides
+ the fixed set of values used for validation logic.
+ """
- Attributes:
- KNOWN_ARCHITECTURES (List[str]):
- List of CPU architectures supported in runtime validation,
- including 'x86_64', 'arm64', 'i386', and others.
+ class SupportedArchitectures(str, Enum):
+ """Valid CPU architectures accepted in contracts."""
+ ARCH_x86_64 = Config.ARCH_x86_64
+ ARCH_AARCH64 = Config.ARCH_AARCH64
+ ARCH_ARM = Config.ARCH_ARM
+ ARCH_ARM64 = Config.ARCH_ARM64
+ ARCH_I386 = Config.ARCH_I386
+ ARCH_PPC64 = Config.ARCH_PPC64
+ ARCH_PPC64LE = Config.ARCH_PPC64LE
+ ARCH_S390X = Config.ARCH_S390X
- SUPPORTED_OS (List[str]):
- List of supported operating systems: 'linux', 'windows', and 'darwin'.
+ class SupportedOS(str, Enum):
+ """Valid operating systems accepted in contracts."""
+ OS_WINDOWS = Config.OS_WINDOWS
+ OS_LINUX = Config.OS_LINUX
+ OS_MACOS = Config.OS_MACOS
- SUPPORTED_PYTHON_VERSION (List[str]):
- List of supported Python versions, e.g. '3.9', '3.10', '3.11', etc.
+ class SupportedPythonImplementations(str, Enum):
+ """Valid Python interpreter implementations."""
+ INTERPRETER_CPYTHON = Config.INTERPRETER_CPYTHON
+ INTERPRETER_PYPY = Config.INTERPRETER_PYPY
+ INTERPRETER_JYTHON = Config.INTERPRETER_JYTHON
+ INTERPRETER_IRON_PYTHON = Config.INTERPRETER_IRON_PYTHON
+ INTERPRETER_MICROPYTHON = Config.INTERPRETER_MICROPYTHON
+ INTERPRETER_BRYTHON = Config.INTERPRETER_BRYTHON
+ INTERPRETER_PYSTON = Config.INTERPRETER_PYSTON
+ INTERPRETER_GRAALPYTHON = Config.INTERPRETER_GRAALPYTHON
+ INTERPRETER_RUSTPYTHON = Config.INTERPRETER_RUSTPYTHON
+ INTERPRETER_NUITKA = Config.INTERPRETER_NUITKA
+ INTERPRETER_TRANSCRYPT = Config.INTERPRETER_TRANSCRYPT
- SUPPORTED_PYTHON_IMPLEMENTATION (List[str]):
- Python interpreter implementations recognized by ImportSpy,
- such as 'CPython', 'PyPy', 'IronPython', and others.
+ class SupportedClassAttributeTypes(str, Enum):
+ """Type of attribute in a class-level contract."""
+ CLASS = Config.CLASS_TYPE
+ INSTANCE = Config.INSTANCE_TYPE
- SUPPORTED_CLASS_ATTRIBUTE_TYPES (List[str]):
- Allowed attribute type classifications: 'class' and 'instance'.
+ NAME = "Name"
+ VALUE = "Value"
+ ANNOTATION = "Annotation"
- SUPPORTED_ANNOTATIONS (List[str]):
- Allowed annotation types used for validating variables,
- arguments, and return values. Includes types such as
- 'int', 'str', 'Optional', 'Union', 'Callable', etc.
+ CLASS_TYPE = Config.CLASS_TYPE
+ INSTANCE_TYPE = Config.INSTANCE_TYPE
- NAME (str):
- Metadata key used for referencing object names in the model.
+ class SupportedAnnotations(str, Enum):
+ """Supported type annotations for validation purposes."""
+ INT = Config.ANNOTATION_INT
+ FLOAT = Config.ANNOTATION_FLOAT
+ STR = Config.ANNOTATION_STR
+ BOOL = Config.ANNOTATION_BOOL
+ LIST = Config.ANNOTATION_LIST
+ DICT = Config.ANNOTATION_DICT
+ TUPLE = Config.ANNOTATION_TUPLE
+ SET = Config.ANNOTATION_SET
+ OPTIONAL = Config.ANNOTATION_OPTIONAL
+ UNION = Config.ANNOTATION_UNION
+ ANY = Config.ANNOTATION_ANY
+ CALLABLE = Config.ANNOTATION_CALLABLE
+ LIST_TYPING = Config.ANNOTATION_LIST_TYPING
+ DICT_TYPING = Config.ANNOTATION_DICT_TYPING
+ TUPLE_TYPING = Config.ANNOTATION_TUPLE_TYPING
- VALUE (str):
- Metadata key used to represent literal values in contracts.
+ LOG_MESSAGE_TEMPLATE = (
+ "[Operation: {operation}] [Status: {status}] [Details: {details}]"
+ )
- ANNOTATION (str):
- Metadata key used to refer to a declared annotation in contracts.
- CLASS_TYPE (str):
- String literal used to label a class-level attribute type.
+class Contexts(str, Enum):
+ """
+ Context types used for contract validation.
+
+ These labels identify which layer of the system the error or constraint applies to.
+ """
+ RUNTIME_CONTEXT = "runtime"
+ ENVIRONMENT_CONTEXT = "environment"
+ MODULE_CONTEXT = "module"
+ CLASS_CONTEXT = "class"
- INSTANCE_TYPE (str):
- String literal used to label an instance-level attribute type.
- LOG_MESSAGE_TEMPLATE (str):
- Template string for standardized log message formatting
- during contract evaluation and model parsing.
+class Errors:
"""
+ Reusable error string templates and labels.
- KNOWN_ARCHITECTURES = [
- Config.ARCH_x86_64,
- Config.ARCH_AARCH64,
- Config.ARCH_ARM,
- Config.ARCH_ARM64,
- Config.ARCH_I386,
- Config.ARCH_PPC64,
- Config.ARCH_PPC64LE,
- Config.ARCH_S390X
- ]
-
- SUPPORTED_OS = [
- Config.OS_WINDOWS,
- Config.OS_LINUX,
- Config.OS_MACOS
- ]
-
- SUPPORTED_PYTHON_VERSION=[
- Config.PYTHON_VERSION_3_13,
- Config.PYTHON_VERSION_3_12,
- Config.PYTHON_VERSION_3_11,
- Config.PYTHON_VERSION_3_10,
- Config.PYTHON_VERSION_3_9
- ]
-
- SUPPORTED_PYTHON_IMPLEMENTATION = [
- Config.INTERPRETER_CPYTHON,
- Config.INTERPRETER_PYPY,
- Config.INTERPRETER_JYTHON,
- Config.INTERPRETER_IRON_PYTHON,
- Config.INTERPRETER_MICROPYTHON,
- Config.INTERPRETER_BRYTHON,
- Config.INTERPRETER_PYSTON,
- Config.INTERPRETER_GRAALPYTHON,
- Config.INTERPRETER_RUSTPYTHON,
- Config.INTERPRETER_NUITKA,
- Config.INTERPRETER_TRANSCRYPT
- ]
-
- SUPPORTED_CLASS_ATTRIBUTE_TYPES = [
- Config.CLASS_TYPE,
- Config.INSTANCE_TYPE
- ]
+ This utility provides consistent formatting for all error messages
+ generated by ImportSpy. It supports singular and plural forms, as well as
+ different validation categories: missing, mismatch, and invalid.
+ """
- NAME = "Name"
- VALUE = "Value"
- ANNOTATION = "Annotation"
+ TEMPLATE_KEY = "template"
+ SOLUTION_KEY = "solution"
+ SCOPE_VARIABLE = "variable"
+ SCOPE_ARGUMENT = "argument"
- CLASS_TYPE = Config.CLASS_TYPE
- INSTANCE_TYPE = Config.INSTANCE_TYPE
+ ENTITY_MESSAGES = "entity"
+ COLLECTIONS_MESSAGES = "collections"
- SUPPORTED_ANNOTATIONS = [
- Config.ANNOTATION_INT,
- Config.ANNOTATION_FLOAT,
- Config.ANNOTATION_STR,
- Config.ANNOTATION_BOOL,
- Config.ANNOTATION_LIST,
- Config.ANNOTATION_DICT,
- Config.ANNOTATION_TUPLE,
- Config.ANNOTATION_SET,
- Config.ANNOTATION_OPTIONAL,
- Config.ANNOTATION_UNION,
- Config.ANNOTATION_ANY,
- Config.ANNOTATION_CALLABLE,
- Config.ANNOTATION_LIST,
- Config.ANNOTATION_DICT,
- Config.ANNOTATION_TUPLE
- ]
+ CONTEXT_INTRO = {
+ Contexts.RUNTIME_CONTEXT: "Runtime constraint violation",
+ Contexts.ENVIRONMENT_CONTEXT: "Environment validation failure",
+ Contexts.MODULE_CONTEXT: "Module structural inconsistency",
+ Contexts.CLASS_CONTEXT: "Class contract violation"
+ }
- LOG_MESSAGE_TEMPLATE = (
- "[Operation: {operation}] [Status: {status}] "
- "[Details: {details}]"
- )
+ class Category(str, Enum):
+ """Validation error types."""
+ MISSING = "missing"
+ MISMATCH = "mismatch"
+ INVALID = "invalid"
+
+ # Label formatting templates for each contract context
+ RUNTIME_LABEL_TEMPLATE = {
+ ENTITY_MESSAGES: 'The runtime "{runtime_1}"',
+ COLLECTIONS_MESSAGES: 'The runtimes "{runtimes_1}"'
+ }
+
+ SYSTEM_LABEL_TEMPLATE = {
+ ENTITY_MESSAGES: 'The system "{system_1}"',
+ COLLECTIONS_MESSAGES: 'systems "{systems_1}"'
+ }
+
+ PYTHON_LABEL_TEMPLATE = {
+ ENTITY_MESSAGES: 'The python "{python_1}"',
+ COLLECTIONS_MESSAGES: 'The pythons "{pythons_1}"'
+ }
+
+ VARIABLES_LABEL_TEMPLATE = {
+ SCOPE_VARIABLE: {
+ ENTITY_MESSAGES: {
+ Contexts.ENVIRONMENT_CONTEXT: 'The environment variable "{environment_variable_name}"',
+ Contexts.MODULE_CONTEXT: 'The variable "{variable_name}" in module "{module_name}"',
+ Contexts.CLASS_CONTEXT: 'The {attribute_type} attribute "{attribute_name}" in class "{class_name}"'
+ },
+ COLLECTIONS_MESSAGES: {
+ Contexts.ENVIRONMENT_CONTEXT: 'The environment "{environment_1}"',
+ Contexts.MODULE_CONTEXT: 'The variables "{variables_1}"',
+ Contexts.CLASS_CONTEXT: 'The attributes "{attributes_1}"'
+ }
+ },
+ SCOPE_ARGUMENT: {
+ ENTITY_MESSAGES: {
+ Contexts.MODULE_CONTEXT: 'The argument "{argument_name}" of function "{function_name}"',
+ Contexts.CLASS_CONTEXT: 'The argument "{argument_name}" of method "{method_name}" in class "{class_name}"'
+ },
+ COLLECTIONS_MESSAGES: {
+ Contexts.MODULE_CONTEXT: 'The arguments "{arguments_1}" of function "{function_name}"',
+ Contexts.CLASS_CONTEXT: 'The arguments "{arguments_1}" of method "{method_name}", in class "{class_name}"'
+ }
+ }
+ }
+
+ FUNCTIONS_LABEL_TEMPLATE = {
+ ENTITY_MESSAGES: {
+ Contexts.MODULE_CONTEXT: 'The function "{function_name}" in module "{filename}"',
+ Contexts.CLASS_CONTEXT: 'The method "{method_name}" in class "{class_name}"'
+ },
+ COLLECTIONS_MESSAGES: {
+ Contexts.MODULE_CONTEXT: 'The functions "{functions_1}" in module "{filename}"',
+ Contexts.CLASS_CONTEXT: 'The methods "{methods_1}" in class "{class_name}"'
+ }
+ }
+
+ MODULE_LABEL_TEMPLATE = {
+ ENTITY_MESSAGES: {
+ Contexts.CLASS_CONTEXT: 'The class "{class_name}"',
+ Contexts.RUNTIME_CONTEXT: 'The module "{filename}"',
+ Contexts.ENVIRONMENT_CONTEXT: 'The version "{version}" of module "{filename}"'
+ },
+ COLLECTIONS_MESSAGES: {
+ Contexts.CLASS_CONTEXT: 'The classes "{classes_1}" in module "{filename}"'
+ }
+ }
+
+ # Dynamic variable keys used in label formatting
+ KEY_RUNTIMES_1 = "runtimes_1"
+ KEY_SYSTEMS_1 = "systems_1"
+ KEY_PYTHONS_1 = "pythons_1"
+ KEY_PYTHON_1 = "python_1"
+ KEY_ENVIRONMENT_1 = "environment_1"
+ KEY_ENVIRONMENT_VARIABLE_NAME = "environment_variable_name"
+ KEY_MODULES_1 = "modules_1"
+ KEY_VARIABLES_1 = "variables_1"
+ KEY_ATTRIBUTES_1 = "attributes_1"
+ KEY_ARGUMENTS_1 = "arguments_1"
+ KEY_FUNCTIONS_1 = "functions_1"
+ KEY_CLASSES_1 = "classes_1"
+ KEY_METHODS_1 = "methods_1"
+
+ KEY_VARIABLE_NAME = "variable_name"
+ KEY_ARGUMENT_NAME = "argument_name"
+ KEY_FUNCTION_NAME = "function_name"
+ KEY_METHOD_NAME = "method_name"
+ KEY_MODULE_NAME = "module_name"
+ KEY_ATTRIBUTE_TYPE = "attribute_type"
+ KEY_ATTRIBUTE_NAME = "attribute_name"
+ KEY_CLASS_NAME = "class_name"
+ KEY_MODULE_VERSION = "version"
+ KEY_FILE_NAME = "filename"
+
+ # Dynamic template values used to build error labels
+ VARIABLES_DINAMIC_PAYLOAD = {
+ SCOPE_VARIABLE: {
+ ENTITY_MESSAGES: {
+ Contexts.ENVIRONMENT_CONTEXT: KEY_ENVIRONMENT_VARIABLE_NAME,
+ Contexts.MODULE_CONTEXT: KEY_VARIABLE_NAME,
+ Contexts.CLASS_CONTEXT: KEY_ATTRIBUTE_NAME
+ },
+ COLLECTIONS_MESSAGES: {
+ Contexts.ENVIRONMENT_CONTEXT: KEY_ENVIRONMENT_1,
+ Contexts.MODULE_CONTEXT: KEY_VARIABLES_1,
+ Contexts.CLASS_CONTEXT: KEY_ATTRIBUTES_1
+ }
+ },
+ SCOPE_ARGUMENT: {
+ ENTITY_MESSAGES: {
+ Contexts.MODULE_CONTEXT: KEY_ARGUMENT_NAME,
+ Contexts.CLASS_CONTEXT: KEY_ARGUMENT_NAME
+ },
+ COLLECTIONS_MESSAGES: {
+ Contexts.MODULE_CONTEXT: KEY_ARGUMENTS_1,
+ Contexts.CLASS_CONTEXT: KEY_ARGUMENTS_1
+ }
+ }
+ }
+
+ FUNCTIONS_DINAMIC_PAYLOAD = {
+ ENTITY_MESSAGES: {
+ Contexts.MODULE_CONTEXT: KEY_FUNCTION_NAME,
+ Contexts.CLASS_CONTEXT: KEY_METHOD_NAME
+ },
+ COLLECTIONS_MESSAGES: {
+ Contexts.MODULE_CONTEXT: KEY_FUNCTIONS_1,
+ Contexts.CLASS_CONTEXT: KEY_METHODS_1
+ }
+ }
+
+ ERROR_MESSAGE_TEMPLATES = {
+ Category.MISSING: {
+ ENTITY_MESSAGES: {
+ TEMPLATE_KEY: '{label} is declared but missing.',
+ SOLUTION_KEY: 'Ensure it is properly defined and implemented.'
+ },
+ COLLECTIONS_MESSAGES: {
+ TEMPLATE_KEY: '{label} are declared but missing.',
+ SOLUTION_KEY: 'Ensure all of them are properly defined and implemented.'
+ }
+ },
+ Category.MISMATCH: {
+ ENTITY_MESSAGES: {
+ TEMPLATE_KEY: '{label} does not match the expected value. Expected: {expected!r}, Found: {actual!r}.',
+ SOLUTION_KEY: 'Check the value and update the contract or implementation accordingly.'
+ },
+ COLLECTIONS_MESSAGES: {
+ TEMPLATE_KEY: '{label} do not match the expected values. Expected: {expected!r}, Found: {actual!r}.',
+ SOLUTION_KEY: 'Review the values and update the contract or implementation as needed.'
+ }
+ },
+ Category.INVALID: {
+ ENTITY_MESSAGES: {
+ TEMPLATE_KEY: '{label} has an invalid value. Allowed values: {allowed}. Found: {found!r}.',
+ SOLUTION_KEY: 'Update the value to one of the allowed options.'
+ },
+ COLLECTIONS_MESSAGES: {
+ TEMPLATE_KEY: '{label} have invalid values. Allowed values: {allowed}. Found: {found!r}.',
+ SOLUTION_KEY: 'Update the values to be within the allowed options.'
+ }
+ }
+ }
diff --git a/src/importspy/errors.py b/src/importspy/errors.py
deleted file mode 100644
index e5b0562..0000000
--- a/src/importspy/errors.py
+++ /dev/null
@@ -1,139 +0,0 @@
-class Errors:
-
- """
- Central repository for error messages used in ImportSpy’s validation engine.
-
- This class contains formatted string constants for every type of structural,
- semantic, and runtime validation error that can be raised during contract
- evaluation. These error messages provide actionable feedback and are used
- throughout ImportSpy's exception handling system.
-
- The format strings typically include placeholders for contextual details,
- such as expected and actual values, function names, class names, or
- annotation types. Grouped by category, these constants help keep the
- validation engine consistent and maintainable.
-
- Attributes:
- ANALYSIS_RECURSION_WARNING (str):
- General warning when the validation process detects recursive self-analysis.
-
- FILENAME_MISMATCH (str):
- Raised when the module filename does not match the expected contract.
-
- VERSION_MISMATCH (str):
- Triggered when the module version deviates from the one declared in the contract.
-
- ENV_VAR_MISSING (str):
- Raised when a required environment variable is not found in the system.
-
- ENV_VAR_MISMATCH (str):
- Indicates a mismatch between the expected and actual values of an environment variable.
-
- VAR_MISSING (str):
- Raised when a required variable is not present in the importing module.
-
- VAR_MISMATCH (str):
- Raised when a variable is present but its value does not match what the contract expects.
-
- FUNCTIONS_MISSING (str):
- Used when one or more expected functions are missing from the module.
-
- FUNCTION_RETURN_ANNOTATION_MISMATCH (str):
- Indicates a mismatch in the return type annotation of a function.
-
- VARIABLE_MISMATCH (str):
- Raised when a declared variable's value does not match the expected value.
-
- VARIABLE_MISSING (str):
- Raised when a declared variable is not found.
-
- ARGUMENT_MISMATCH (str):
- Raised when a function argument has an unexpected name or annotation.
-
- ARGUMENT_MISSING (str):
- Raised when a required argument is missing in the function signature.
-
- CLASS_MISSING (str):
- Triggered when a required class is not defined in the importing module.
-
- CLASS_ATTRIBUTE_MISSING (str):
- Raised when an expected attribute is not found in a class definition.
-
- CLASS_ATTRIBUTE_MISMATCH (str):
- Raised when an attribute exists but its value does not match what the contract expects.
-
- CLASS_SUPERCLASS_MISSING (str):
- Triggered when a required superclass is missing from a class declaration.
-
- INVALID_ATTRIBUTE_TYPE (str):
- Raised when an attribute has an unsupported type.
-
- INVALID_ARCHITECTURE (str):
- Triggered when the system architecture does not match any of the allowed values.
-
- INVALID_OS (str):
- Triggered when the operating system is not among those supported.
-
- INVALID_PYTHON_VERSION (str):
- Raised when the current Python version is not one of the accepted versions.
-
- INVALID_PYTHON_INTERPRETER (str):
- Raised when the Python interpreter is not among the supported ones.
-
- INVALID_ANNOTATION (str):
- Raised when a variable, argument, or return annotation is unsupported.
-
- ELEMENT_MISSING (str):
- Generic error for any expected element missing from the system or module context.
- """
-
- # General Warnings
- ANALYSIS_RECURSION_WARNING = (
- "Warning: Analysis recursion detected. Avoid analyzing code that itself handles analysis, "
- "to prevent stack overflow or performance issues."
- )
-
- # Module Validation Errors
- FILENAME_MISMATCH = "Filename mismatch: expected '{0}', found '{1}'."
- VERSION_MISMATCH = "Version mismatch: expected '{0}', found '{1}'."
- ENV_VAR_MISSING = "Missing environment variable: '{0}'. Ensure it is defined in the system."
- ENV_VAR_MISMATCH = "Environment variable value mismatch: expected '{0}', found '{1}'."
- VAR_MISSING = "Missing variable: '{0}'. Ensure it is defined."
- VAR_MISMATCH = "Variable value mismatch: expected '{0}', found '{1}'."
- FUNCTIONS_MISSING = "Missing {0}: '{1}'. Ensure it is defined."
-
- # Function and Class Validation Errors
- FUNCTION_RETURN_ANNOTATION_MISMATCH = (
- "Return annotation mismatch for {0} '{1}': expected '{2}', found '{3}'."
- )
- VARIABLE_MISMATCH = "Variable mismatch'{1}': expected '{2}', found '{3}'."
- VARIABLE_MISSING = "Missing variable '{0}'"
- ARGUMENT_MISMATCH = "Argument mismatch for {0} '{1}': expected '{2}', found '{3}'."
- ARGUMENT_MISSING = "Missing argument '{0}' in {1}."
-
- CLASS_MISSING = "Missing class: '{0}'. Ensure it is defined."
- CLASS_ATTRIBUTE_MISSING = "Missing attribute '{0}' in class '{1}'."
- CLASS_ATTRIBUTE_MISMATCH = (
- "Attribute value mismatch for '{0}' in class '{1}': expected '{2}', found '{3}'."
- )
- CLASS_SUPERCLASS_MISSING = (
- "Missing superclass '{0}' in class '{1}'. Ensure that '{1}' extends '{0}'."
- )
- INVALID_ATTRIBUTE_TYPE = "Invalid attribute type: '{0}'. Supported types are: {1}."
-
- # Runtime Validation Errors
- INVALID_ARCHITECTURE = "Invalid architecture: expected '{0}', found '{1}'."
- INVALID_OS = "Invalid Operating System: expected one of {0}, but found '{1}'."
-
- # Python Valitation Errors
- INVALID_PYTHON_VERSION = "Invalid python version: expected one of '{0}', but found '{1}'."
- INVALID_PYTHON_INTERPRETER = "Invalid python interpreter: expected one of '{0}', but found '{1}'."
-
- # Annotation Validation
- INVALID_ANNOTATION = "Invalid annotation: expected one of {0}, but found '{1}'."
-
- # Generic Element Missing
- ELEMENT_MISSING = (
- "{0} is declared but missing in the system. "
- "Ensure it is properly defined and implemented."
- )
\ No newline at end of file
diff --git a/src/importspy/log_manager.py b/src/importspy/log_manager.py
index c0ca8fb..754592a 100644
--- a/src/importspy/log_manager.py
+++ b/src/importspy/log_manager.py
@@ -22,19 +22,15 @@ class CustomFormatter(logging.Formatter):
This formatter extends the default logging format by appending the exact
filename, line number, and function name where each log was triggered.
- This is especially useful in distributed architectures, plugin-based systems,
- or debugging deeply nested calls during module inspection.
-
Format:
-------
- ``[timestamp] [LEVEL] [logger name] [caller: file, line, function] message``
+ [timestamp] [LEVEL] [logger name]
+ [caller: file, line, function] message
Example:
--------
- .. code-block:: text
-
- 2024-02-24 14:30:12 [INFO] [my_logger]
- [caller: example.py, line: 42, function: my_function] This is a log message.
+ 2024-02-24 14:30:12 [INFO] [my_logger]
+ [caller: example.py, line: 42, function: my_function] This is a log message.
"""
LOG_FORMAT = (
@@ -51,22 +47,17 @@ def __init__(self):
def format(self, record):
"""
- Adds caller details to the log record.
-
- Enhances logs with:
- - Filename where the log was triggered
- - Line number
- - Function name
+ Enriches the log record with caller information.
Parameters:
-----------
record : logging.LogRecord
- The original log event.
+ The original log record to be formatted.
Returns:
--------
str
- The enriched, formatted log message.
+ A fully formatted log message including file, line, and function context.
"""
record.caller_file = record.pathname.split("/")[-1]
record.caller_line = record.lineno
@@ -78,37 +69,28 @@ class LogManager:
"""
Centralized manager for all logging within ImportSpy.
- This class ensures that:
- - All loggers use the same format (`CustomFormatter`)
- - Logging is only configured once to avoid duplication
- - Each component of the framework can retrieve its own scoped logger
-
- Whether ImportSpy runs embedded inside another module or as a CLI tool,
- the `LogManager` ensures that log output is clean, traceable, and standardized.
+ This class ensures:
+ - Uniform formatting across all loggers
+ - Avoidance of duplicate configuration
+ - Consistent output in both CLI and embedded contexts
Attributes:
-----------
default_level : int
- The system's current log level at the time of instantiation.
+ The current log level derived from the root logger.
default_handler : logging.StreamHandler
- Default output handler using `CustomFormatter`.
+ Default handler for logging output, using the `CustomFormatter`.
configured : bool
- Indicates whether global logging has already been configured.
-
- Methods:
- --------
- - `configure(level, handlers)`: Applies global settings to the root logger.
- - `get_logger(name)`: Retrieves a logger with consistent formatting and context.
+ Whether the logging system has already been configured.
"""
def __init__(self):
"""
- Sets up default logging options.
+ Sets up the default logging handler and format.
- The default handler uses ImportSpy’s `CustomFormatter` and logs to `stdout`.
- Logging is deferred until explicitly configured.
+ Uses `CustomFormatter` and logs to standard output by default.
"""
self.default_level = logging.getLogger().getEffectiveLevel()
self.default_handler = logging.StreamHandler()
@@ -117,30 +99,22 @@ def __init__(self):
def configure(self, level: int = None, handlers: list = None):
"""
- Configures the global logging system.
+ Applies logging configuration globally.
- This method attaches handlers to the root logger and sets the global level.
- It must be called only once to avoid duplicate logs or handler conflicts.
+ Prevents duplicate setup. This method should be called once per application.
Parameters:
-----------
level : int, optional
- Desired log level (e.g., `logging.DEBUG` or `logging.INFO`).
- Defaults to the system’s current level.
+ Log level (e.g., logging.DEBUG). Defaults to current system level.
handlers : list of logging.Handler, optional
- List of custom handlers to attach. If omitted, uses the default stream handler.
+ Custom handlers to use. Falls back to `default_handler` if none provided.
Raises:
-------
RuntimeError
- If logging has already been configured elsewhere in the application.
-
- Example:
- --------
- .. code-block:: python
-
- LogManager().configure(level=logging.DEBUG)
+ If logging is already configured.
"""
if self.configured:
raise RuntimeError("LogManager has already been configured.")
@@ -158,27 +132,19 @@ def configure(self, level: int = None, handlers: list = None):
def get_logger(self, name: str) -> logging.Logger:
"""
- Retrieves a scoped logger configured with ImportSpy’s formatting.
+ Returns a named logger with ImportSpy's formatting applied.
- This logger is safe to use across modules and plugins.
- It ensures no duplicate handlers and maintains the current log level.
+ Ensures the logger is properly configured and ready for use.
Parameters:
-----------
name : str
- Name of the logger (typically `__name__` or class name).
+ The name of the logger (e.g., a module name).
Returns:
--------
logging.Logger
- A configured logger ready for use.
-
- Example:
- --------
- .. code-block:: python
-
- logger = LogManager().get_logger("my_module")
- logger.info("Validation complete.")
+ The initialized logger instance.
"""
logger = logging.getLogger(name)
if not logger.handlers:
diff --git a/src/importspy/models.py b/src/importspy/models.py
index e6c3651..15b0b73 100644
--- a/src/importspy/models.py
+++ b/src/importspy/models.py
@@ -1,15 +1,18 @@
"""
-importspy.models
-----------------
+models.py
+==========
-This module defines the core data models used by ImportSpy for contract-based
-runtime validation of Python modules. It includes structural representations
-of variables, functions, classes, and full modules, as well as runtime and
-system-level metadata required to enforce import contracts across execution contexts.
+Defines the structural and contextual data models used across ImportSpy.
+These models represent modules, variables, functions, classes, runtimes,
+systems, and environments involved in contract-based validation.
+
+This module powers both embedded validation and CLI checks, enabling ImportSpy
+to introspect, serialize, and enforce compatibility rules at multiple levels:
+from source code structure to runtime platform details.
"""
-from pydantic import BaseModel, field_validator, Field
-from typing import Optional, List, Union
+from pydantic import BaseModel
+from typing import Optional, Union, List
from types import ModuleType
from .utilities.module_util import (
@@ -19,8 +22,8 @@
from .utilities.runtime_util import RuntimeUtil
from .utilities.system_util import SystemUtil
from .utilities.python_util import PythonUtil
-from .constants import Constants
-from .errors import Errors
+from .constants import Constants, Contexts, Errors
+from .config import Config
import logging
logger = logging.getLogger("/".join(__file__.split('/')[-2:]))
@@ -29,128 +32,118 @@
class Python(BaseModel):
"""
- Represents a specific Python runtime configuration.
+ Represents a Python runtime environment.
- Includes the Python version, interpreter type, and the list of loaded modules.
- Used to validate compatibility between caller and callee environments.
+ Includes:
+ - Python version
+ - Interpreter type (e.g., CPython, PyPy)
+ - List of loaded modules
+ Used in validating runtime compatibility.
"""
version: Optional[str] = None
- interpreter: Optional[str] = None
- modules: List['Module']
+ interpreter: Optional[Constants.SupportedPythonImplementations] = None
+ modules: list['Module']
- @field_validator('version')
- def validate_version(cls, value: str):
- """
- Validate that the Python version is within supported versions.
- """
- if ".".join(value.split(".")[:2]) not in Constants.SUPPORTED_PYTHON_VERSION:
- raise ValueError(Errors.INVALID_PYTHON_VERSION.format(Constants.SUPPORTED_PYTHON_VERSION, value))
- return value
+ def __str__(self):
+ return f"{self.interpreter} v{self.version}"
+
+ def __repr__(self):
+ return str(self)
- @field_validator('interpreter')
- def validate_interpreter(cls, value: str):
- """
- Validate that the interpreter is among the supported Python implementations.
- """
- if value not in Constants.SUPPORTED_PYTHON_IMPLEMENTATION:
- raise ValueError(Errors.INVALID_PYTHON_INTERPRETER.format(Constants.SUPPORTED_PYTHON_IMPLEMENTATION, value))
- return value
+
+class Environment(BaseModel):
+ """
+ Represents runtime environment variables and secrets.
+ Used for validating runtime configuration.
+ """
+ variables: Optional[list['Variable']] = None
+ secrets: Optional[list[str]] = None
+
+ def __str__(self):
+ return f"variables: {self.variables} | secrets: {self.secrets}"
+
+ def __repr__(self):
+ return str(self)
class System(BaseModel):
"""
- Represents the system environment, including OS, environment variables,
- and Python runtimes configured within the system.
+ Represents a full OS environment within a deployment system.
+
+ Includes:
+ - OS type
+ - Environment variables
+ - Python runtimes
+ Used to validate cross-platform compatibility.
"""
- os: str
- envs: Optional[dict] = Field(default=None, repr=False)
- pythons: List[Python]
+ os: Constants.SupportedOS
+ environment: Optional[Environment] = None
+ pythons: list[Python]
- @field_validator('os')
- def validate_os(cls, value: str):
- """
- Validate that the provided OS is among the supported platforms.
- """
- if value not in Constants.SUPPORTED_OS:
- raise ValueError(Errors.INVALID_OS.format(Constants.SUPPORTED_OS, value))
- return value
+ def __str__(self):
+ return f"{self.os.value}"
+
+ def __repr__(self):
+ return str(self)
class Runtime(BaseModel):
"""
- Represents the deployment runtime, identified by CPU architecture and
- the list of supported systems associated with that architecture.
+ Represents a runtime deployment context.
+
+ Defined by CPU architecture and associated systems.
"""
- arch: str
- systems: List[System]
+ arch: Constants.SupportedArchitectures
+ systems: list[System]
- @field_validator('arch')
- def validate_arch(cls, value: str):
- """
- Validate that the CPU architecture is known and supported.
- """
- if value not in Constants.KNOWN_ARCHITECTURES:
- raise ValueError(Errors.INVALID_ARCHITECTURE.format(value, Constants.KNOWN_ARCHITECTURES))
- return value
+ def __str__(self):
+ return f"{self.arch}"
+
+ def __repr__(self):
+ return str(self)
class Variable(BaseModel):
"""
- Represents a declared variable within a Python module, including optional type
- annotation and value. Used for structural validation of the importing module.
+ Represents a top-level variable in a Python module.
+
+ Includes:
+ - Name
+ - Optional annotation
+ - Optional static value
+ Used to enforce structural consistency.
"""
name: str
- annotation: Optional[str] = None
+ annotation: Optional[Constants.SupportedAnnotations] = None
value: Optional[Union[int, str, float, bool, None]] = None
- @field_validator("annotation")
- def validate_annotation(cls, value):
- """
- Validate that the annotation is supported by the current contract.
- """
- if not value:
- return None
- base = value.split("[")[0]
- if base not in Constants.SUPPORTED_ANNOTATIONS:
- raise ValueError(
- Errors.INVALID_ANNOTATION.format(value, Constants.SUPPORTED_ANNOTATIONS)
- )
- return value
-
@classmethod
- def from_variable_info(cls, variables_info: List[VariableInfo]):
- """
- Convert a list of extracted VariableInfo into Variable instances.
- """
- return [Variable(
+ def from_variable_info(cls, variables_info: list[VariableInfo]):
+ return [cls(
name=var_info.name,
value=var_info.value,
annotation=var_info.annotation
) for var_info in variables_info]
+ def __str__(self):
+ type_part = f": {self.annotation}" if self.annotation else ""
+ return f"{self.name}{type_part} = {self.value}"
+
+ def __repr__(self):
+ return str(self)
+
class Attribute(Variable):
"""
- Represents a class attribute, extending Variable with a 'type' indicator
- (e.g., 'class', 'instance'). Used in class-level contract validation.
- """
- type: str
+ Represents a class-level attribute.
- @field_validator('type')
- def validate_type(cls, value: str):
- """
- Validate that the attribute type is among supported class attribute types.
- """
- if value not in Constants.SUPPORTED_CLASS_ATTRIBUTE_TYPES:
- raise ValueError(Errors.INVALID_ATTRIBUTE_TYPE.format(value, Constants.SUPPORTED_CLASS_ATTRIBUTE_TYPES))
- return value
+ Extends Variable with attribute type (e.g., class or instance).
+ """
+ type: Constants.SupportedClassAttributeTypes
@classmethod
- def from_attributes_info(cls, attributes_info: List[AttributeInfo]):
- """
- Convert a list of AttributeInfo objects into Attribute instances.
- """
- return [Attribute(
+ def from_attributes_info(cls, attributes_info: list[AttributeInfo]):
+ return [cls(
type=attr_info.type,
name=attr_info.name,
value=attr_info.value,
@@ -160,15 +153,17 @@ def from_attributes_info(cls, attributes_info: List[AttributeInfo]):
class Argument(Variable, BaseModel):
"""
- Represents a function argument, including its name, type annotation, and default value.
- Used to validate callable structures and type consistency.
+ Represents a function/method argument.
+
+ Includes:
+ - Name
+ - Optional type annotation
+ - Optional default value
+ Used to check call signatures.
"""
@classmethod
- def from_arguments_info(cls, arguments_info: List[ArgumentInfo]):
- """
- Convert a list of ArgumentInfo into Argument instances.
- """
- return [Argument(
+ def from_arguments_info(cls, arguments_info: list[ArgumentInfo]):
+ return [cls(
name=arg_info.name,
annotation=arg_info.annotation,
value=arg_info.value
@@ -177,81 +172,102 @@ def from_arguments_info(cls, arguments_info: List[ArgumentInfo]):
class Function(BaseModel):
"""
- Represents a callable function, including its name, argument signature,
- and return type annotation.
+ Represents a callable entity.
+
+ Includes:
+ - Name
+ - List of arguments
+ - Optional return annotation
"""
name: str
- arguments: Optional[List[Argument]] = None
- return_annotation: Optional[str] = None
-
- @field_validator("return_annotation")
- def validate_annotation(cls, value):
- """
- Validate that the return annotation is supported.
- """
- return CommonValidator.validate_annotation(value)
+ arguments: Optional[list[Argument]] = None
+ return_annotation: Optional[Constants.SupportedAnnotations] = None
@classmethod
- def from_functions_info(cls, functions_info: List[FunctionInfo]):
- """
- Convert a list of FunctionInfo into Function instances.
- """
- return [Function(
+ def from_functions_info(cls, functions_info: list[FunctionInfo]):
+ return [cls(
name=func_info.name,
arguments=Argument.from_arguments_info(func_info.arguments),
return_annotation=func_info.return_annotation
) for func_info in functions_info]
+ def __str__(self):
+ args = ", ".join(str(arg) for arg in self.arguments) if self.arguments else ""
+ return f"{self.name}({args}) -> {self.return_annotation}"
+
+ def __repr__(self):
+ return str(self)
+
class Class(BaseModel):
"""
- Represents a Python class, including its attributes, methods, and declared superclasses.
- Used to enforce object-level validation rules in contracts.
+ Represents a Python class declaration.
+
+ Includes:
+ - Name
+ - Attributes (class/instance)
+ - Methods
+ - Superclasses (recursive)
"""
name: str
- attributes: Optional[List[Attribute]] = None
- methods: Optional[List[Function]] = None
- superclasses: Optional[List[str]] = None
+ attributes: Optional[list[Attribute]] = None
+ methods: Optional[list[Function]] = None
+ superclasses: Optional[list['Class']] = None
@classmethod
- def from_class_info(cls, extracted_classes: List[ClassInfo]):
- """
- Convert a list of extracted class definitions into Class instances.
- """
- return [Class(
+ def from_class_info(cls, extracted_classes: list[ClassInfo]):
+ return [cls(
name=name,
attributes=Attribute.from_attributes_info(attributes),
methods=Function.from_functions_info(methods),
- superclasses=superclasses
+ superclasses=cls.from_class_info(superclasses)
) for name, attributes, methods, superclasses in extracted_classes]
+ def get_class_attributes(self) -> List[Attribute]:
+ if self.attributes:
+ return [attr for attr in self.attributes if attr.type == Config.CLASS_TYPE]
+
+ def get_instance_attributes(self) -> List[Attribute]:
+ if self.attributes:
+ return [attr for attr in self.attributes if attr.type == Config.INSTANCE_TYPE]
+
class Module(BaseModel):
"""
- Represents a full Python module, including its filename, version,
- and all its internal components (variables, functions, classes).
+ Represents a Python module.
+
+ Includes:
+ - Filename
+ - Version (if extractable)
+ - Top-level variables, functions, and classes
"""
filename: Optional[str] = None
version: Optional[str] = None
- variables: Optional[List[Variable]] = None
- functions: Optional[List[Function]] = None
- classes: Optional[List[Class]] = None
+ variables: Optional[list[Variable]] = None
+ functions: Optional[list[Function]] = None
+ classes: Optional[list[Class]] = None
+
+ def __str__(self):
+ return f"Module: {self.filename or 'unknown'} (v{self.version or '-'})"
+
+ def __repr__(self):
+ return str(self)
class SpyModel(Module):
"""
- Extends the base Module structure with additional deployment metadata.
+ High-level model used by ImportSpy for validation.
- SpyModel is the top-level object representing a module's structure and its
- runtime/environment constraints. This is the core of ImportSpy's contract model.
+ Extends the module representation with runtime metadata and
+ platform-specific deployment constraints (architecture, OS, interpreter, etc).
"""
- deployments: Optional[List[Runtime]] = None
+ deployments: Optional[list[Runtime]] = None
@classmethod
def from_module(cls, info_module: ModuleType):
"""
- Create a SpyModel from a loaded Python module, extracting its metadata
- and attaching runtime/system context.
+ Build a SpyModel instance by extracting structure and metadata
+ from an actual Python module object.
"""
module_utils = ModuleUtil()
runtime_utils = RuntimeUtil()
@@ -271,7 +287,7 @@ def from_module(cls, info_module: ModuleType):
os = system_utils.extract_os()
python_version = python_utils.extract_python_version()
interpreter = python_utils.extract_python_implementation()
- envs = system_utils.extract_envs()
+ envs = Variable.from_variable_info(system_utils.extract_envs())
module_utils.unload_module(info_module)
logger.debug("Unload module")
@@ -284,7 +300,9 @@ def from_module(cls, info_module: ModuleType):
systems=[
System(
os=os,
- envs=envs,
+ environment=Environment(
+ variables=envs
+ ),
pythons=[
Python(
version=python_version,
@@ -307,21 +325,15 @@ def from_module(cls, info_module: ModuleType):
)
-class CommonValidator:
- """
- Provides shared validation utilities for type annotations and other structural elements.
+class Error(BaseModel):
"""
+ Describes a structured validation error.
- @classmethod
- def validate_annotation(cls, value):
- """
- Validate a type annotation against the supported base annotations.
- """
- if not value:
- return None
- base = value.split("[")[0]
- if base not in Constants.SUPPORTED_ANNOTATIONS:
- raise ValueError(
- Errors.INVALID_ANNOTATION.format(value, Constants.SUPPORTED_ANNOTATIONS)
- )
- return value
+ Includes the context, error type, message, and resolution steps.
+ Used to serialize feedback during contract enforcement.
+ """
+ context: Contexts
+ title: str
+ category: Errors.Category
+ description: str
+ solution: str
diff --git a/src/importspy/persistences.py b/src/importspy/persistences.py
index ebd9a43..099f85d 100644
--- a/src/importspy/persistences.py
+++ b/src/importspy/persistences.py
@@ -1,16 +1,12 @@
"""
-importspy.persistences
-=======================
-
-This module defines the interfaces and implementations for handling **import contracts** —
+Defines interfaces and implementations for handling **import contracts** —
external YAML files used by ImportSpy to validate the structure and runtime expectations
of dynamically loaded Python modules.
-Currently, YAML is the only supported contract format, but the architecture is fully
-extensible via the `Parser` interface.
+Currently, only YAML is supported, but the architecture is extensible via the `Parser` interface.
-All file access operations are wrapped in safe error handling using `handle_persistence_error`,
-which raises human-readable exceptions when contract files are missing, corrupted, or unreadable.
+All file I/O operations are wrapped in `handle_persistence_error`, ensuring clear error
+messages in case of missing, malformed, or inaccessible contract files.
"""
from abc import ABC, abstractmethod
@@ -20,86 +16,84 @@
class Parser(ABC):
"""
- Abstract interface for import contract parsers.
+ Abstract base class for import contract parsers.
- A contract parser is responsible for loading and saving `.yml` files
- that describe the expected structure of a Python module. This abstraction
- allows ImportSpy to support multiple formats (e.g., YAML, JSON, TOML) in the future.
+ Parsers are responsible for loading and saving `.yml` contract files that define
+ a module’s structural and runtime expectations. This abstraction enables future
+ support for additional formats (e.g., JSON, TOML).
- Subclasses must implement both `save()` and `load()` methods.
+ Subclasses must implement `save()` and `load()`.
"""
@abstractmethod
def save(self, data: dict, filepath: str):
"""
- Serializes the given import contract (as a Python dictionary) and writes it to a file.
+ Serializes the contract (as a dictionary) and writes it to disk.
Parameters:
-----------
data : dict
- A dictionary representation of the import contract.
+ Dictionary containing the contract structure.
filepath : str
- The path where the contract should be saved (usually with `.yml` extension).
+ Target path for saving the contract (typically `.yml`).
"""
pass
@abstractmethod
def load(self, filepath: str) -> dict:
"""
- Loads and parses an import contract from a file into a Python dictionary.
+ Parses a contract file and returns it as a dictionary.
Parameters:
-----------
filepath : str
- Path to the `.yml` contract file.
+ Path to the contract file on disk.
Returns:
--------
dict
- The parsed contract as a dictionary.
+ Parsed contract data.
"""
pass
class PersistenceError(Exception):
"""
- Custom exception raised when there is a problem reading or writing import contracts.
+ Raised when contract loading or saving fails due to I/O or syntax issues.
- This error wraps low-level I/O or parsing issues and presents them in a way
- that is meaningful to end users.
+ This exception wraps low-level errors and provides human-readable feedback.
"""
def __init__(self, msg: str):
"""
- Initializes the `PersistenceError`.
+ Initialize the error with a descriptive message.
Parameters:
-----------
msg : str
- A human-readable error message describing the failure.
+ Explanation of the failure.
"""
super().__init__(msg)
def handle_persistence_error(func):
"""
- Decorator that wraps file I/O operations in safe error handling.
-
- If the decorated function raises any exception (e.g., file not found, malformed YAML),
- a `PersistenceError` is raised with a descriptive message instead.
+ Decorator for wrapping parser I/O methods with user-friendly error handling.
- This helps ensure that ImportSpy fails gracefully during contract handling.
+ Catches all exceptions and raises a `PersistenceError` with a generic message.
+ This ensures ImportSpy fails gracefully if a contract file is missing,
+ malformed, or inaccessible.
Parameters:
-----------
func : Callable
- The function to decorate.
+ The I/O method to wrap.
Returns:
--------
Callable
- The wrapped function.
+ A wrapped version that raises `PersistenceError` on failure.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
@@ -115,26 +109,26 @@ def wrapper(*args, **kwargs):
class YamlParser(Parser):
"""
- YAML-based implementation of the `Parser` interface.
+ YAML-based contract parser implementation.
- This parser reads and writes import contracts from `.yml` files using the `ruamel.yaml` library.
- It preserves indentation, flow style, and quotes to ensure consistent structure across validations.
+ Uses `ruamel.yaml` to read and write `.yml` files that define import contracts.
+ Preserves formatting, indentation, and quotes for consistent serialization.
"""
def __init__(self):
"""
- Initializes the YAML parser and applies default formatting rules for readability.
+ Initializes the YAML parser and configures output formatting.
"""
self.yaml = YAML()
self._yml_configuration()
def _yml_configuration(self):
"""
- Applies consistent formatting to YAML output:
+ Applies formatting rules to YAML output:
- - Disables flow style for better readability
- - Sets indentation rules for mappings and sequences
- - Preserves quotes for exact string representation
+ - Disables flow style
+ - Sets consistent indentation
+ - Preserves quotes in strings
"""
self.yaml.default_flow_style = False
self.yaml.indent(mapping=2, sequence=4, offset=2)
@@ -143,15 +137,15 @@ def _yml_configuration(self):
@handle_persistence_error
def save(self, data: dict, filepath: str):
"""
- Saves an import contract to a `.yml` file.
+ Saves a contract dictionary to a `.yml` file.
Parameters:
-----------
data : dict
- The contract content as a dictionary.
+ Contract structure.
filepath : str
- The output path where the YAML file will be saved.
+ Destination file path.
"""
with open(filepath, "w") as file:
self.yaml.dump(data, file)
@@ -159,17 +153,17 @@ def save(self, data: dict, filepath: str):
@handle_persistence_error
def load(self, filepath: str) -> dict:
"""
- Loads an import contract from a `.yml` file and parses it into a dictionary.
+ Loads and parses a `.yml` contract into a Python dictionary.
Parameters:
-----------
filepath : str
- Path to the YAML file.
+ Path to the contract file.
Returns:
--------
dict
- A Python dictionary containing the contract structure.
+ Parsed contract structure.
"""
with open(filepath) as file:
data = self.yaml.load(file)
diff --git a/src/importspy/s.py b/src/importspy/s.py
index b421685..8d38433 100644
--- a/src/importspy/s.py
+++ b/src/importspy/s.py
@@ -1,7 +1,4 @@
"""
-importspy.s
-===========
-
Core validation logic for ImportSpy.
This module defines the `Spy` class, the central component responsible for dynamically
@@ -20,57 +17,64 @@
"""
from types import ModuleType
-from .models import SpyModel
+from .models import (
+ SpyModel,
+ Runtime,
+ Python,
+ Module
+)
from .utilities.module_util import ModuleUtil
-from .validators.spymodel_validator import SpyModelValidator
+from .validators import (
+ RuntimeValidator,
+ SystemValidator,
+ PythonValidator,
+ ModuleValidator
+)
from .log_manager import LogManager
from .persistences import Parser, YamlParser
-from typing import Optional
+from typing import (
+ Optional,
+ List
+)
import logging
+from .violation_systems import (
+ Bundle,
+ ModuleContractViolation,
+ RuntimeContractViolation,
+ SystemContractViolation,
+ PythonContractViolation
+)
+from .constants import Contexts
class Spy:
"""
- The `Spy` class is the core engine of ImportSpy — it handles validation, introspection,
- and enforcement of structural contracts for Python modules.
-
- This class is designed to support both:
+ Core validation engine for ImportSpy.
- - **Embedded validation**, where it is imported and executed inside the module under control.
- - **CLI-based or pipeline validation**, where an external tool invokes Spy programmatically.
+ The `Spy` class is responsible for loading a target module, extracting its structure,
+ and validating it against a YAML-based import contract. This ensures that the importing
+ module satisfies all declared structural and runtime constraints.
- ImportSpy uses declarative **import contracts**, written as human-readable YAML files,
- to describe what a valid module should contain. These contracts define expected classes,
- attributes, methods, and even environmental constraints (like Python version or OS).
-
- The `Spy` class dynamically loads the target module, extracts its metadata, and checks
- for compliance against the contract. If validation fails, descriptive errors are raised
- before the module can be used improperly.
+ It supports two modes:
+ - **Embedded mode**: validates the caller of the current module
+ - **External/CLI mode**: validates an explicitly provided module
Attributes:
-----------
logger : logging.Logger
- Logger instance used to track validation steps and internal processing.
+ Structured logger for validation diagnostics.
parser : Parser
- Parser responsible for loading the import contract from disk (currently supports YAML).
-
- Methods:
- --------
- - `__init__()` → Initializes logger and default parser.
- - `importspy(filepath, log_level, info_module)` → Validates a specified or inferred module.
- - `_configure_logging(log_level)` → Sets logging level based on user/system config.
- - `_validate_module(contract, info_module)` → Compares a module to the contract definition.
- - `_inspect_module()` → Introspects the call stack to locate the calling module.
+ Parser used to load import contracts (defaults to YAML).
"""
def __init__(self):
"""
- Initializes the `Spy` instance.
+ Initialize the Spy instance.
- This method sets up:
- - the logging system for capturing all validation and introspection steps
- - the default parser (`YamlParser`) for loading `.yml` import contracts
+ Sets up:
+ - a dedicated logger
+ - the default YAML parser
"""
self.logger = LogManager().get_logger(self.__class__.__name__)
self.parser: Parser = YamlParser()
@@ -80,35 +84,34 @@ def importspy(self,
log_level: Optional[int] = None,
info_module: Optional[ModuleType] = None) -> ModuleType:
"""
- Loads and validates a Python module based on an import contract.
+ Main entry point for validation.
- This is the primary method used to validate a module, whether in embedded mode
- (by inspecting the importer), or in external mode (via CLI or script).
+ Loads and validates a Python module against the contract defined in the given YAML file.
+ If no module is explicitly provided, introspects the call stack to infer the caller.
Parameters:
-----------
filepath : Optional[str]
- Path to the `.yml` contract file defining the expected structure.
+ Path to the `.yml` import contract.
log_level : Optional[int]
- Logging level for output verbosity. Uses system default if not provided.
+ Log verbosity level (e.g., `logging.DEBUG`).
info_module : Optional[ModuleType]
- Optional reference to the module to validate. If not provided,
- the calling module is inferred via stack inspection.
+ The module to validate. If `None`, uses the importer via stack inspection.
Returns:
--------
ModuleType
- The module that was validated.
+ The validated module.
Raises:
-------
RuntimeError
- If logging is misconfigured or reconfigured unexpectedly.
+ If logging setup fails.
ValueError
- If a recursion pattern is detected (e.g., a module validating itself).
+ If recursion is detected (e.g., a module is validating itself).
"""
self._configure_logging(log_level)
spymodel: SpyModel = SpyModel(**self.parser.load(filepath=filepath))
@@ -118,15 +121,15 @@ def importspy(self,
def _configure_logging(self, log_level: Optional[int] = None):
"""
- Configures ImportSpy's logging system for runtime use.
+ Set up logging for validation.
- If a log level is provided, it overrides the system's default. This method ensures
- the logger is only configured once, preventing duplicate log handlers.
+ If not already configured, applies the provided or default log level
+ using ImportSpy’s centralized logging system.
Parameters:
-----------
log_level : Optional[int]
- The desired log level (e.g., logging.INFO, logging.DEBUG).
+ Logging level to use (e.g., `logging.INFO`, `logging.DEBUG`).
"""
log_manager = LogManager()
if not log_manager.configured:
@@ -135,62 +138,69 @@ def _configure_logging(self, log_level: Optional[int] = None):
def _validate_module(self, spymodel: SpyModel, info_module: ModuleType) -> ModuleType:
"""
- Compares a module's structure against the loaded import contract.
+ Perform all validation steps against the loaded module.
- This includes checking for:
- - required classes and methods
- - expected variable names and values
- - inheritance and method signatures
+ This includes contract-level, runtime, system, and Python environment checks.
+ All contract violations are collected in a `Bundle`.
Parameters:
-----------
spymodel : SpyModel
- Parsed import contract used as the validation baseline.
+ The expected contract loaded from file.
info_module : ModuleType
- The actual module being validated.
+ The actual module to inspect and validate.
Returns:
--------
ModuleType
- The validated module.
-
- Raises:
- -------
- ValueError
- If the module does not conform to the expected structure.
+ The validated module, reloaded after introspection.
"""
self.logger.debug(f"info_module: {info_module}")
if spymodel:
+ bundle = Bundle()
+ module_validator = ModuleValidator()
self.logger.debug(f"Import contract detected: {spymodel}")
spy_module = SpyModel.from_module(info_module)
self.logger.debug(f"Extracted module structure: {spy_module}")
- SpyModelValidator().validate(spymodel, spy_module)
+
+ module_contract = ModuleContractViolation(Contexts.MODULE_CONTEXT, bundle)
+ module_validator.validate([spymodel], spy_module.deployments[0].systems[0].pythons[0].modules[0], module_contract)
+
+ runtime_contract = RuntimeContractViolation(Contexts.RUNTIME_CONTEXT, bundle)
+ runtime = RuntimeValidator().validate(spymodel.deployments, spy_module.deployments, runtime_contract)
+
+ system_contract = SystemContractViolation(Contexts.RUNTIME_CONTEXT, bundle)
+ pythons = SystemValidator().validate(runtime.systems, spy_module.deployments[0].systems, system_contract)
+
+ python_contract = PythonContractViolation(Contexts.RUNTIME_CONTEXT, bundle)
+ modules = PythonValidator().validate(pythons, spy_module.deployments[0].systems[0].pythons, python_contract)
+
+ module_validator.validate(modules, spy_module.deployments[0].systems[0].pythons[0].modules[0], module_contract)
+
return ModuleUtil().load_module(info_module)
def _inspect_module(self) -> ModuleType:
"""
- Introspects the call stack to determine which module called `importspy()`.
+ Infer the module that invoked validation (embedded mode).
- This is used primarily in embedded mode to locate the external plugin
- or module that triggered the validation. It prevents the system from
- analyzing itself (recursive inspection).
+ This prevents a module from validating itself and ensures that
+ ImportSpy targets the correct caller in the stack.
Returns:
--------
ModuleType
- The module that imported or triggered validation.
+ The inferred external module.
Raises:
-------
ValueError
- If recursion is detected (i.e., the same module is inspecting itself).
+ If a module attempts to validate itself.
"""
module_util = ModuleUtil()
current_frame, caller_frame = module_util.inspect_module()
if current_frame.filename == caller_frame.filename:
raise ValueError("Recursion detected during module analysis.")
-
info_module = module_util.get_info_module(caller_frame)
self.logger.debug(f"Inferred caller module: {info_module}")
return info_module
diff --git a/src/importspy/utilities/__init__.py b/src/importspy/utilities/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/importspy/utilities/module_util.py b/src/importspy/utilities/module_util.py
index c81b419..ea52a7c 100644
--- a/src/importspy/utilities/module_util.py
+++ b/src/importspy/utilities/module_util.py
@@ -1,25 +1,26 @@
"""
-Module: Module Utilities
+Module utilities for runtime introspection and structure extraction.
-This module provides a comprehensive set of utility functions for dynamic module inspection,
-loading, unloading, and metadata extraction. It is designed to support ImportSpy's runtime
-validation processes by enabling detailed analysis of Python modules.
+This module provides utility functions for analyzing Python modules dynamically,
+primarily to support ImportSpy's runtime validation mechanisms. It enables inspection
+of modules, their metadata, and internal structure at runtime.
-Key Features:
--------------
-- Inspect the calling stack and retrieve information about modules.
-- Dynamically load and unload modules for runtime modifications.
-- Extract metadata such as classes, functions, variables, and inheritance hierarchies from modules.
-
-Example Usage:
---------------
-.. code-block:: python
+Features:
+- Inspect the call stack and determine caller modules.
+- Dynamically load and unload Python modules.
+- Extract version information via metadata or attributes.
+- Retrieve global variables, top-level functions, and class definitions.
+- Analyze methods, attributes (class-level and instance-level), and superclasses.
+Example:
+ ```python
from importspy.utilities.module_util import ModuleUtil
+ import inspect
module_util = ModuleUtil()
- module_info = module_util.get_info_module(inspect.stack()[0])
- print(f"Module Name: {module_info.__name__}")
+ info = module_util.get_info_module(inspect.stack()[0])
+ print(info.__name__)
+ ```
"""
import inspect
@@ -43,23 +44,11 @@
class ModuleUtil:
"""
- Utility class for dynamic module inspection and metadata extraction.
-
- The `ModuleUtil` class provides methods to inspect, load, unload, and analyze Python
- modules at runtime. These utilities are essential for enabling ImportSpy's dynamic
- validation of runtime conditions.
-
- Methods:
- --------
- - `inspect_module()`: Retrieve current and caller frames.
- - `get_info_module()`: Extract module from a caller frame.
- - `load_module()`: Dynamically reload a module.
- - `unload_module()`: Remove module from memory.
- - `extract_version()`: Retrieve version of a module.
- - `extract_variables()`: Extract global variables.
- - `extract_functions()`: Extract top-level functions.
- - `extract_classes()`: Extract class definitions.
- - `extract_superclasses()`: Collect all used base classes.
+ Provides methods to inspect and extract structural metadata from Python modules.
+
+ This class enables runtime inspection of loaded modules for metadata such as
+ functions, classes, variables, inheritance hierarchies, and version information.
+ It is a core component used by ImportSpy to validate structural contracts.
"""
def inspect_module(self) -> tuple:
@@ -67,9 +56,7 @@ def inspect_module(self) -> tuple:
Retrieve the current and caller frames from the stack.
Returns:
- --------
- tuple
- A tuple containing the current and caller frame.
+ tuple: A tuple with the current and the outermost caller frame.
"""
stack = inspect.stack()
current_frame = stack[1]
@@ -78,33 +65,25 @@ def inspect_module(self) -> tuple:
def get_info_module(self, caller_frame: inspect.FrameInfo) -> ModuleType | None:
"""
- Retrieves the module object from a caller frame.
+ Resolve a module object from a given caller frame.
- Parameters:
- -----------
- caller_frame : inspect.FrameInfo
- The frame to analyze.
+ Args:
+ caller_frame (inspect.FrameInfo): The caller frame to analyze.
Returns:
- --------
- ModuleType | None
- The resolved module or None if not found.
+ ModuleType | None: The resolved module or None if not found.
"""
return inspect.getmodule(caller_frame.frame)
def load_module(self, info_module: ModuleType) -> ModuleType | None:
"""
- Dynamically reload a module.
+ Reload a module dynamically from its file location.
- Parameters:
- -----------
- info_module : ModuleType
- The module reference.
+ Args:
+ info_module (ModuleType): The module to reload.
Returns:
- --------
- ModuleType | None
- The reloaded module.
+ ModuleType | None: The reloaded module or None if loading fails.
"""
spec = importlib.util.spec_from_file_location(info_module.__name__, info_module.__file__)
if spec and spec.loader:
@@ -116,12 +95,10 @@ def load_module(self, info_module: ModuleType) -> ModuleType | None:
def unload_module(self, module: ModuleType):
"""
- Removes a module from memory.
+ Unload a module from sys.modules and globals.
- Parameters:
- -----------
- module : ModuleType
- The module to unload.
+ Args:
+ module (ModuleType): The module to unload.
"""
module_name = module.__name__
if module_name in sys.modules:
@@ -130,16 +107,13 @@ def unload_module(self, module: ModuleType):
def extract_version(self, info_module: ModuleType) -> str | None:
"""
- Retrieves version metadata for the module.
+ Attempt to retrieve the version string from a module.
- Parameters:
- -----------
- info_module : ModuleType
+ Args:
+ info_module (ModuleType): The target module.
Returns:
- --------
- str | None
- The version string if found.
+ str | None: Version string if found, otherwise None.
"""
if hasattr(info_module, '__version__'):
return info_module.__version__
@@ -150,7 +124,13 @@ def extract_version(self, info_module: ModuleType) -> str | None:
def extract_annotation(self, annotation) -> Optional[str]:
"""
- Converts annotations to string format for validation.
+ Convert a type annotation object into a string representation.
+
+ Args:
+ annotation: The annotation object to convert.
+
+ Returns:
+ Optional[str]: The extracted annotation string or None.
"""
if annotation == inspect._empty or not annotation:
return None
@@ -158,8 +138,17 @@ def extract_annotation(self, annotation) -> Optional[str]:
return annotation.__name__
return str(annotation)
- def extract_variables(self, info_module: ModuleType) -> dict:
- variables_info:List[VariableInfo] = []
+ def extract_variables(self, info_module: ModuleType) -> List[VariableInfo]:
+ """
+ Extract top-level variable definitions from a module.
+
+ Args:
+ info_module (ModuleType): The module to analyze.
+
+ Returns:
+ List[VariableInfo]: List of variable metadata.
+ """
+ variables_info: List[VariableInfo] = []
for name, value in inspect.getmembers(info_module):
if not name.startswith('__') and not inspect.ismodule(value) and not inspect.isfunction(value) and not inspect.isclass(value):
annotation = self.extract_annotation(type(value))
@@ -168,11 +157,13 @@ def extract_variables(self, info_module: ModuleType) -> dict:
def extract_functions(self, info_module: ModuleType) -> List[FunctionInfo]:
"""
- Extracts function definitions from the module.
+ Extract all functions defined at the top level of the module.
+
+ Args:
+ info_module (ModuleType): The target module.
Returns:
- --------
- List[FunctionInfo]
+ List[FunctionInfo]: Function metadata extracted from the module.
"""
functions_info: List[FunctionInfo] = []
for name, obj in inspect.getmembers(info_module, inspect.isfunction):
@@ -182,7 +173,14 @@ def extract_functions(self, info_module: ModuleType) -> List[FunctionInfo]:
def _extract_function(self, name: str, obj: FunctionType) -> FunctionInfo:
"""
- Builds metadata for a function.
+ Build structured metadata for a function.
+
+ Args:
+ name (str): Function name.
+ obj (FunctionType): Function object.
+
+ Returns:
+ FunctionInfo: Extracted function metadata.
"""
return FunctionInfo(
name,
@@ -192,7 +190,13 @@ def _extract_function(self, name: str, obj: FunctionType) -> FunctionInfo:
def _extract_arguments(self, obj: FunctionType) -> List[ArgumentInfo]:
"""
- Retrieves argument names and annotations.
+ Extract arguments from a function's signature.
+
+ Args:
+ obj (FunctionType): Function object.
+
+ Returns:
+ List[ArgumentInfo]: List of function argument metadata.
"""
args = []
for name, param in inspect.signature(obj).parameters.items():
@@ -202,7 +206,13 @@ def _extract_arguments(self, obj: FunctionType) -> List[ArgumentInfo]:
def extract_methods(self, cls_obj) -> List[FunctionInfo]:
"""
- Extracts all method definitions from a class.
+ Extract method definitions from a class object.
+
+ Args:
+ cls_obj: The class to inspect.
+
+ Returns:
+ List[FunctionInfo]: Extracted method metadata.
"""
methods: List[FunctionInfo] = []
for name, obj in inspect.getmembers(cls_obj, inspect.isfunction):
@@ -212,7 +222,14 @@ def extract_methods(self, cls_obj) -> List[FunctionInfo]:
def extract_attributes(self, cls_obj, info_module: ModuleType) -> List[AttributeInfo]:
"""
- Extracts class-level and instance-level attributes.
+ Extract both class-level and instance-level attributes.
+
+ Args:
+ cls_obj: The class to analyze.
+ info_module (ModuleType): The module containing the class.
+
+ Returns:
+ List[AttributeInfo]: List of extracted attributes.
"""
attributes: List[AttributeInfo] = []
annotations = getattr(cls_obj, '__annotations__', {})
@@ -243,31 +260,43 @@ def extract_attributes(self, cls_obj, info_module: ModuleType) -> List[Attribute
def extract_classes(self, info_module: ModuleType) -> List[ClassInfo]:
"""
- Extracts class definitions from the module.
+ Extract all class definitions from a module.
+
+ Args:
+ info_module (ModuleType): The module to inspect.
Returns:
- --------
- List[ClassInfo]
+ List[ClassInfo]: Metadata about the module’s classes.
"""
classes = []
for name, cls in inspect.getmembers(info_module, inspect.isclass):
attributes = self.extract_attributes(cls, info_module)
methods = self.extract_methods(cls)
- superclasses = [base.__name__ for base in cls.__bases__ if base.__name__ != "object"]
+ superclasses = self.extract_superclasses(cls)
classes.append(ClassInfo(name, attributes, methods, superclasses))
return classes
- def extract_superclasses(self, module: ModuleType) -> List[str]:
+ def extract_superclasses(self, cls) -> List[ClassInfo]:
"""
- Extracts unique superclass names from all classes.
+ Extract base classes for a given class, recursively.
+
+ Args:
+ cls: The class whose base classes are being extracted.
Returns:
- --------
- List[str]
+ List[ClassInfo]: Metadata for each superclass.
"""
- superclasses = set()
- for name, cls in inspect.getmembers(module, inspect.isclass):
- if cls.__module__ == module.__name__:
- for base in cls.__bases__:
- superclasses.add(base.__name__)
- return list(superclasses)
+ superclasses = []
+ for base in cls.__bases__:
+ if base.__name__ == "object":
+ continue
+ module = sys.modules.get(base.__module__)
+ if not module:
+ continue
+ superclasses.append(ClassInfo(
+ base.__name__,
+ self.extract_attributes(base, module),
+ self.extract_methods(base),
+ []
+ ))
+ return superclasses
diff --git a/src/importspy/utilities/python_util.py b/src/importspy/utilities/python_util.py
index eca7867..0e37d53 100644
--- a/src/importspy/utilities/python_util.py
+++ b/src/importspy/utilities/python_util.py
@@ -1,22 +1,20 @@
-"""
-Python Runtime Utilities
-========================
+"""Python Runtime Utilities
-Provides helpers to inspect the active Python environment,
-such as the interpreter implementation and version.
+Provides utility methods to inspect the active Python runtime environment,
+such as the version number and interpreter implementation.
-Useful in ImportSpy to validate compatibility constraints across Python versions
-and runtime variants (e.g., CPython, PyPy, IronPython).
+These utilities are useful within ImportSpy to evaluate whether the current
+runtime context satisfies declared compatibility constraints in import contracts.
+This includes checks for specific Python versions and interpreter families
+(CPython, PyPy, IronPython, etc.).
Example:
---------
-.. code-block:: python
-
- from importspy.utilities.python_util import PythonUtil
-
- util = PythonUtil()
- print(util.extract_python_version())
- print(util.extract_python_implementation())
+ >>> from importspy.utilities.python_util import PythonUtil
+ >>> util = PythonUtil()
+ >>> util.extract_python_version()
+ '3.12.0'
+ >>> util.extract_python_implementation()
+ 'CPython'
"""
import logging
@@ -25,47 +23,42 @@
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
-class PythonUtil:
- """
- Utility class for querying Python runtime details.
- Methods
- -------
- extract_python_version() -> str
- Returns the current Python version (e.g., "3.11.2").
+class PythonUtil:
+ """Utility class for inspecting Python runtime characteristics.
- extract_python_implementation() -> str
- Returns the Python interpreter name (e.g., "CPython", "PyPy").
+ Used internally by ImportSpy to validate runtime-specific conditions declared
+ in `.yml` import contracts. This includes checking Python version and interpreter
+ type during structural introspection and contract validation.
"""
def extract_python_version(self) -> str:
- """
- Return the active Python version.
+ """Return the currently active Python version as a string.
- Returns
- -------
- str
- Python version string (e.g., '3.11.2').
+ This method queries the runtime using `platform.python_version()` and is
+ typically used to match version constraints defined in an import contract.
- Example
- -------
- >>> PythonUtil().extract_python_version()
- '3.11.2'
+ Returns:
+ str: The Python version string (e.g., "3.11.4").
+
+ Example:
+ >>> PythonUtil().extract_python_version()
+ '3.11.4'
"""
return platform.python_version()
def extract_python_implementation(self) -> str:
- """
- Return the Python implementation type.
-
- Returns
- -------
- str
- Python interpreter name (e.g., 'CPython', 'PyPy').
-
- Example
- -------
- >>> PythonUtil().extract_python_implementation()
- 'CPython'
+ """Return the implementation name of the running Python interpreter.
+
+ Common values include "CPython", "PyPy", or "IronPython". This is
+ essential in contexts where the implementation affects runtime behavior
+ or compatibility with native extensions.
+
+ Returns:
+ str: The interpreter implementation (e.g., "CPython").
+
+ Example:
+ >>> PythonUtil().extract_python_implementation()
+ 'CPython'
"""
return platform.python_implementation()
diff --git a/src/importspy/utilities/runtime_util.py b/src/importspy/utilities/runtime_util.py
index f19ebfe..60ec06e 100644
--- a/src/importspy/utilities/runtime_util.py
+++ b/src/importspy/utilities/runtime_util.py
@@ -1,20 +1,16 @@
-"""
-Runtime Environment Utilities
-=============================
-
-Provides a lightweight interface to query the system's hardware architecture.
-
-This module supports ImportSpy in enforcing architecture-specific constraints
-declared in import contracts.
+"""Runtime Environment Utilities
-Example
--------
-.. code-block:: python
+Provides a lightweight utility for querying the system's hardware architecture.
- from importspy.utilities.runtime_util import RuntimeUtil
+This module is used by ImportSpy to enforce architecture-specific constraints
+defined in import contracts (e.g., allowing a plugin only on x86_64 or arm64).
+It ensures that module imports are aligned with the intended deployment environment.
- runtime = RuntimeUtil()
- print(runtime.extract_arch())
+Example:
+ >>> from importspy.utilities.runtime_util import RuntimeUtil
+ >>> runtime = RuntimeUtil()
+ >>> runtime.extract_arch()
+ 'x86_64'
"""
import logging
@@ -23,30 +19,28 @@
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
-class RuntimeUtil:
- """
- Utility class to retrieve system architecture details.
- Methods
- -------
- extract_arch() -> str
- Returns the machine’s hardware architecture.
+class RuntimeUtil:
+ """Utility class to inspect system architecture.
- Example
- -------
- >>> RuntimeUtil().extract_arch()
- 'x86_64'
+ This class provides methods to retrieve runtime hardware architecture details,
+ which are essential when validating platform-specific import constraints
+ in ImportSpy's embedded or CLI modes.
"""
def extract_arch(self) -> str:
- """
- Return the system architecture (e.g., 'x86_64', 'arm64').
-
- Uses the `platform.machine()` method to query the current hardware.
-
- Returns
- -------
- str
- Architecture name (e.g., 'x86_64', 'arm64').
+ """Return the name of the machine's hardware architecture.
+
+ Uses `platform.machine()` to retrieve the architecture string, which may vary
+ depending on the underlying system (e.g., "x86_64", "arm64", "aarch64").
+ This is typically used during contract validation to ensure that the importing
+ environment matches expected deployment conditions.
+
+ Returns:
+ str: The system's hardware architecture.
+
+ Example:
+ >>> RuntimeUtil().extract_arch()
+ 'arm64'
"""
return platform.machine()
diff --git a/src/importspy/utilities/system_util.py b/src/importspy/utilities/system_util.py
index a886158..5fb2709 100644
--- a/src/importspy/utilities/system_util.py
+++ b/src/importspy/utilities/system_util.py
@@ -1,80 +1,79 @@
-"""
-System Utilities for ImportSpy
-==============================
+"""System Utilities for ImportSpy
-Provides tools to interact with the host system and environment variables.
+Provides tools to inspect the host operating system and environment variables.
-This utility module helps ImportSpy detect and normalize runtime conditions, such as
-the operating system or environment setup, ensuring compatibility checks work reliably.
+This module supports ImportSpy by normalizing system-level information that may
+affect import contract validation. It helps ensure that environmental conditions
+are consistent and inspectable across different operating systems and deployment contexts.
Features:
----------
-- Identifies the current operating system in a standardized lowercase format.
-- Retrieves environment variables as a key-value dictionary.
+ - Detects the current operating system in a normalized, lowercase format.
+ - Retrieves all environment variables as a list of structured objects.
Example:
---------
-.. code-block:: python
-
- from importspy.utilities.system_util import SystemUtil
-
- util = SystemUtil()
- os_name = util.extract_os()
- env = util.extract_envs()
+ >>> from importspy.utilities.system_util import SystemUtil
+ >>> util = SystemUtil()
+ >>> util.extract_os()
+ 'linux'
+ >>> envs = util.extract_envs()
+ >>> envs[0]
+ VariableInfo(name='PATH', annotation=None, value='/usr/bin')
"""
import os
import logging
import platform
+from collections import namedtuple
+from typing import List
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
-class SystemUtil:
- """
- System-level utility class for environment inspection.
+VariableInfo = namedtuple('VariableInfo', ["name", "annotation", "value"])
- Offers support for OS detection and retrieval of environment variables.
- Methods
- -------
- extract_os() -> str
- Returns the lowercase name of the operating system (e.g., 'windows', 'linux').
+class SystemUtil:
+ """Utility class for inspecting system-level properties.
+
+ Used by ImportSpy to collect information about the current operating system
+ and active environment variables. These details are typically validated
+ against constraints defined in `.yml` import contracts.
- extract_envs() -> dict
- Returns a dictionary of all active environment variables.
+ Methods:
+ extract_os(): Return the normalized name of the current operating system.
+ extract_envs(): Return all active environment variables as structured entries.
"""
def extract_os(self) -> str:
- """
- Return the operating system name in lowercase.
+ """Return the name of the operating system in lowercase format.
- Uses `platform.system()` for OS detection.
+ This method uses `platform.system()` and normalizes the result
+ to lowercase. It simplifies comparisons with import contract conditions
+ that expect a canonical form such as "linux", "darwin", or "windows".
- Returns
- -------
- str
- 'windows', 'linux', or 'darwin', depending on the system.
+ Returns:
+ str: The normalized operating system name (e.g., "linux", "windows").
- Example
- -------
- >>> SystemUtil().extract_os()
- 'linux'
+ Example:
+ >>> SystemUtil().extract_os()
+ 'darwin'
"""
return platform.system().lower()
- def extract_envs(self) -> dict:
- """
- Retrieve all current environment variables.
+ def extract_envs(self) -> List[VariableInfo]:
+ """Return all environment variables as a list of structured objects.
+
+ Collects all key-value pairs from `os.environ` and wraps them in
+ `VariableInfo` namedtuples. The `annotation` field is reserved for
+ optional type annotation metadata (currently set to `None`).
- Returns
- -------
- dict
- Dictionary of key-value environment variables.
+ Returns:
+ List[VariableInfo]: A list of environment variables available
+ in the current process environment.
- Example
- -------
- >>> SystemUtil().extract_envs()
- {'PATH': '/usr/bin:/bin', 'HOME': '/home/user', ...}
+ Example:
+ >>> envs = SystemUtil().extract_envs()
+ >>> envs[0]
+ VariableInfo(name='PATH', annotation=None, value='/usr/bin')
"""
- return dict(os.environ)
+ return [VariableInfo(name, None, value) for name, value in os.environ.items()]
diff --git a/src/importspy/validators.py b/src/importspy/validators.py
new file mode 100644
index 0000000..1833b4c
--- /dev/null
+++ b/src/importspy/validators.py
@@ -0,0 +1,501 @@
+from typing import List
+from .models import (
+ Runtime,
+ System,
+ Environment,
+ Python,
+ Module,
+ Variable,
+ Function,
+ Class
+)
+
+from .violation_systems import (
+ RuntimeContractViolation,
+ SystemContractViolation,
+ VariableContractViolation,
+ PythonContractViolation,
+ ModuleContractViolation,
+ BaseContractViolation,
+ FunctionContractViolation,
+ Bundle
+)
+
+from .constants import (
+ Constants,
+ Contexts,
+ Errors
+)
+
+from .config import Config
+
+from .log_manager import LogManager
+
+class RuntimeValidator:
+
+ def validate(
+ self,
+ runtimes_1: List[Runtime],
+ runtimes_2: List[Runtime],
+ contract_violation: RuntimeContractViolation
+
+ ):
+ if not runtimes_1:
+ return
+
+ bundle: Bundle = contract_violation.bundle
+ bundle[Errors.KEY_RUNTIMES_1] = runtimes_1
+
+ if not runtimes_2:
+ raise ValueError(
+ RuntimeContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ ).missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+ runtime_2 = runtimes_2[0]
+
+ for runtime_1 in runtimes_1:
+ if runtime_1.arch == runtime_2.arch:
+ return runtime_1
+ raise ValueError(RuntimeContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ ).missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+
+class SystemValidator:
+
+
+ def __init__(self):
+
+ self._environment_validator = SystemValidator.EnvironmentValidator()
+
+ def validate(
+ self,
+ systems_1: List[System],
+ systems_2: List[System],
+ contract_violation: SystemContractViolation
+ ):
+
+ if not systems_1:
+ return
+
+ bundle: Bundle = contract_violation.bundle
+ bundle[Errors.KEY_SYSTEMS_1] = systems_1
+
+ if not systems_2:
+ raise ValueError(
+ SystemContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ ).missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+ system_2 = systems_2[0]
+
+ for system_1 in systems_1:
+ if system_1.os == system_2.os:
+ if system_1.environment:
+ self._environment_validator.validate(system_1.environment, system_2.environment, bundle)
+ return system_1.pythons
+ raise ValueError(
+ SystemContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ ).missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+ class EnvironmentValidator:
+
+ def validate(self,
+ environment_1: Environment,
+ environment_2: Environment,
+ bundle: Bundle
+ ):
+
+ if not environment_1:
+ return
+
+ bundle[Errors.KEY_ENVIRONMENT_1] = environment_1
+
+ if not environment_2:
+ raise ValueError(
+ VariableContractViolation(
+ Errors.SCOPE_VARIABLE,
+ Contexts.ENVIRONMENT_CONTEXT,
+ bundle
+ ).missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+ variables_2 = environment_2.variables
+
+ if environment_1.variables:
+ variables_1 = environment_1.variables
+ VariableValidator().validate(
+ variables_1,
+ variables_2,
+ VariableContractViolation(
+ Errors.SCOPE_VARIABLE,
+ Contexts.ENVIRONMENT_CONTEXT,
+ bundle
+ )
+ )
+
+class PythonValidator:
+
+ def validate(
+ self,
+ pythons_1: List[Python],
+ pythons_2: List[Python],
+ contract_violation: PythonContractViolation
+ ):
+ if not pythons_1:
+ return
+
+ bundle: Bundle = contract_violation.bundle
+ bundle[Errors.KEY_PYTHONS_1] = pythons_1
+
+ if not pythons_2:
+ raise ValueError(
+ PythonContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ ).missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+ python_2 = pythons_2[0]
+ for python_1 in pythons_1:
+
+ if self._is_python_match(python_1, python_2, contract_violation):
+ return python_1.modules
+
+ raise ValueError(
+ PythonContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ ).missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+ def _is_python_match(
+ self,
+ python_1: Python,
+ python_2: Python,
+ contract_violation: PythonContractViolation
+ ) -> bool:
+ bundle: Bundle = contract_violation.bundle
+ bundle[Errors.KEY_PYTHON_1] = python_1
+ if python_1.version and python_1.interpreter:
+ return (
+ python_1.version == python_2.version and
+ python_1.interpreter == python_2.interpreter
+ )
+
+ if python_1.version:
+ return python_1.version == python_2.version
+
+ if python_1.interpreter:
+ return python_1.interpreter == python_2.interpreter
+
+class ModuleValidator:
+
+ def __init__(self):
+ self.variable_validator:VariableValidator = VariableValidator()
+ self.function_validator:FunctionValidator = FunctionValidator()
+ self.class_validator:ClassValidator = ClassValidator()
+
+ def validate(
+ self,
+ modules_1: List[Module],
+ module_2: Module,
+ contract_violation: ModuleContractViolation
+
+ ):
+ bundle: Bundle = contract_violation.bundle
+ if not modules_1:
+ return
+
+ bundle[Errors.KEY_MODULES_1] = modules_1
+
+ if not module_2:
+ raise ValueError(
+ ModuleContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ ).missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+ for module_1 in modules_1:
+
+ bundle[Errors.KEY_MODULE_NAME] = module_1.filename
+ bundle[Errors.KEY_MODULE_VERSION] = module_1.version
+
+ if module_1.filename and module_1.filename != module_2.filename:
+ raise ValueError(
+ ModuleContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ ).mismatch_error_handler(module_1.filename, module_2.filename, Errors.ENTITY_MESSAGES))
+
+ if module_1.version and module_1.version != module_2.version:
+ raise ValueError(
+ ModuleContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ ).mismatch_error_handler(module_1.version, module_2.version, Errors.ENTITY_MESSAGES))
+
+ self.variable_validator.validate(
+ module_1.variables,
+ module_2.variables,
+ VariableContractViolation(
+ Errors.SCOPE_VARIABLE,
+ Contexts.MODULE_CONTEXT,
+ bundle
+ )
+ )
+
+ self.function_validator.validate(
+ module_1.functions,
+ module_2.functions,
+ FunctionContractViolation(
+ Contexts.MODULE_CONTEXT,
+ bundle
+ )
+ )
+
+ self.class_validator.validate(
+ module_1.classes,
+ module_2.classes,
+ ModuleContractViolation(
+ Contexts.CLASS_CONTEXT,
+ bundle
+ )
+ )
+
+class ClassValidator:
+
+ def __init__(self):
+
+ self.variable_validator:VariableValidator = VariableValidator()
+ self.function_validator:FunctionValidator = FunctionValidator()
+
+ def validate(
+ self,
+ classes_1: List[Class],
+ classes_2: List[Class],
+ contract_violation: BaseContractViolation
+ ):
+ if not classes_1:
+ return
+
+ bundle: Bundle = contract_violation.bundle
+ bundle[Errors.KEY_CLASSES_1] = classes_1
+
+ if not classes_2:
+ raise ValueError(
+ ModuleContractViolation(
+ Contexts.CLASS_CONTEXT,
+ bundle
+ ).missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+ for class_1 in classes_1:
+ class_2 = next((cls for cls in classes_2 if cls.name == class_1.name), None)
+
+ bundle[Errors.KEY_CLASS_NAME] = class_1.name
+
+ if not class_2:
+ raise ValueError(
+ ModuleContractViolation(
+ Contexts.CLASS_CONTEXT,
+ bundle
+ ).missing_error_handler(Errors.ENTITY_MESSAGES)
+ )
+
+ bundle[Errors.KEY_ATTRIBUTE_TYPE] = Config.CLASS_TYPE
+
+ self.variable_validator.validate(
+ class_1.get_class_attributes(),
+ class_2.get_class_attributes(),
+ VariableContractViolation(Errors.SCOPE_ARGUMENT, Contexts.CLASS_CONTEXT, bundle)
+ )
+
+ bundle[Errors.KEY_ATTRIBUTE_TYPE] = Config.INSTANCE_TYPE
+
+ self.variable_validator.validate(
+ class_1.get_instance_attributes(),
+ class_2.get_instance_attributes(),
+ VariableContractViolation(Errors.SCOPE_ARGUMENT, Contexts.CLASS_CONTEXT, bundle)
+ )
+
+ self.function_validator.validate(
+ class_1.methods,
+ class_2.methods,
+ FunctionContractViolation(
+ Contexts.CLASS_CONTEXT,
+ bundle
+ )
+ )
+
+ self.validate(class_1.superclasses,
+ class_2.superclasses,
+ ModuleContractViolation(
+ Contexts.CLASS_CONTEXT,
+ bundle
+ )
+ )
+
+class VariableValidator:
+
+ def __init__(self):
+
+ self.logger = LogManager().get_logger(self.__class__.__name__)
+
+ def validate(
+ self,
+ variables_1: List[Variable],
+ variables_2: List[Variable],
+ contract_violation: VariableContractViolation
+ ):
+ bundle: Bundle = contract_violation.bundle
+ self.logger.debug(f"Type of variables_1: {type(variables_1)}")
+ self.logger.debug(
+ Constants.LOG_MESSAGE_TEMPLATE.format(
+ operation="Variable validating",
+ status="Starting",
+ details=f"Expected Variables: {variables_1} ; Actual Variables: {variables_2}"
+ )
+ )
+
+ if not variables_1:
+ self.logger.debug(
+ Constants.LOG_MESSAGE_TEMPLATE.format(
+ operation="Check if variables_1 is not none",
+ status="Finished",
+ details="No expected Variables to validate"
+ )
+ )
+ return
+
+ bundle[Errors.VARIABLES_DINAMIC_PAYLOAD[contract_violation.scope][Errors.COLLECTIONS_MESSAGES][contract_violation.context]] = variables_1
+
+ if not variables_2:
+ self.logger.debug(
+ Constants.LOG_MESSAGE_TEMPLATE.format(
+ operation="Checking variables_2 when variables_1 is missing",
+ status="Finished",
+ details="No actual Variables found for validation"
+ )
+ )
+ raise ValueError(contract_violation.missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+ for var_1 in variables_1:
+ self.logger.debug(
+ Constants.LOG_MESSAGE_TEMPLATE.format(
+ operation="Variable validating",
+ status="Progress",
+ details=f"Current var_1: {var_1}"
+ )
+ )
+ bundle[Errors.VARIABLES_DINAMIC_PAYLOAD[contract_violation.scope][Errors.ENTITY_MESSAGES][contract_violation.context]] = var_1.name
+ self.logger.debug(bundle)
+ if var_1.name not in {var.name for var in variables_2}:
+ raise ValueError(contract_violation.missing_error_handler(Errors.ENTITY_MESSAGES))
+
+ for var_1 in variables_1:
+ var_2 = next((var for var in variables_2 if var.name == var_1.name), None)
+ if not var_2:
+ raise ValueError(contract_violation.missing_error_handler(Errors.ENTITY_MESSAGES))
+
+ if var_1.annotation and var_1.annotation != var_2.annotation:
+ raise ValueError(contract_violation.mismatch_error_handler(var_1.annotation, var_2.annotation, Errors.ENTITY_MESSAGES))
+
+ if var_1.value != var_2.value:
+ raise ValueError(contract_violation.mismatch_error_handler(var_1.value, var_2.value, Errors.ENTITY_MESSAGES))
+
+class FunctionValidator:
+
+ def __init__(self):
+
+ self.argument_validator:VariableValidator = VariableValidator()
+ self.logger = LogManager().get_logger(self.__class__.__name__)
+
+ def validate(
+ self,
+ functions_1: List[Function],
+ functions_2: List[Function],
+ contract_violation: BaseContractViolation
+ ):
+
+ bundle: Bundle = contract_violation.bundle
+ self.logger.debug(
+ Constants.LOG_MESSAGE_TEMPLATE.format(
+ operation="Function validating",
+ status="Starting",
+ details=f"Expected functions: {functions_1} ; Current functions: {functions_2}"
+ )
+ )
+
+ if not functions_1:
+ self.logger.debug(
+ Constants.LOG_MESSAGE_TEMPLATE.format(
+ operation="Check if functions_1 is not none",
+ status="Finished",
+ details="No functions to validate"
+ )
+ )
+ return
+
+ bundle[Errors.FUNCTIONS_DINAMIC_PAYLOAD[Errors.COLLECTIONS_MESSAGES][contract_violation.context]] = functions_1
+
+ if not functions_2:
+ self.logger.debug(
+ Constants.LOG_MESSAGE_TEMPLATE.format(
+ operation="Checking functions_2 when functions_1 is missing",
+ status="Finished",
+ details="No actual functions found"
+ )
+ )
+ raise ValueError(contract_violation.missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+
+ for function_1 in functions_1:
+ bundle[Errors.FUNCTIONS_DINAMIC_PAYLOAD[Errors.ENTITY_MESSAGES][contract_violation.context]] = function_1.name
+ self.logger.debug(
+ Constants.LOG_MESSAGE_TEMPLATE.format(
+ operation="Function validating",
+ status="Progress",
+ details=f"Current function: {function_1}"
+ )
+ )
+ if function_1.name not in {f.name for f in functions_2}:
+ self.logger.debug(
+ Constants.LOG_MESSAGE_TEMPLATE.format(
+ operation="Checking if function_1 is in functions_2",
+ status="Finished for function missing",
+ details=f"function_1: {function_1}; functions_2: {functions_2}"
+ )
+ )
+ raise ValueError(contract_violation.missing_error_handler(Errors.ENTITY_MESSAGES))
+
+ for function_1 in functions_1:
+ function_2 = next((f for f in functions_2 if f.name == function_1.name), None)
+ if not function_2:
+ raise ValueError(contract_violation.missing_error_handler(Errors.ENTITY_MESSAGES))
+
+ self.argument_validator.validate(
+ function_1.arguments,
+ function_2.arguments,
+ VariableContractViolation(
+ Errors.SCOPE_ARGUMENT,
+ Contexts.MODULE_CONTEXT,
+ bundle)
+ )
+
+ if function_1.return_annotation and function_1.return_annotation != function_2.return_annotation:
+ raise ValueError(contract_violation.mismatch_error_handler(
+ function_1.return_annotation,
+ function_2.return_annotation,
+ Errors.ENTITY_MESSAGES
+ )
+ )
+
+ self.logger.debug(
+ Constants.LOG_MESSAGE_TEMPLATE.format(
+ operation="Function validating",
+ status="Completed",
+ details="Validation successful."
+ )
+ )
diff --git a/src/importspy/validators/argument_validator.py b/src/importspy/validators/argument_validator.py
deleted file mode 100644
index a8eb027..0000000
--- a/src/importspy/validators/argument_validator.py
+++ /dev/null
@@ -1,162 +0,0 @@
-"""
-importspy.validators.argument_validator
-=======================================
-
-This module provides validation for function or method arguments
-within Python modules being inspected by ImportSpy.
-
-The `ArgumentValidator` compares declared arguments from the import contract
-against the actual arguments found in the target module, ensuring:
-- Name consistency
-- Type annotation compliance
-- Default value consistency
-
-This validator is typically called from FunctionValidator or ClassValidator
-as part of a full SpyModel validation.
-"""
-
-from ..models import Argument
-from ..errors import Errors
-from ..constants import Constants
-from typing import Optional, List
-from importspy.log_manager import LogManager
-
-
-class ArgumentValidator:
- """
- Validates argument definitions within functions or methods.
-
- This class ensures that each expected argument matches its actual counterpart
- in terms of name, type annotation, and default value.
-
- Attributes
- ----------
- logger : logging.Logger
- Internal logger used for debug output.
-
- Methods
- -------
- validate(arguments_1, arguments_2, function_name, class_name="")
- Compare two sets of arguments and raise errors for mismatches.
- """
-
- def __init__(self):
- """
- Initialize the ArgumentValidator with a scoped logger.
- """
- self.logger = LogManager().get_logger(self.__class__.__name__)
-
- def validate(
- self,
- arguments_1: List[Argument],
- arguments_2: List[Argument],
- function_name: str,
- class_name: Optional[str] = ""
- ):
- """
- Validate function or method arguments for name, type, and value compliance.
-
- Parameters
- ----------
- arguments_1 : List[Argument]
- List of expected arguments defined in the import contract.
-
- arguments_2 : List[Argument]
- List of actual arguments found in the inspected module.
-
- function_name : str
- The name of the function or method being validated.
-
- class_name : Optional[str], default=""
- The name of the class containing the method (if any), used for error context.
-
- Returns
- -------
- bool
- True if validation passes without raising an exception.
-
- Raises
- ------
- ValueError
- - If expected arguments are missing.
- - If type annotations mismatch.
- - If default values differ.
-
- Example
- -------
- >>> validator = ArgumentValidator()
- >>> validator.validate(
- ... arguments_1=[Argument(name="x", annotation="int")],
- ... arguments_2=[Argument(name="x", annotation="int")],
- ... function_name="my_function"
- ... )
- True
- """
- context_name = f"method {function_name}" if class_name else f"function {function_name}"
-
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Attribute validating",
- status="Starting",
- details=f"Expected attributes: {arguments_1} ; Current attributes: {arguments_2}"
- )
- )
-
- if not arguments_1:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Check if arguments_1 is not none",
- status="Finished",
- details=f"No declared arguments to validate; arguments_1: {arguments_1}"
- )
- )
- return
-
- if not arguments_2:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Checking arguments_2 when arguments_1 is missing",
- status="Finished",
- details=f"No actual arguments found; arguments_2: {arguments_2}"
- )
- )
- raise ValueError(Errors.ELEMENT_MISSING.format(arguments_1))
-
- for argument_1 in arguments_1:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Argument validating",
- status="Progress",
- details=f"Current argument_1: {argument_1}"
- )
- )
- if argument_1.name not in set(arg.name for arg in arguments_2):
- raise ValueError(Errors.ARGUMENT_MISSING.format(argument_1.name, context_name))
-
- for argument_1 in arguments_1:
- argument_2 = next((arg for arg in arguments_2 if arg.name == argument_1.name), None)
-
- if not argument_2:
- raise ValueError(Errors.ELEMENT_MISSING.format(argument_1))
-
- if argument_1.annotation and argument_1.annotation != argument_2.annotation:
- raise ValueError(
- Errors.ARGUMENT_MISMATCH.format(
- Constants.ANNOTATION,
- argument_1.name,
- argument_1.annotation,
- argument_2.annotation
- )
- )
-
- if argument_1.value != argument_2.value:
- raise ValueError(
- Errors.ARGUMENT_MISMATCH.format(
- Constants.VALUE,
- argument_1.name,
- argument_1.value,
- argument_2.value
- )
- )
-
- return True
diff --git a/src/importspy/validators/attribute_validator.py b/src/importspy/validators/attribute_validator.py
deleted file mode 100644
index 9dcc3b8..0000000
--- a/src/importspy/validators/attribute_validator.py
+++ /dev/null
@@ -1,172 +0,0 @@
-"""
-importspy.validators.attribute_validator
-========================================
-
-This module implements the validation logic for class and instance attributes
-within modules inspected by ImportSpy.
-
-The `AttributeValidator` ensures that attributes declared in the import contract
-match the ones actually defined in the module under inspection in terms of:
-- Existence
-- Type annotation
-- Default value
-"""
-
-from ..models import Attribute
-from ..errors import Errors
-from ..constants import Constants
-from typing import List
-from importspy.log_manager import LogManager
-
-
-class AttributeValidator:
- """
- Validator for class and instance attributes.
-
- Compares expected attributes (from the import contract) with those
- extracted from the inspected module. Ensures attribute names,
- annotations, and values are consistent.
-
- Attributes
- ----------
- logger : logging.Logger
- Internal logger used for debug tracing during validation.
-
- Methods
- -------
- validate(attrs_1, attrs_2, classname)
- Performs full validation of attributes for a given class.
- """
-
- def __init__(self):
- """
- Initializes the AttributeValidator with scoped logging.
- """
- self.logger = LogManager().get_logger(self.__class__.__name__)
-
- def validate(
- self,
- attrs_1: List[Attribute],
- attrs_2: List[Attribute],
- classname: str
- ):
- """
- Validates expected vs actual attributes in a class definition.
-
- Parameters
- ----------
- attrs_1 : List[Attribute]
- List of attributes defined in the import contract.
-
- attrs_2 : List[Attribute]
- List of attributes found in the actual module.
-
- classname : str
- The name of the class whose attributes are being validated.
-
- Returns
- -------
- bool
- True if all attributes match expectations.
-
- Raises
- ------
- ValueError
- - If required attributes are missing.
- - If type annotations differ.
- - If attribute values differ.
-
- Example
- -------
- >>> validator = AttributeValidator()
- >>> validator.validate(
- ... attrs_1=[Attribute(name="path", value="/tmp", annotation="str", type="class")],
- ... attrs_2=[Attribute(name="path", value="/tmp", annotation="str", type="class")],
- ... classname="Config"
- ... )
- True
- """
- self.logger.debug(f"Type of attrs_1: {type(attrs_1)}")
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Attribute validating",
- status="Starting",
- details=f"Expected attributes: {attrs_1} ; Current attributes: {attrs_2}"
- )
- )
-
- if not attrs_1:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Check if attrs_1 is not none",
- status="Finished",
- details=f"No expected attributes; attrs_1: {attrs_1}"
- )
- )
- return
-
- if not attrs_2:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Checking attrs_2 when attrs_1 is missing",
- status="Finished",
- details=f"No actual attributes found; attrs_2: {attrs_2}"
- )
- )
- raise ValueError(Errors.ELEMENT_MISSING.format(attrs_1))
-
- for attr_1 in attrs_1:
- self.logger.debug(f"Type of attr_1: {type(attr_1)}")
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Attribute validating",
- status="Progress",
- details=f"Current attr_1: {attr_1}"
- )
- )
- if attr_1.name not in {attr.name for attr in attrs_2}:
- self.logger.debug(Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Checking if attr_1 is in attrs_2",
- status="Finished",
- details=Errors.CLASS_ATTRIBUTE_MISSING.format(
- attr_1.type,
- f"{attr_1.name}={attr_1.value}",
- classname
- )
- ))
- raise ValueError(
- Errors.CLASS_ATTRIBUTE_MISSING.format(
- attr_1.type,
- f"{attr_1.name}={attr_1.value}",
- classname
- )
- )
-
- for attr_1 in attrs_1:
- attr_2 = next((attr for attr in attrs_2 if attr.name == attr_1.name), None)
- if not attr_2:
- raise ValueError(Errors.ELEMENT_MISSING.format(attrs_1))
-
- if attr_1.annotation and attr_1.annotation != attr_2.annotation:
- raise ValueError(
- Errors.CLASS_ATTRIBUTE_MISMATCH.format(
- Constants.ANNOTATION,
- attr_1.type,
- attr_1.name,
- attr_1.annotation,
- attr_2.annotation
- )
- )
-
- if attr_1.value != attr_2.value:
- raise ValueError(
- Errors.CLASS_ATTRIBUTE_MISMATCH.format(
- Constants.VALUE,
- attr_1.type,
- attr_1.name,
- attr_1.value,
- attr_2.value
- )
- )
-
- return True
diff --git a/src/importspy/validators/common_validator.py b/src/importspy/validators/common_validator.py
deleted file mode 100644
index 27227af..0000000
--- a/src/importspy/validators/common_validator.py
+++ /dev/null
@@ -1,161 +0,0 @@
-"""
-importspy.validators.common_validator
-=====================================
-
-Reusable validation logic for dictionary and list structures.
-
-This module defines the `CommonValidator` class, which provides utility methods to validate
-data structures commonly used in contract inspection:
-- General list containment validation
-- Key/value consistency between dictionaries
-
-It is used across structural validators like:
-- SystemValidator
-- ModuleValidator
-- RuntimeValidator
-"""
-from typing import List, Dict
-from ..errors import Errors
-from ..constants import Constants
-from ..log_manager import LogManager
-
-
-class CommonValidator:
- """
- Common validation utilities for iterable structures.
-
- This helper class enables reusable checks across all ImportSpy validators.
- It ensures list and dict structures match between expected (from `.yml`)
- and actual (live modules) data.
-
- Validation Modes:
- -----------------
- - list_validate(...) : All elements in list1 must exist in list2.
- - dict_validate(...) : All key/value pairs in dict1 must match dict2.
-
- Attributes
- ----------
- logger : logging.Logger
- A scoped logger for structured output and debugging.
- """
-
- def __init__(self):
- """
- Initializes the CommonValidator and its logger.
- """
- self.logger = LogManager().get_logger(self.__class__.__name__)
-
- def list_validate(
- self,
- list1: List,
- list2: List,
- missing_error: str,
- *args
- ) -> None:
- """
- Validates that all elements in `list1` exist in `list2`.
-
- Parameters
- ----------
- list1 : List
- The expected list of items.
- list2 : List
- The actual list to be validated.
- missing_error : str
- Error message format if an element is missing (e.g., "Missing: {0}").
- *args : tuple
- Optional dynamic context passed to `missing_error.format(...)`.
-
- Raises
- ------
- ValueError
- If any element from `list1` is not present in `list2`.
-
- Returns
- -------
- None
-
- Example
- -------
- >>> CommonValidator().list_validate(["A", "B"], ["A"], "Missing item: {0}")
- """
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="List validating",
- status="Starting",
- details=f"Expected list: {list1} ; Current list: {list2}"
- )
- )
-
- if not list1:
- return
- if list1 and not list2:
- return
- if not list2:
- raise ValueError(Errors.ELEMENT_MISSING.format(list1))
-
- for expected_element in list1:
- if expected_element not in list2:
- raise ValueError(missing_error.format(expected_element, *args))
-
- def dict_validate(
- self,
- dict1: Dict,
- dict2: Dict,
- missing_error: str,
- mismatch_error: str
- ) -> bool:
- """
- Validates keys and values between two dictionaries.
-
- Parameters
- ----------
- dict1 : Dict
- The expected key-value mapping.
- dict2 : Dict
- The actual dictionary to check.
- missing_error : str
- Format string for a missing key (e.g., "Key missing: {0}").
- mismatch_error : str
- Format string for value mismatch (e.g., "Mismatch for {0}: {1} != {2}").
-
- Returns
- -------
- bool
- True if validation passes.
-
- Raises
- ------
- ValueError
- If a key is missing or a value does not match.
-
- Example
- -------
- >>> CommonValidator().dict_validate(
- ... {"x": 1}, {"x": 2},
- ... "Missing key: {0}",
- ... "Mismatch for {0}: expected {1}, got {2}"
- ... )
- """
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Dict validating",
- status="Starting",
- details=f"Expected dict: {dict1} ; Current dict: {dict2}"
- )
- )
-
- if not dict1:
- return True
- if not dict2:
- raise ValueError(missing_error.format(dict1))
-
- for expected_key, expected_value in dict1.items():
- if expected_key in dict2:
- actual_value = dict2[expected_key]
- if expected_value != actual_value:
- raise ValueError(mismatch_error.format(expected_key, expected_value, actual_value))
- else:
- raise ValueError(missing_error.format(expected_key))
-
- return True
diff --git a/src/importspy/validators/function_validator.py b/src/importspy/validators/function_validator.py
deleted file mode 100644
index 72ef6b7..0000000
--- a/src/importspy/validators/function_validator.py
+++ /dev/null
@@ -1,160 +0,0 @@
-"""
-importspy.validators.function_validator
-=======================================
-
-Validator for function declarations and signatures.
-
-This module defines the `FunctionValidator`, responsible for verifying that functions
-defined in a Python module (or class) match those specified in an import contract.
-It ensures that:
-- Each expected function is present.
-- Return annotations are correct.
-- Function arguments match in name, annotation, and value.
-
-This validator uses `ArgumentValidator` to validate function arguments.
-"""
-
-from ..models import Function
-from ..errors import Errors
-from ..constants import Constants
-from typing import List, Optional
-from ..log_manager import LogManager
-from .argument_validator import ArgumentValidator
-
-
-class FunctionValidator:
- """
- Validator for function declarations and signatures.
-
- Attributes
- ----------
- logger : logging.Logger
- Logger used for debug tracing.
- _argument_validator : ArgumentValidator
- Helper validator to handle argument validation.
- """
-
- def __init__(self):
- """
- Initialize the function validator and argument checker.
- """
- self.logger = LogManager().get_logger(self.__class__.__name__)
- self._argument_validator = ArgumentValidator()
-
- def validate(
- self,
- functions_1: List[Function],
- functions_2: List[Function],
- classname: Optional[str] = ""
- ) -> Optional[bool]:
- """
- Validate a list of expected functions against actual module functions.
-
- Parameters
- ----------
- functions_1 : List[Function]
- The list of expected functions (from import contract).
- functions_2 : List[Function]
- The actual functions extracted from the module.
- classname : Optional[str], default=""
- If validating class methods, the class name (for error context).
-
- Returns
- -------
- Optional[bool]
- True if validation passes, None if nothing to validate.
-
- Raises
- ------
- ValueError
- If:
- - A function is missing.
- - Return annotations differ.
- - Argument validation fails.
-
- Example
- -------
- >>> validator = FunctionValidator()
- >>> validator.validate(expected_functions, actual_functions, classname="MyService")
- True
- """
- context_name = f"method in class {classname}" if classname else "function"
-
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Function validating",
- status="Starting",
- details=f"Expected functions: {functions_1} ; Current functions: {functions_2}"
- )
- )
-
- if not functions_1:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Check if functions_1 is not none",
- status="Finished",
- details="No functions to validate"
- )
- )
- return None
-
- if not functions_2:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Checking functions_2 when functions_1 is missing",
- status="Finished",
- details="No actual functions found"
- )
- )
- raise ValueError(Errors.ELEMENT_MISSING.format(functions_1))
-
- for function_1 in functions_1:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Function validating",
- status="Progress",
- details=f"Current function: {function_1}"
- )
- )
- if function_1.name not in {f.name for f in functions_2}:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Checking if function_1 is in functions_2",
- status="Finished for function missing",
- details=f"function_1: {function_1}; functions_2: {functions_2}"
- )
- )
- raise ValueError(
- Errors.FUNCTIONS_MISSING.format(context_name, function_1.name)
- )
-
- for function_1 in functions_1:
- function_2 = next((f for f in functions_2 if f.name == function_1.name), None)
- if not function_2:
- raise ValueError(Errors.ELEMENT_MISSING.format(function_1))
-
- self._argument_validator.validate(
- function_1.arguments,
- function_2.arguments,
- function_1.name,
- classname
- )
-
- if function_1.return_annotation and function_1.return_annotation != function_2.return_annotation:
- raise ValueError(
- Errors.FUNCTION_RETURN_ANNOTATION_MISMATCH.format(
- context_name,
- function_1.name,
- function_1.return_annotation,
- function_2.return_annotation
- )
- )
-
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Function validating",
- status="Completed",
- details="Validation successful."
- )
- )
- return True
diff --git a/src/importspy/validators/module_validator.py b/src/importspy/validators/module_validator.py
deleted file mode 100644
index d8b6b3f..0000000
--- a/src/importspy/validators/module_validator.py
+++ /dev/null
@@ -1,141 +0,0 @@
-"""
-importspy.validators.module_validator
-=====================================
-
-Validator for Python modules and their internal structures.
-
-This module provides the `ModuleValidator` class, responsible for checking
-that a Python module conforms to the expectations defined in an import contract.
-
-It validates:
-- Module filename and version
-- Declared global variables
-- Top-level functions
-- Declared classes, including their attributes, methods, and superclasses
-
-Delegates detailed checks to:
-- `AttributeValidator`
-- `FunctionValidator`
-- `CommonValidator`
-"""
-
-from ..models import Module
-from ..errors import Errors
-from .variable_validator import VariableValidator
-from .attribute_validator import AttributeValidator
-from .function_validator import FunctionValidator
-from .common_validator import CommonValidator
-from typing import List, Optional
-
-
-class ModuleValidator:
- """
- Validator for full Python module metadata and structure.
-
- Attributes
- ----------
- _attribute_validator : AttributeValidator
- Responsible for validating class attributes.
- _function_validator : FunctionValidator
- Validates top-level and class methods.
- """
-
- def __init__(self):
- """
- Initialize the validator with attribute and function checkers.
- """
- self._variable_validator = VariableValidator()
- self._attribute_validator = AttributeValidator()
- self._function_validator = FunctionValidator()
-
- def validate(
- self,
- modules_1: List[Module],
- module_2: Optional[Module]
- ) -> Optional[None]:
- """
- Validate one or more expected modules against the actual loaded module.
-
- Parameters
- ----------
- modules_1 : List[Module]
- List of expected module definitions from the import contract.
- module_2 : Optional[Module]
- The actual module extracted from the system for validation.
-
- Returns
- -------
- None
- Returns None when:
- - No modules to validate (`modules_1` is empty).
- - Validation is successful.
-
- Raises
- ------
- ValueError
- Raised if:
- - `module_2` is missing.
- - Filename or version mismatches.
- - Variables differ in name or value.
- - Missing or invalid functions, classes, attributes, or superclasses.
-
- Example
- -------
- >>> validator = ModuleValidator()
- >>> validator.validate([expected_module], actual_module)
- """
- if not modules_1:
- return
-
- if not module_2:
- raise ValueError(Errors.ELEMENT_MISSING.format(modules_1))
-
- for module_1 in modules_1:
- # Check filename
- if module_1.filename and module_1.filename != module_2.filename:
- raise ValueError(Errors.FILENAME_MISMATCH.format(module_1.filename, module_2.filename))
-
- # Check version
- if module_1.version and module_1.version != module_2.version:
- raise ValueError(Errors.VERSION_MISMATCH.format(module_1.version, module_2.version))
-
- self._variable_validator.validate(
- module_1.variables,
- module_2.variables)
-
- # Validate top-level functions
- self._function_validator.validate(
- module_1.functions,
- module_2.functions
- )
-
- # Validate classes and class contents
- if module_1.classes:
- for class_1 in module_1.classes:
- class_2 = next((cls for cls in module_2.classes if cls.name == class_1.name), None)
- if not class_2:
- raise ValueError(Errors.CLASS_MISSING.format(class_1.name))
-
- # Class attribute check
- self._attribute_validator.validate(
- class_1.attributes,
- class_2.attributes,
- class_1.name
- )
-
- # Method (function) check
- self._function_validator.validate(
- class_1.methods,
- class_2.methods,
- classname=class_1.name
- )
-
- # Superclass check
- CommonValidator().list_validate(
- class_1.superclasses,
- class_2.superclasses,
- Errors.CLASS_SUPERCLASS_MISSING,
- class_2.name
- )
-
- return
diff --git a/src/importspy/validators/python_validator.py b/src/importspy/validators/python_validator.py
deleted file mode 100644
index b6cc131..0000000
--- a/src/importspy/validators/python_validator.py
+++ /dev/null
@@ -1,135 +0,0 @@
-"""
-importspy.validators.python_validator
-=====================================
-
-Validator for Python runtime configurations.
-
-This module defines the `PythonValidator` class, responsible for validating
-the Python version, interpreter, and associated modules declared in an
-import contract against the actual Python runtime context.
-
-Delegates module-level validation to `ModuleValidator`.
-"""
-
-from ..models import Python
-from ..errors import Errors
-from .module_validator import ModuleValidator
-from ..log_manager import LogManager
-from ..constants import Constants
-from typing import List, Optional
-
-
-class PythonValidator:
- """
- Validates Python runtime configuration and associated modules.
-
- Attributes
- ----------
- logger : logging.Logger
- Logger instance for debugging and tracing.
- _module_validator : ModuleValidator
- Validator for modules within the Python configuration.
- """
-
- def __init__(self):
- """
- Initialize the validator and internal module validator.
- """
- self.logger = LogManager().get_logger(self.__class__.__name__)
- self._module_validator = ModuleValidator()
-
- def validate(
- self,
- pythons_1: List[Python],
- pythons_2: Optional[List[Python]]
- ) -> Optional[None]:
- """
- Validate a list of expected Python environments against actual ones.
-
- Parameters
- ----------
- pythons_1 : List[Python]
- Expected Python configurations from the contract.
- pythons_2 : Optional[List[Python]]
- Actual Python runtime details extracted from the system.
-
- Returns
- -------
- None
- Returned when:
- - `pythons_1` is empty (no validation needed).
- - Validation succeeds.
-
- Raises
- ------
- ValueError
- If `pythons_2` is missing or does not match
- the declared expectations in `pythons_1`.
-
- Example
- -------
- >>> validator = PythonValidator()
- >>> validator.validate([expected_python], [actual_python])
- """
- if not pythons_1:
- return
-
- if not pythons_2:
- raise ValueError(Errors.ELEMENT_MISSING.format(pythons_1))
-
- python_2 = pythons_2[0]
- for python_1 in pythons_1:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Python validating",
- status="Progress",
- details=f"Expected python: {python_1} ; Current python: {python_2}"
- )
- )
-
- if self._is_python_match(python_1, python_2):
- if python_2.modules:
- self._module_validator.validate(python_1.modules, python_2.modules[0])
- return
-
- def _is_python_match(
- self,
- python_1: Python,
- python_2: Python
- ) -> bool:
- """
- Determine whether two Python configurations match.
-
- Parameters
- ----------
- python_1 : Python
- Expected configuration.
- python_2 : Python
- Actual system configuration.
-
- Returns
- -------
- bool
- `True` if the two configurations match according to the declared criteria,
- otherwise `False`.
-
- Matching Criteria
- -----------------
- - If both version and interpreter are defined: match both.
- - If only version is defined: match version.
- - If only interpreter is defined: match interpreter.
- - If none are defined: match anything (default `True`).
- """
- if python_1.version and python_1.interpreter:
- return (
- python_1.version == python_2.version and
- python_1.interpreter == python_2.interpreter
- )
-
- if python_1.version:
- return python_1.version == python_2.version
-
- if python_1.interpreter:
- return python_1.interpreter == python_2.interpreter
-
- return True
diff --git a/src/importspy/validators/runtime_validator.py b/src/importspy/validators/runtime_validator.py
deleted file mode 100644
index 87e0ff3..0000000
--- a/src/importspy/validators/runtime_validator.py
+++ /dev/null
@@ -1,82 +0,0 @@
-"""
-importspy.validators.runtime_validator
-======================================
-
-Validator for runtime configurations.
-
-This module defines the `RuntimeValidator` class, which ensures that the
-runtime architecture and system-level environment of a Python module
-conform to what is declared in its import contract.
-
-Delegates system validation to `SystemValidator`.
-"""
-
-from ..models import Runtime
-from ..errors import Errors
-from .system_validator import SystemValidator
-from typing import List
-
-
-class RuntimeValidator:
- """
- Validates runtime architecture and system configurations.
-
- Attributes
- ----------
- _system_validator : SystemValidator
- Handles validation of OS and platform-specific system expectations.
- """
-
- def __init__(self):
- """
- Initialize the runtime validator and prepare the system validator.
- """
- self._system_validator = SystemValidator()
-
- def validate(
- self,
- runtimes_1: List[Runtime],
- runtimes_2: List[Runtime]
- ) -> None:
- """
- Validate expected runtime declarations against actual runtime data.
-
- Parameters
- ----------
- runtimes_1 : List[Runtime]
- The expected runtime environments declared in the contract.
- runtimes_2 : List[Runtime]
- The actual detected runtime environments from the host system.
-
- Returns
- -------
- None
- Returned when:
- - `runtimes_1` is empty (no validation required).
- - Validation completes successfully.
-
- Raises
- ------
- ValueError
- - If `runtimes_2` is missing but expectations are defined.
- - If the architectures do not match.
- - If any contained system-level configuration mismatches are detected.
-
- Example
- -------
- >>> validator = RuntimeValidator()
- >>> validator.validate([expected_runtime], [actual_runtime])
- """
- if not runtimes_1:
- return
-
- if not runtimes_2:
- raise ValueError(Errors.ELEMENT_MISSING.format(runtimes_1))
-
- runtime_2 = runtimes_2[0]
-
- for runtime_1 in runtimes_1:
- if runtime_1.arch == runtime_2.arch:
- if runtime_1.systems:
- self._system_validator.validate(runtime_1.systems, runtime_2.systems)
- return
diff --git a/src/importspy/validators/spymodel_validator.py b/src/importspy/validators/spymodel_validator.py
deleted file mode 100644
index a7d7327..0000000
--- a/src/importspy/validators/spymodel_validator.py
+++ /dev/null
@@ -1,95 +0,0 @@
-"""
-importspy.validators.spymodel_validator
-========================================
-
-Validator for top-level SpyModel objects.
-
-This module defines the `SpyModelValidator` class, which orchestrates full-model validation,
-including both runtime environments and declared modules. It is typically invoked as the
-final step during the ImportSpy validation pipeline.
-"""
-
-from ..models import SpyModel
-from .runtime_validator import RuntimeValidator
-from .module_validator import ModuleValidator
-
-
-class SpyModelValidator:
- """
- Validates the full structure of an ImportSpy model contract.
-
- The `SpyModelValidator` ensures that:
- - Declared runtime deployments (architecture + system + interpreter) match.
- - Declared module definitions (files, classes, functions) match.
-
- Delegates:
- ----------
- - Runtime inspection to `RuntimeValidator`
- - Module structure comparison to `ModuleValidator`
-
- Validation Scope:
- -----------------
- ✓ Architecture and OS validation
- ✓ Interpreter and Python version match
- ✓ Module filename, version, structure, functions, classes
-
- This validator serves as the **entry point** for verifying SpyModel objects,
- typically loaded from YAML contracts and dynamically matched against live modules.
-
- Attributes
- ----------
- _runtime_validator : RuntimeValidator
- Validates runtime architecture and interpreter.
- _module_validator : ModuleValidator
- Validates classes, functions, and variables inside modules.
- """
-
- def __init__(self):
- """
- Initializes the SpyModelValidator with supporting sub-validators.
- """
- self._runtime_validator = RuntimeValidator()
- self._module_validator = ModuleValidator()
-
- def validate(
- self,
- spy_model_1: SpyModel,
- spy_model_2: SpyModel
- ) -> None:
- """
- Validates a declared SpyModel (from contract) against the active runtime SpyModel.
-
- Parameters
- ----------
- spy_model_1 : SpyModel
- The expected SpyModel structure (loaded from contract).
- spy_model_2 : SpyModel
- The actual SpyModel structure (derived from live inspection).
-
- Returns
- -------
- None
- If validation passes or `spy_model_1` has no runtime deployments to validate.
-
- Raises
- ------
- ValueError
- If architecture, interpreter, modules, or structural expectations are not met.
-
- Example
- -------
- >>> validator = SpyModelValidator()
- >>> validator.validate(spy_model_contract, spy_model_live)
- """
- # Validate runtime deployments
- self._runtime_validator.validate(spy_model_1.deployments, spy_model_2.deployments)
-
- # Navigate through the resolved runtime > system > python > module
- self._module_validator.validate(
- [spy_model_1],
- spy_model_2
- .deployments[0]
- .systems[0]
- .pythons[0]
- .modules[0]
- )
diff --git a/src/importspy/validators/system_validator.py b/src/importspy/validators/system_validator.py
deleted file mode 100644
index 4bbb42c..0000000
--- a/src/importspy/validators/system_validator.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""
-importspy.validators.system_validator
-======================================
-
-Validator for system-level configurations.
-
-This module defines the `SystemValidator` class, responsible for validating
-operating systems, environment variables, and Python interpreter settings
-within a runtime context.
-
-Delegates:
-- Environment and key-value matching to `CommonValidator`
-- Python version/interpreter matching to `PythonValidator`
-"""
-
-from typing import List
-from ..models import System
-from ..errors import Errors
-from .common_validator import CommonValidator
-from .python_validator import PythonValidator
-
-
-class SystemValidator:
- """
- Validates system-level execution environments.
-
- This includes:
- - Operating system matching
- - Environment variable validation
- - Python configuration checks (via `PythonValidator`)
-
- Attributes
- ----------
- _python_validator : PythonValidator
- Handles validation of nested Python interpreter configurations.
- """
-
- def __init__(self):
- """
- Initialize the system validator and prepare supporting validators.
- """
- self._python_validator = PythonValidator()
-
- def validate(
- self,
- systems_1: List[System],
- systems_2: List[System]
- ) -> None:
- """
- Validate declared system expectations against actual system properties.
-
- Parameters
- ----------
- systems_1 : List[System]
- Expected system configurations as declared in the import contract.
- systems_2 : List[System]
- Actual detected system environment.
-
- Returns
- -------
- None
- Returned when:
- - `systems_1` is empty (no validation required).
- - Validation completes successfully without mismatches.
-
- Raises
- ------
- ValueError
- - If `systems_2` is missing but expected.
- - If operating systems do not match.
- - If environment variables mismatch or are missing.
- - If any Python interpreter configuration fails validation.
-
- Example
- -------
- >>> validator = SystemValidator()
- >>> validator.validate([expected_system], [actual_system])
- """
- if not systems_1:
- return
-
- if not systems_2:
- raise ValueError(Errors.ELEMENT_MISSING.format(systems_1))
-
- cv = CommonValidator()
- system_2 = systems_2[0]
-
- for system_1 in systems_1:
- if system_1.os == system_2.os:
- if system_1.envs:
- cv.dict_validate(
- system_1.envs,
- system_2.envs,
- Errors.ENV_VAR_MISSING,
- Errors.ENV_VAR_MISMATCH
- )
- if system_1.pythons:
- self._python_validator.validate(system_1.pythons, system_2.pythons)
- return
diff --git a/src/importspy/validators/variable_validator.py b/src/importspy/validators/variable_validator.py
deleted file mode 100644
index a3fdcd5..0000000
--- a/src/importspy/validators/variable_validator.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from ..log_manager import LogManager
-from ..models import Variable
-from ..constants import Constants
-from ..errors import Errors
-from typing import List
-
-class VariableValidator:
-
- def __init__(self):
- self.logger = LogManager().get_logger(self.__class__.__name__)
-
- def validate(
- self,
- variables_1: List[Variable],
- variables_2: List[Variable],
- ):
- self.logger.debug(f"Type of variables_1: {type(variables_1)}")
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Variable validating",
- status="Starting",
- details=f"Expected Variables: {variables_1} ; Current Variables: {variables_2}"
- )
- )
-
- if not variables_1:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Check if variables_1 is not none",
- status="Finished",
- details=f"No expected Variables; variables_1: {variables_1}"
- )
- )
- return
-
- if not variables_2:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Checking variables_2 when variables_1 is missing",
- status="Finished",
- details=f"No actual Variables found; variables_2: {variables_2}"
- )
- )
- raise ValueError(Errors.ELEMENT_MISSING.format(variables_1))
-
- for vars_1 in variables_1:
- self.logger.debug(
- Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Variable validating",
- status="Progress",
- details=f"Current vars_1: {vars_1}"
- )
- )
- if vars_1.name not in {var.name for var in variables_2}:
- self.logger.debug(Constants.LOG_MESSAGE_TEMPLATE.format(
- operation="Checking if vars_1 is in variables_2",
- status="Finished",
- details=Errors.VARIABLE_MISSING.format(
- f"{vars_1.name}={vars_1.value}"
- )
- ))
- raise ValueError(
- Errors.VARIABLE_MISSING.format(
- f"{vars_1.name}={vars_1.value}"
- )
- )
-
- for vars_1 in variables_1:
- vars_2 = next((var for var in variables_2 if var.name == vars_1.name), None)
- if not vars_2:
- raise ValueError(Errors.ELEMENT_MISSING.format(variables_1))
-
- if vars_1.annotation and vars_1.annotation != vars_2.annotation:
- raise ValueError(
- Errors.VARIABLE_MISMATCH.format(
- Constants.ANNOTATION,
- vars_1.name,
- vars_1.annotation,
- vars_2.annotation
- )
- )
-
- if vars_1.value != vars_2.value:
- raise ValueError(
- Errors.VARIABLE_MISMATCH.format(
- Constants.VALUE,
- vars_1.name,
- vars_1.value,
- vars_2.value
- )
- )
-
- return True
\ No newline at end of file
diff --git a/src/importspy/violation_systems.py b/src/importspy/violation_systems.py
new file mode 100644
index 0000000..460e010
--- /dev/null
+++ b/src/importspy/violation_systems.py
@@ -0,0 +1,200 @@
+"""
+This module defines the hierarchy of contract violation classes used by ImportSpy.
+
+Each violation type corresponds to a validation context (e.g., environment, runtime, module structure),
+and provides structured, human-readable error messages when the importing module does not meet the
+contract’s requirements.
+
+The base interface `ContractViolation` defines the common error interface, while specialized classes
+like `VariableContractViolation` or `RuntimeContractViolation` define formatting logic for each scope.
+
+Violations carry a dynamic `Bundle` object, which collects contextual metadata needed for
+formatting error messages and debugging failed imports.
+"""
+
+from abc import ABC, abstractmethod
+from collections.abc import MutableMapping
+from dataclasses import dataclass, field
+from typing import Optional, Any, Iterator
+from .constants import Errors
+
+
+class ContractViolation(ABC):
+ """
+ Abstract base interface for all contract violations.
+
+ Defines the core methods for rendering structured error messages,
+ including context resolution and label generation.
+
+ Properties:
+ -----------
+ - `context`: Validation context (e.g., environment, class, runtime)
+ - `label(spec)`: Retrieves the field name or reference used in error text.
+ - `missing_error_handler(spec)`: Formats error when required entity is missing.
+ - `mismatch_error_handler(expected, actual, spec)`: Formats error when values differ.
+ - `invalid_error_handler(allowed, found, spec)`: Formats error when a value is invalid.
+ """
+
+ @property
+ @abstractmethod
+ def context(self) -> str:
+ pass
+
+ @abstractmethod
+ def label(self, spec: str) -> str:
+ pass
+
+ @abstractmethod
+ def missing_error_handler(self, spec: str) -> str:
+ pass
+
+ @abstractmethod
+ def mismatch_error_handler(self, expected: Any, actual: Any, spec: str) -> str:
+ pass
+
+ @abstractmethod
+ def invalid_error_handler(self, allowed: Any, found: Any, spec: str) -> str:
+ pass
+
+
+class BaseContractViolation(ContractViolation):
+ """
+ Base implementation of a contract violation.
+
+ Includes default implementations of error formatting methods.
+ """
+
+ def __init__(self, context: str, bundle: 'Bundle'):
+ self._context = context
+ self.bundle = bundle
+ super().__init__()
+
+ @property
+ def context(self) -> str:
+ return self._context
+
+ def missing_error_handler(self, spec: str) -> str:
+ return (
+ f"{Errors.CONTEXT_INTRO[self.context]}: "
+ f"{Errors.ERROR_MESSAGE_TEMPLATES[Errors.Category.MISSING][spec][Errors.TEMPLATE_KEY].format(label=self.label(spec))} - "
+ f"{Errors.ERROR_MESSAGE_TEMPLATES[Errors.Category.MISSING][spec][Errors.SOLUTION_KEY].capitalize()}"
+ )
+
+ def mismatch_error_handler(self, expected: Any, actual: Any, spec: str) -> str:
+ return (
+ f"{Errors.CONTEXT_INTRO[self.context]}: "
+ f"{Errors.ERROR_MESSAGE_TEMPLATES[Errors.Category.MISMATCH][spec][Errors.TEMPLATE_KEY].format(label=self.label(spec), expected=expected, actual=actual)} - "
+ f"{Errors.ERROR_MESSAGE_TEMPLATES[Errors.Category.MISMATCH][spec][Errors.SOLUTION_KEY].capitalize()}"
+ )
+
+ def invalid_error_handler(self, allowed: Any, found: Any, spec: str) -> str:
+ return (
+ f"{Errors.CONTEXT_INTRO[self.context]}: "
+ f"{Errors.ERROR_MESSAGE_TEMPLATES[Errors.Category.INVALID][spec][Errors.TEMPLATE_KEY].format(label=self.label(spec), expected=allowed, actual=found)} - "
+ f"{Errors.ERROR_MESSAGE_TEMPLATES[Errors.Category.INVALID][spec][Errors.SOLUTION_KEY].capitalize()}"
+ )
+
+
+class VariableContractViolation(BaseContractViolation):
+ """
+ Contract violation handler for variables (module, class, environment, etc.).
+
+ Includes scope information to distinguish between types of variables.
+ """
+
+ def __init__(self, scope: str, context: str, bundle: 'Bundle'):
+ super().__init__(context, bundle)
+ self.scope = scope
+
+ def label(self, spec: str) -> str:
+ return Errors.VARIABLES_LABEL_TEMPLATE[self.scope][spec][self.context].format(**self.bundle)
+
+
+class FunctionContractViolation(BaseContractViolation):
+ """
+ Contract violation handler for function signature mismatches.
+ """
+
+ def __init__(self, context: str, bundle: 'Bundle'):
+ super().__init__(context, bundle)
+
+ def label(self, spec: str) -> str:
+ return Errors.FUNCTIONS_LABEL_TEMPLATE[spec][self.context].format(**self.bundle)
+
+
+class RuntimeContractViolation(BaseContractViolation):
+ """
+ Contract violation handler for runtime architecture mismatches.
+ """
+
+ def __init__(self, context: str, bundle: 'Bundle'):
+ super().__init__(context, bundle)
+
+ def label(self, spec: str) -> str:
+ return Errors.RUNTIME_LABEL_TEMPLATE[spec].format(**self.bundle)
+
+
+class SystemContractViolation(BaseContractViolation):
+ """
+ Contract violation handler for system-level mismatches (OS, environment variables).
+ """
+
+ def __init__(self, context: str, bundle: 'Bundle'):
+ super().__init__(context, bundle)
+
+ def label(self, spec: str) -> str:
+ return Errors.SYSTEM_LABEL_TEMPLATE[spec].format(**self.bundle)
+
+
+class PythonContractViolation(BaseContractViolation):
+ """
+ Contract violation handler for Python version and interpreter mismatches.
+ """
+
+ def __init__(self, context: str, bundle: 'Bundle'):
+ super().__init__(context, bundle)
+
+ def label(self, spec: str) -> str:
+ return Errors.PYTHON_LABEL_TEMPLATE[spec].format(**self.bundle)
+
+
+class ModuleContractViolation(BaseContractViolation):
+ """
+ Contract violation handler for module-level mismatches (filename, version, structure).
+ """
+
+ def __init__(self, context: str, bundle: 'Bundle'):
+ super().__init__(context, bundle)
+
+ def label(self, spec: str) -> str:
+ return Errors.MODULE_LABEL_TEMPLATE[spec][self.context].format(**self.bundle)
+
+
+@dataclass
+class Bundle(MutableMapping):
+ """
+ Shared mutable state passed to all violation handlers.
+
+ The bundle is a dynamic container used to inject contextual values
+ (like module name, attribute name, or class name) into error templates.
+ """
+
+ state: Optional[dict[str, Any]] = field(default_factory=dict)
+
+ def __getitem__(self, key):
+ return self.state[key]
+
+ def __setitem__(self, key, value):
+ self.state[key] = value
+
+ def __delitem__(self, key):
+ del self.state[key]
+
+ def __iter__(self) -> Iterator:
+ return iter(self.state)
+
+ def __len__(self) -> int:
+ return len(self.state)
+
+ def __repr__(self):
+ return repr(self.state)
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..1030f84
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,30 @@
+import pytest
+
+from importspy.violation_systems import Bundle
+
+from importspy.constants import (
+ Errors,
+ Contexts
+)
+
+@pytest.fixture
+def modulebundle() -> Bundle:
+ bundle = Bundle()
+ bundle[Errors.KEY_FILE_NAME] = "testmodule.py"
+ bundle[Errors.KEY_MODULE_VERSION] = "0.1.0"
+ return bundle
+
+@pytest.fixture
+def classbundle(modulebundle) -> Bundle:
+ modulebundle[Errors.KEY_CLASS_NAME] = "TestClass"
+ return modulebundle
+
+@pytest.fixture
+def functionbundle(modulebundle) -> Bundle:
+ modulebundle[Errors.FUNCTIONS_DINAMIC_PAYLOAD[Errors.ENTITY_MESSAGES][Contexts.MODULE_CONTEXT]] = "test_function"
+ return modulebundle
+
+@pytest.fixture
+def methodbundle(classbundle) -> Bundle:
+ classbundle[Errors.FUNCTIONS_DINAMIC_PAYLOAD[Errors.ENTITY_MESSAGES][Contexts.CLASS_CONTEXT]] = "test_method"
+ return classbundle
\ No newline at end of file
diff --git a/tests/validators/test_argument_validator.py b/tests/validators/test_argument_validator.py
index 37e0681..759396a 100644
--- a/tests/validators/test_argument_validator.py
+++ b/tests/validators/test_argument_validator.py
@@ -3,17 +3,25 @@
Argument
)
from typing import List
-from importspy.validators.argument_validator import ArgumentValidator
-from importspy.errors import Errors
+from importspy.validators import VariableValidator
import re
-from importspy.constants import Constants
+
+from importspy.constants import (
+ Errors,
+ Contexts
+)
+
+from importspy.violation_systems import (
+ VariableContractViolation,
+ Bundle
+)
class TestArgumentValidator:
- validator = ArgumentValidator()
+ validator = VariableValidator()
@pytest.fixture
- def data_1(self):
+ def data_1(self) -> Argument:
return [Argument(
name="arg1",
annotation="int",
@@ -21,7 +29,7 @@ def data_1(self):
)]
@pytest.fixture
- def data_2(self):
+ def data_2(self) -> Argument:
return [Argument(
name="arg2",
annotation="str",
@@ -29,7 +37,7 @@ def data_2(self):
)]
@pytest.fixture
- def data_3(self):
+ def data_3(self) -> Argument:
return [Argument(
name="arg1",
annotation="int",
@@ -37,13 +45,17 @@ def data_3(self):
)]
@pytest.fixture
- def data_4(self):
+ def data_4(self) -> Argument:
return [Argument(
name="arg1",
annotation="int",
value=10
)]
+ @pytest.fixture
+ def contract_violation(self, functionbundle:Bundle) -> VariableContractViolation:
+ return VariableContractViolation(Errors.SCOPE_ARGUMENT, Contexts.MODULE_CONTEXT, functionbundle)
+
@pytest.fixture
def argument_value_setter(self, data_3: List[Argument]):
data_3[0].value = 10
@@ -52,60 +64,83 @@ def argument_value_setter(self, data_3: List[Argument]):
def argument_annotation_setter(self, data_3: List[Argument]):
data_3[0].annotation = "str"
- def test_argument_match(self, data_1: List[Argument], data_4: List[Argument]):
- assert self.validator.validate(data_1, data_4, "function_name")
+ def test_argument_match(self, data_1: List[Argument], data_4: List[Argument], contract_violation):
+ assert data_1
+ assert data_4
+ assert self.validator.validate(data_1, data_4, contract_violation) is None
@pytest.mark.usefixtures("argument_value_setter")
- def test_argument_match_1(self, data_1: List[Argument], data_3: List[Argument]):
- assert self.validator.validate(data_1, data_3, "function_name")
-
- def test_argument_mismatch(self, data_2: List[Argument]):
- assert self.validator.validate(None, data_2, "function_name") is None
-
- def test_argument_mismatch_1(self, data_3: List[Argument], data_4: List[Argument]):
+ def test_argument_match_1(self, data_1: List[Argument], data_3: List[Argument], contract_violation):
+ assert data_1
+ assert data_3
+ assert self.validator.validate(data_1, data_3, contract_violation) is None
+
+ def test_argument_mismatch_no_data(self, data_2: List[Argument], contract_violation):
+ assert self.validator.validate(None, data_2, contract_violation) is None
+
+ def test_argument_mismatch_1(self, data_3: List[Argument], data_4: List[Argument], contract_violation: VariableContractViolation):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_ARGUMENT_NAME] = "arg1"
+ mock_bundle[Errors.KEY_FUNCTION_NAME] = "test_function"
+ mock_contract_violation = VariableContractViolation(Errors.SCOPE_ARGUMENT, Contexts.MODULE_CONTEXT, mock_bundle)
with pytest.raises(ValueError,
match=re.escape(
- Errors.ARGUMENT_MISMATCH.format(
- Constants.VALUE,
- data_4[0].name,
+ mock_contract_violation.mismatch_error_handler(
data_4[0].value,
- data_3[0].value
+ data_3[0].value,
+ Errors.ENTITY_MESSAGES,
)
)):
- self.validator.validate(data_4, data_3, "function_name")
+ self.validator.validate(data_4, data_3, contract_violation)
- def test_argument_mismatch_2(self):
- assert self.validator.validate(None, None, "function_name") is None
+ def test_argument_mismatch_2(self, contract_violation):
+ assert self.validator.validate(None, None, contract_violation) is None
- def test_argument_mismatch_3(self, data_2: List[Argument]):
- assert self.validator.validate(None, data_2, "function_name") is None
+ def test_argument_mismatch_3(self, data_2: List[Argument], contract_violation):
+ assert self.validator.validate(None, data_2, contract_violation) is None
@pytest.mark.usefixtures("argument_value_setter")
@pytest.mark.usefixtures("argument_annotation_setter")
- def test_argument_mismatch_5(self, data_1: List[Argument], data_3: List[Argument]):
- arg_1 = data_3[0]
- arg_2 = data_1[0]
- with pytest.raises(
- ValueError,
- match=re.escape(Errors.ARGUMENT_MISMATCH.format(Constants.ANNOTATION, arg_1.name, arg_1.annotation, arg_2.annotation))
- ):
- self.validator.validate(data_3, data_1, "function_name")
-
- def test_argument_mismatch_6(self, data_1: List[Argument], data_3: List[Argument]):
- arg_1 = data_3[0]
- arg_2 = data_1[0]
- with pytest.raises(
- ValueError,
- match=re.escape(Errors.ARGUMENT_MISMATCH.format(Constants.VALUE, arg_1.name, arg_1.value, arg_2.value))
- ):
- self.validator.validate(data_3, data_1, "function_name")
+ def test_argument_mismatch_5(self, data_1: List[Argument], data_3: List[Argument], contract_violation):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_ARGUMENT_NAME] = "arg1"
+ mock_bundle[Errors.KEY_FUNCTION_NAME] = "test_function"
+ mock_contract_violation = VariableContractViolation(Errors.SCOPE_ARGUMENT, Contexts.MODULE_CONTEXT, mock_bundle)
+ with pytest.raises(ValueError,
+ match=re.escape(
+ mock_contract_violation.mismatch_error_handler(
+ data_3[0].annotation,
+ data_1[0].annotation,
+ Errors.ENTITY_MESSAGES,
+ )
+ )):
+ self.validator.validate(data_3, data_1, contract_violation)
+
+ def test_argument_mismatch_6(self, data_1: List[Argument], data_3: List[Argument], contract_violation):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_ARGUMENT_NAME] = "arg1"
+ mock_bundle[Errors.KEY_FUNCTION_NAME] = "test_function"
+ mock_contract_violation = VariableContractViolation(Errors.SCOPE_ARGUMENT, Contexts.MODULE_CONTEXT, mock_bundle)
+ with pytest.raises(ValueError,
+ match=re.escape(
+ mock_contract_violation.mismatch_error_handler(
+ data_3[0].value,
+ data_1[0].value,
+ Errors.ENTITY_MESSAGES,
+ )
+ )):
+ self.validator.validate(data_3, data_1, contract_violation)
- def test_argument_mismatch_7(self, data_1: List[Argument]):
- with pytest.raises(
- ValueError,
- match=re.escape(
- Errors.ELEMENT_MISSING.format(data_1)
- )
- ):
- self.validator.validate(data_1, None, "function_name")
+ def test_argument_mismatch_7(self, data_1: List[Argument], contract_violation):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_ARGUMENTS_1] = data_1
+ mock_bundle[Errors.KEY_FUNCTION_NAME] = "test_function"
+ mock_contract_violation = VariableContractViolation(Errors.SCOPE_ARGUMENT, Contexts.MODULE_CONTEXT, mock_bundle)
+ with pytest.raises(ValueError,
+ match=re.escape(
+ mock_contract_violation.missing_error_handler(
+ Errors.COLLECTIONS_MESSAGES,
+ )
+ )):
+ self.validator.validate(data_1, None, contract_violation)
diff --git a/tests/validators/test_attribute_validator.py b/tests/validators/test_attribute_validator.py
index e8b5770..fa72a94 100644
--- a/tests/validators/test_attribute_validator.py
+++ b/tests/validators/test_attribute_validator.py
@@ -2,16 +2,29 @@
from importspy.models import (
Attribute
)
-from importspy.config import Config
+
+from importspy.validators import VariableValidator
+
from typing import List
-from importspy.validators.attribute_validator import AttributeValidator
-from importspy.errors import Errors
+
import re
-from importspy.constants import Constants
+
+from importspy.constants import (
+ Errors,
+ Contexts
+)
+
+from importspy.violation_systems import (
+ VariableContractViolation,
+ Bundle
+)
+
+from importspy.config import Config
+
class TestAttributeValidator:
- validator = AttributeValidator()
+ validator = VariableValidator()
@pytest.fixture
def data_1(self):
@@ -45,36 +58,57 @@ def data_4(self):
value=4
)]
+ @pytest.fixture
+ def class_type_bundle(self, classbundle) -> Bundle:
+ classbundle[Errors.KEY_ATTRIBUTE_TYPE] = Config.CLASS_TYPE
+ return classbundle
+
+ @pytest.fixture
+ def instance_type_bundle(self, classbundle) -> Bundle:
+ classbundle[Errors.KEY_ATTRIBUTE_TYPE] = Config.INSTANCE_TYPE
+ return classbundle
+
+ @pytest.fixture
+ def class_type_contract(self, class_type_bundle):
+ return VariableContractViolation(Errors.SCOPE_VARIABLE, Contexts.CLASS_CONTEXT, class_type_bundle)
+
+ @pytest.fixture
+ def instance_type_contract(self, instance_type_bundle):
+ return VariableContractViolation(Errors.SCOPE_VARIABLE, Contexts.CLASS_CONTEXT, instance_type_bundle)
+
+
@pytest.fixture
def attribute_value_setter(self, data_3:Attribute):
data_3[0].value = "value"
- def test_attribute_match(self, data_1:List[Attribute], data_4:List[Attribute]):
- assert self.validator.validate(data_1, data_4, "classname")
+ def test_attribute_match(self, data_1:List[Attribute], data_4:List[Attribute], class_type_contract):
+ assert self.validator.validate(data_1, data_4, class_type_contract) is None
@pytest.mark.usefixtures("attribute_value_setter")
- def test_attribute_match_1(self, data_2:List[Attribute], data_3:List[Attribute]):
- assert self.validator.validate(data_2, data_3, "classname")
+ def test_attribute_match_1(self, data_2:List[Attribute], data_3:List[Attribute], instance_type_contract):
+ assert self.validator.validate(data_2, data_3, instance_type_contract) is None
- def test_attribute_mismatch(self, data_2:List[Attribute]):
- assert self.validator.validate(None, data_2, "classname") is None
+ def test_attribute_mismatch(self, data_2:List[Attribute], class_type_contract):
+ assert self.validator.validate(None, data_2, class_type_contract) is None
- def test_attribute_mismatch_1(self, data_3, data_4):
+ def test_attribute_mismatch_1(self, data_3, data_4, class_type_contract):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_ATTRIBUTE_NAME] = "class_attribute"
+ mock_bundle[Errors.KEY_ATTRIBUTE_TYPE] = "class"
+ mock_bundle[Errors.KEY_CLASS_NAME] = "TestClass"
+ mock_contract_violation = VariableContractViolation(Errors.SCOPE_VARIABLE, Contexts.CLASS_CONTEXT, mock_bundle)
with pytest.raises(ValueError,
match=re.escape(
- Errors.CLASS_ATTRIBUTE_MISSING.format(
- Config.CLASS_TYPE,
- f"{data_4[0].name}={data_4[0].value}",
- "classname"
- )
- )):
- self.validator.validate(data_4, data_3, "classname")
+ mock_contract_violation.missing_error_handler(
+ Errors.ENTITY_MESSAGES
+ ))):
+ self.validator.validate(data_4, data_3, class_type_contract)
- def test_attribute_mismatch_2(self):
- assert self.validator.validate(None, None, "classname") is None
+ def test_attribute_mismatch_2(self, instance_type_contract):
+ assert self.validator.validate(None, None, instance_type_contract) is None
- def test_attribute_mismatch_3(self, data_2:List[Attribute]):
- assert self.validator.validate(None, data_2, "classname") is None
+ def test_attribute_mismatch_3(self, data_2:List[Attribute], instance_type_contract):
+ assert self.validator.validate(None, data_2, instance_type_contract) is None
@pytest.fixture
def attribute_annotation_setter(self, data_3:Attribute):
@@ -82,23 +116,34 @@ def attribute_annotation_setter(self, data_3:Attribute):
@pytest.mark.usefixtures("attribute_value_setter")
@pytest.mark.usefixtures("attribute_annotation_setter")
- def test_attribute_match_3(self, data_2:List[Attribute], data_3:List[Attribute]):
- assert self.validator.validate(data_2, data_3, "classname")
+ def test_attribute_match_3(self, data_2:List[Attribute], data_3:List[Attribute], instance_type_contract):
+ assert self.validator.validate(data_2, data_3, instance_type_contract) is None
@pytest.mark.usefixtures("attribute_value_setter")
@pytest.mark.usefixtures("attribute_annotation_setter")
- def test_attribute_mismatch_5(self, data_2:List[Attribute], data_3:List[Attribute]):
- attr_1 = data_3[0]
- attr_2 = data_2[0]
+ def test_attribute_mismatch_5(self, data_2:List[Attribute], data_3:List[Attribute], instance_type_contract):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_ATTRIBUTE_NAME] = "instance_attribute"
+ mock_bundle[Errors.KEY_ATTRIBUTE_TYPE] = "instance"
+ mock_bundle[Errors.KEY_CLASS_NAME] = "TestClass"
+ mock_contract_violation = VariableContractViolation(Errors.SCOPE_VARIABLE, Contexts.CLASS_CONTEXT, mock_bundle)
with pytest.raises(
ValueError,
- match=re.escape(Errors.CLASS_ATTRIBUTE_MISMATCH.format(Constants.ANNOTATION, attr_1.type, attr_1.name, attr_1.annotation, attr_2.annotation))
+ match=re.escape(
+ mock_contract_violation.mismatch_error_handler(data_3[0].annotation, data_2[0].annotation, Errors.ENTITY_MESSAGES)
+ )
):
- self.validator.validate(data_3, data_2, "classname")
+ self.validator.validate(data_3, data_2, instance_type_contract)
- def test_attribute_mismatch_6(self, data_3:List[Attribute]):
+ def test_attribute_mismatch_6(self, data_3:List[Attribute], instance_type_contract):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_ATTRIBUTES_1] = data_3
+ mock_bundle[Errors.KEY_CLASS_NAME] = "TestClass"
+ mock_contract_violation = VariableContractViolation(Errors.SCOPE_VARIABLE, Contexts.CLASS_CONTEXT, mock_bundle)
with pytest.raises(
ValueError,
- match=re.escape(Errors.ELEMENT_MISSING.format(data_3))
+ match=re.escape(
+ mock_contract_violation.missing_error_handler(Errors.COLLECTIONS_MESSAGES)
+ )
):
- self.validator.validate(data_3, None, "classname")
\ No newline at end of file
+ self.validator.validate(data_3, None, instance_type_contract)
\ No newline at end of file
diff --git a/tests/validators/test_function_validator.py b/tests/validators/test_function_validator.py
index 77f7180..d8efe5a 100644
--- a/tests/validators/test_function_validator.py
+++ b/tests/validators/test_function_validator.py
@@ -1,10 +1,18 @@
import pytest
-from importspy.models import (
- Function
-)
+from importspy.models import Function
from typing import List
-from importspy.validators.function_validator import FunctionValidator
-from importspy.errors import Errors
+from importspy.validators import FunctionValidator
+
+from importspy.violation_systems import (
+ FunctionContractViolation,
+ Bundle
+)
+
+from importspy.constants import (
+ Errors,
+ Contexts
+)
+
import re
class TestFunctionValidator:
@@ -38,49 +46,56 @@ def data_4(self):
return_annotation="str"
)]
+ @pytest.fixture
+ def function_contract(self, functionbundle:Bundle):
+ return FunctionContractViolation(Contexts.MODULE_CONTEXT, functionbundle)
+
@pytest.fixture
def function_return_annotation_setter(self, data_3:Function):
data_3[0].return_annotation = "str"
- def test_function_match(self, data_1:List[Function], data_4:List[Function]):
- assert self.validator.validate(data_1, data_4, "classname")
+ def test_function_match(self, data_1:List[Function], data_4:List[Function], function_contract: FunctionContractViolation):
+ assert self.validator.validate(data_1, data_4, function_contract) is None
- def test_function_mismatch(self, data_2:List[Function]):
- assert self.validator.validate(None, data_2, "classname") is None
+ def test_function_mismatch(self, data_2:List[Function], function_contract):
+ assert self.validator.validate(None, data_2, function_contract) is None
@pytest.mark.usefixtures("function_return_annotation_setter")
- def test_function_mismatch_1(self, data_2:List[Function], data_3:List[Function]):
+ def test_function_mismatch_1(self, data_2:List[Function], data_3:List[Function], function_contract: FunctionContractViolation):
with pytest.raises(
ValueError,
match=re.escape(
- Errors.FUNCTIONS_MISSING.format("function", data_2[0].name)
+ Errors.FUNCTIONS_MISSING.format("function", data_2[0].name, function_contract)
)
):
self.validator.validate(data_2, data_3)
- def test_function_mismatch_1(self, data_3:List[Function], data_4:List[Function]):
+ def test_function_mismatch_1(self, data_3:List[Function], data_4:List[Function], function_contract:FunctionContractViolation):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_FILE_NAME] = "testmodule.py"
+ mock_bundle[Errors.KEY_FUNCTION_NAME] = "function"
+ mock_contract_violation = FunctionContractViolation(Contexts.MODULE_CONTEXT, mock_bundle)
with pytest.raises(
ValueError,
match=re.escape(
- Errors.FUNCTION_RETURN_ANNOTATION_MISMATCH.format(
- "method in class classname",
- data_4[0].name,
- data_4[0].return_annotation,
- data_3[0].return_annotation
+ mock_contract_violation.mismatch_error_handler(data_4[0].return_annotation, data_3[0].return_annotation, Errors.ENTITY_MESSAGES)
)
- )
- ):
- self.validator.validate(data_4, data_3, "classname")
+ ):
+ self.validator.validate(data_4, data_3, function_contract)
- def test_function_mismatch_2(self):
- assert self.validator.validate(None, None, "classname") is None
+ def test_function_mismatch_2(self, function_contract:FunctionContractViolation):
+ assert self.validator.validate(None, None, function_contract) is None
- def test_function_mismatch_3(self, data_2:List[Function]):
- assert self.validator.validate(None, data_2, "classname") is None
+ def test_function_mismatch_3(self, data_2:List[Function], function_contract:FunctionContractViolation):
+ assert self.validator.validate(None, data_2, function_contract) is None
- def test_function_mismatch_5(self, data_3:List[Function]):
+ def test_function_mismatch_5(self, data_3:List[Function], function_contract:FunctionContractViolation):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_FILE_NAME] = "testmodule.py"
+ mock_bundle[Errors.KEY_FUNCTIONS_1] = data_3
+ mock_contract_violation = FunctionContractViolation(Contexts.MODULE_CONTEXT, mock_bundle)
with pytest.raises(
ValueError,
- match=re.escape(Errors.ELEMENT_MISSING.format(data_3))
+ match=re.escape(mock_contract_violation.missing_error_handler(Errors.COLLECTIONS_MESSAGES))
):
- self.validator.validate(data_3, None, "classname")
\ No newline at end of file
+ self.validator.validate(data_3, None, function_contract)
\ No newline at end of file
diff --git a/tests/validators/test_method_validator.py b/tests/validators/test_method_validator.py
new file mode 100644
index 0000000..e2b484d
--- /dev/null
+++ b/tests/validators/test_method_validator.py
@@ -0,0 +1,103 @@
+import pytest
+from importspy.models import Function
+from typing import List
+from importspy.validators import FunctionValidator
+
+from importspy.violation_systems import (
+ FunctionContractViolation,
+ Bundle
+)
+
+from importspy.constants import (
+ Errors,
+ Contexts
+)
+
+import re
+
+class TestFunctionValidator:
+
+ validator = FunctionValidator()
+
+ @pytest.fixture
+ def data_1(self):
+ return [Function(
+ name="method",
+ )]
+
+ @pytest.fixture
+ def data_2(self):
+ return [Function(
+ name="getname",
+ return_annotation="str"
+ )]
+
+ @pytest.fixture
+ def data_3(self):
+ return [Function(
+ name="method",
+ return_annotation="int"
+ )]
+
+ @pytest.fixture
+ def data_4(self):
+ return [Function(
+ name="method",
+ return_annotation="str"
+ )]
+
+ @pytest.fixture
+ def function_contract(self, methodbundle:Bundle):
+ return FunctionContractViolation(Contexts.CLASS_CONTEXT, methodbundle)
+
+ @pytest.fixture
+ def function_return_annotation_setter(self, data_3:Function):
+ data_3[0].return_annotation = "str"
+
+ def test_function_match(self, data_1:List[Function], data_4:List[Function], function_contract: FunctionContractViolation):
+ assert self.validator.validate(data_1, data_4, function_contract) is None
+
+ def test_function_mismatch(self, data_2:List[Function], function_contract):
+ assert self.validator.validate(None, data_2, function_contract) is None
+
+ @pytest.mark.usefixtures("function_return_annotation_setter")
+ def test_function_mismatch_1(self, data_2:List[Function], data_3:List[Function], function_contract: FunctionContractViolation):
+ with pytest.raises(
+ ValueError,
+ match=re.escape(
+ Errors.FUNCTIONS_MISSING.format("function", data_2[0].name, function_contract)
+ )
+ ):
+ self.validator.validate(data_2, data_3)
+
+ def test_function_mismatch_1(self, data_3:List[Function], data_4:List[Function], function_contract:FunctionContractViolation):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_FILE_NAME] = "testmodule.py"
+ mock_bundle[Errors.KEY_CLASS_NAME] = "TestClass"
+ mock_bundle[Errors.KEY_METHOD_NAME] = "method"
+ mock_contract_violation = FunctionContractViolation(Contexts.CLASS_CONTEXT, mock_bundle)
+ with pytest.raises(
+ ValueError,
+ match=re.escape(
+ mock_contract_violation.mismatch_error_handler(data_4[0].return_annotation, data_3[0].return_annotation, Errors.ENTITY_MESSAGES)
+ )
+ ):
+ self.validator.validate(data_4, data_3, function_contract)
+
+ def test_function_mismatch_2(self, function_contract:FunctionContractViolation):
+ assert self.validator.validate(None, None, function_contract) is None
+
+ def test_function_mismatch_3(self, data_2:List[Function], function_contract:FunctionContractViolation):
+ assert self.validator.validate(None, data_2, function_contract) is None
+
+ def test_function_mismatch_5(self, data_3:List[Function], function_contract:FunctionContractViolation):
+ mock_bundle = Bundle()
+ mock_bundle[Errors.KEY_FILE_NAME] = "testmodule.py"
+ mock_bundle[Errors.KEY_CLASS_NAME] = "TestClass"
+ mock_bundle[Errors.KEY_METHODS_1] = data_3
+ mock_contract_violation = FunctionContractViolation(Contexts.CLASS_CONTEXT, mock_bundle)
+ with pytest.raises(
+ ValueError,
+ match=re.escape(mock_contract_violation.missing_error_handler(Errors.COLLECTIONS_MESSAGES))
+ ):
+ self.validator.validate(data_3, None, function_contract)
\ No newline at end of file
diff --git a/tests/validators/test_module_validator.py b/tests/validators/test_module_validator.py
index b35ed04..8db13dc 100644
--- a/tests/validators/test_module_validator.py
+++ b/tests/validators/test_module_validator.py
@@ -3,10 +3,15 @@
Module,
Variable
)
-from importspy.validators.module_validator import ModuleValidator
-from importspy.errors import Errors
+from importspy.validators import ModuleValidator
+from importspy.violation_systems import (
+ Bundle,
+ ModuleContractViolation
+)
+from importspy.constants import Errors
import re
from typing import List
+from importspy.constants import Contexts
class TestModuleValidator:
@@ -24,6 +29,17 @@ def data_2(self):
filename="package.py",
version="0.1.0"
)]
+
+ @pytest.fixture
+ def data_3(self):
+ return [Module(
+ filename="package.py",
+ version="0.2.0"
+ )]
+
+ @pytest.fixture
+ def module_contract(self, methodbundle:Bundle) -> ModuleContractViolation:
+ return ModuleContractViolation(Contexts.RUNTIME_CONTEXT, methodbundle)
@pytest.fixture
def filename_setter(self, data_1:List[Module]):
@@ -66,43 +82,35 @@ def variables_msg_setter(self, data_3:List[Module]):
@pytest.mark.usefixtures("filename_setter")
@pytest.mark.usefixtures("version_unsetter")
- def test_module_match(self, data_1:List[Module], data_2:List[Module]):
- assert self.validator.validate(data_1, data_2[0]) is None
+ def test_module_match(self, data_1:List[Module], data_2:List[Module], module_contract:ModuleContractViolation):
+ assert self.validator.validate(data_1, data_2[0], module_contract) is None
@pytest.mark.usefixtures("filename_setter")
@pytest.mark.usefixtures("variables_setter")
@pytest.mark.usefixtures("variables_msg_setter")
- def test_module_match_1(self, data_2:List[Module], data_3:List[Module]):
- assert self.validator.validate(data_2, data_3[0]) is None
+ def test_module_match_1(self, data_2:List[Module], data_3:List[Module], module_contract:ModuleContractViolation):
+ assert self.validator.validate(data_2, data_3[0], module_contract) is None
- def test_module_match_2(self, data_2:List[Module], data_3:List[Module]):
- assert self.validator.validate(data_2, data_3[0]) is None
+ def test_module_match_2(self, data_2:List[Module], data_3:List[Module], module_contract:ModuleContractViolation):
+ assert self.validator.validate(data_2, data_3[0], module_contract) is None
- def test_module_mismatch(self, data_2:List[Module]):
- assert self.validator.validate(None, data_2[0]) is None
-
- def test_module_mismatch_2(self, data_2:List[Module], data_3:List[Module]):
- with pytest.raises(
- ValueError,
- match=re.escape(Errors.ELEMENT_MISSING.format(data_3[0].variables))
- ):
- self.validator.validate(data_3, data_2[0])
+ def test_module_mismatch(self, data_2:List[Module], module_contract:ModuleContractViolation):
+ assert self.validator.validate(None, data_2[0], module_contract) is None
@pytest.mark.usefixtures("version_unsetter")
- def test_module_mismatch_3(self, data_1:List[Module], data_2:List[Module]):
+ def test_module_mismatch_1(self, data_1:List[Module], data_2:List[Module], module_contract:ModuleContractViolation):
with pytest.raises(ValueError,
match=re.escape(
- Errors.FILENAME_MISMATCH.format(data_1[0].filename,
- data_2[0].filename)
- )
+ module_contract.mismatch_error_handler(data_1[0].filename, data_2[0].filename, Errors.ENTITY_MESSAGES)
+ )
):
- self.validator.validate(data_1, data_2[0])
-
- def test_module_mismatch_4(self, data_1:List[Module], data_2:List[Module]):
+ self.validator.validate(data_1, data_2[0], module_contract)
+
+ @pytest.mark.usefixtures("version_unsetter")
+ def test_module_mismatch_1(self, data_3:List[Module], data_2:List[Module], module_contract:ModuleContractViolation):
with pytest.raises(ValueError,
match=re.escape(
- Errors.FILENAME_MISMATCH.format(data_1[0].filename,
- data_2[0].filename)
- )
+ module_contract.mismatch_error_handler(data_3[0].version, data_2[0].version, Errors.ENTITY_MESSAGES)
+ )
):
- self.validator.validate(data_1, data_2[0])
\ No newline at end of file
+ self.validator.validate(data_3, data_2[0], module_contract)
\ No newline at end of file
diff --git a/tests/validators/test_python_validator.py b/tests/validators/test_python_validator.py
index 410f788..8184f1c 100644
--- a/tests/validators/test_python_validator.py
+++ b/tests/validators/test_python_validator.py
@@ -3,11 +3,20 @@
Python
)
from importspy.config import Config
-from importspy.errors import Errors
-from importspy.validators.python_validator import PythonValidator
+from importspy.constants import (
+ Errors,
+ Contexts
+)
+
+from importspy.validators import PythonValidator
from typing import List
import re
+from importspy.violation_systems import (
+ PythonContractViolation,
+ Bundle
+)
+
class TestPythonValidator:
@@ -40,6 +49,20 @@ def data_4(self):
modules=[]
)]
+ @pytest.fixture
+ def pythonbundle(self) -> Bundle:
+ bundle = Bundle()
+ bundle[Errors.KEY_PYTHON_1] = Python(
+ version=Config.PYTHON_VERSION_3_13,
+ interpreter=Config.INTERPRETER_IRON_PYTHON,
+ modules=[]
+ )
+ return bundle
+
+ @pytest.fixture
+ def python_contract(self, pythonbundle: Bundle) -> PythonContractViolation:
+ return PythonContractViolation(context=Contexts.RUNTIME_CONTEXT, bundle=pythonbundle)
+
@pytest.fixture
def python_version_setter(self, data_2:List[Python]):
data_2[0].version = "12.0.1"
@@ -48,34 +71,61 @@ def python_version_setter(self, data_2:List[Python]):
def python_interpreter_setter(self, data_3:List[Python]):
data_3[0].interpreter = Config.INTERPRETER_GRAALPYTHON
- @pytest.mark.usefixtures("python_version_setter")
- def test_python_match(self, data_1:List[Python], data_2:List[Python]):
- assert self.validator.validate(data_1, data_2) is None
-
- def test_python_match_1(self, data_3:List[Python], data_4:List[Python]):
- assert self.validator.validate(data_3, data_4) is None
+ def test_python_match(self, data_3:List[Python], data_4:List[Python], python_contract):
+ assert self.validator.validate(data_3, data_4, python_contract) == data_3[0].modules
- def test_python_mismatch(self, data_2:List[Python]):
- assert self.validator.validate(None, data_2) is None
+ def test_python_mismatch(self, data_2:List[Python], python_contract):
+ assert self.validator.validate(None, data_2, python_contract) is None
@pytest.mark.usefixtures("python_interpreter_setter")
- def test_python_mismatch_1(self, data_3, data_4):
- assert self.validator.validate(data_4, data_3) is None
+ def test_python_mismatch_1(self, data_3, data_4, python_contract):
+ mock_contract = PythonContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ Bundle(
+ state= { Errors.KEY_PYTHONS_1 : data_4 }
+ )
+ )
+ with pytest.raises(ValueError,
+ match=re.escape(
+ mock_contract.missing_error_handler(Errors.COLLECTIONS_MESSAGES)
+ )
+ ):
+ assert self.validator.validate(data_4, data_3, python_contract)
+
+ def test_python_mismatch_2(self, python_contract):
+ assert self.validator.validate(None, None, python_contract) is None
- def test_python_mismatch_2(self):
- assert self.validator.validate(None, None) is None
+ def test_python_mismatch_3(self, python_contract):
+ assert self.validator.validate(None, None, python_contract) is None
- def test_python_mismatch_3(self):
- assert self.validator.validate(None, None) is None
+ def test_python_mismatch_4(self, data_2:List[Python], python_contract):
+ assert self.validator.validate(None, data_2, python_contract) is None
- def test_python_mismatch_4(self, data_2:List[Python]):
- assert self.validator.validate(None, data_2) is None
+ def test_python_mismatch_5(self, data_3:List[Python], python_contract):
+ mock_contract = PythonContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ Bundle(
+ state= { Errors.KEY_PYTHONS_1 : data_3 }
+ )
+ )
+ with pytest.raises(ValueError,
+ match=re.escape(
+ mock_contract.missing_error_handler(Errors.COLLECTIONS_MESSAGES)
+ )
+ ):
+ self.validator.validate(data_3, None, python_contract)
- def test_python_mismatch_5(self, data_3:List[Python]):
- with pytest.raises(
- ValueError,
- match=re.escape(
- Errors.ELEMENT_MISSING.format(data_3)
- )
+ @pytest.mark.usefixtures("python_version_setter")
+ def test_python_mismatch_6(self, data_1:List[Python], data_2:List[Python], python_contract:PythonContractViolation):
+ mock_contract = PythonContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ Bundle(
+ state= { Errors.KEY_PYTHONS_1 : data_1 }
+ )
+ )
+ with pytest.raises(ValueError,
+ match=re.escape(
+ mock_contract.missing_error_handler(Errors.COLLECTIONS_MESSAGES)
+ )
):
- self.validator.validate(data_3, None)
\ No newline at end of file
+ self.validator.validate(data_1, data_2, python_contract)
\ No newline at end of file
diff --git a/tests/validators/test_runtime_validator.py b/tests/validators/test_runtime_validator.py
index 76a1df1..ab647c9 100644
--- a/tests/validators/test_runtime_validator.py
+++ b/tests/validators/test_runtime_validator.py
@@ -3,11 +3,20 @@
Runtime
)
from importspy.config import Config
-from importspy.constants import Constants
-from importspy.validators.runtime_validator import RuntimeValidator
-from importspy.errors import Errors
+
+from importspy.validators import RuntimeValidator
+from importspy.constants import (
+ Errors,
+ Constants,
+ Contexts
+)
+
import re
from typing import List
+from importspy.violation_systems import (
+ Bundle,
+ RuntimeContractViolation
+)
class TestRuntimeValidator:
@@ -27,21 +36,33 @@ def data_2(self):
systems=[]
)]
+ @pytest.fixture
+ def runtimebundle(self) -> Bundle:
+ bundle = Bundle()
+ return bundle
+
+ @pytest.fixture
+ def runtime_contract(self, runtimebundle: Bundle) -> RuntimeContractViolation:
+ return RuntimeContractViolation(context=Contexts.RUNTIME_CONTEXT, bundle=runtimebundle)
+
@pytest.fixture
def arch_arm_setter(self, data_1:List[Runtime]):
data_1[0].arch = Config.ARCH_ARM
- def test_runtime_arch_match(self, data_1:List[Runtime], data_2:List[Runtime]):
- assert self.validator.validate(data_1, data_2) is None
-
- def test_runtime_arch_invalid(self):
- with pytest.raises(ValueError,
- match=re.escape(Errors.INVALID_ARCHITECTURE.format("A invalid value", Constants.KNOWN_ARCHITECTURES))):
- Runtime(
- arch="A invalid value",
- systems=[]
- )
+ def test_runtime_arch_match(self, data_1:List[Runtime], data_2:List[Runtime], runtime_contract):
+ assert self.validator.validate(data_1, data_2, runtime_contract) == data_1[0]
@pytest.mark.usefixtures("arch_arm_setter")
- def test_runtime_arch_mismatch(self, data_1:List[Runtime], data_2:List[Runtime]):
- assert self.validator.validate(data_1, data_2) is None
\ No newline at end of file
+ def test_runtime_arch_mismatch(self, data_1:List[Runtime], data_2:List[Runtime], runtime_contract):
+ mock_contract = RuntimeContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ Bundle(
+ state= { Errors.KEY_RUNTIMES_1 : data_1 }
+ )
+ )
+ with pytest.raises(ValueError,
+ match=re.escape(
+ mock_contract.missing_error_handler(Errors.COLLECTIONS_MESSAGES)
+ )
+ ):
+ assert self.validator.validate(data_1, data_2, runtime_contract) is None
\ No newline at end of file
diff --git a/tests/validators/test_system_validator.py b/tests/validators/test_system_validator.py
index feb79f3..fa8effa 100644
--- a/tests/validators/test_system_validator.py
+++ b/tests/validators/test_system_validator.py
@@ -1,14 +1,27 @@
import pytest
from importspy.models import (
- System
+ System,
+ Environment,
+ Variable
)
from importspy.config import Config
-from importspy.constants import Constants
-from importspy.validators.system_validator import SystemValidator
-from importspy.errors import Errors
+
+from importspy.constants import (
+ Contexts,
+ Constants
+)
+
+from importspy.validators import SystemValidator
+from importspy.constants import Errors
import re
from typing import List
+from importspy.violation_systems import (
+ Bundle,
+ SystemContractViolation,
+ VariableContractViolation
+)
+
class TestSystemValidator:
validator = SystemValidator()
@@ -16,60 +29,97 @@ class TestSystemValidator:
@pytest.fixture
def data_1(self):
return [System(
- os=Config.OS_LINUX,
- envs={"CI": "true"},
+ os=Constants.SupportedOS.OS_LINUX,
+ environment=Environment(
+ variables=[Variable(
+ name="CI",
+ value="true"
+ )]
+ ),
pythons=[]
)]
@pytest.fixture
def data_2(self):
return [System(
- os=Config.OS_LINUX,
+ os=Constants.SupportedOS.OS_LINUX,
pythons=[]
)]
+ @pytest.fixture
+ def systembundle(self) -> Bundle:
+ bundle = Bundle()
+ return bundle
+
+ @pytest.fixture
+ def variable_contract(self, systembundle) -> VariableContractViolation:
+ return VariableContractViolation(Errors.SCOPE_VARIABLE, Contexts.ENVIRONMENT_CONTEXT, systembundle)
+
+ @pytest.fixture
+ def system_contract(self, systembundle) -> SystemContractViolation:
+ return SystemContractViolation(Contexts.RUNTIME_CONTEXT, systembundle)
+
@pytest.fixture
def os_windows_setter(self, data_1:List[System]):
- data_1[0].os = Config.OS_WINDOWS
+ data_1[0].os = Constants.SupportedOS.OS_WINDOWS
@pytest.fixture
def envs_setter(self, data_2):
- data_2[0].envs = {"CI": "true"}
+ data_2[0].environment = Environment(variables=[(Variable(name="CI", value="true"))])
@pytest.mark.usefixtures("envs_setter")
- def test_system_os_match(self, data_1:List[System], data_2:List[System]):
- assert self.validator.validate(data_1, data_2) is None
+ def test_system_os_match(self, data_1:List[System], data_2:List[System], system_contract: SystemContractViolation):
+ assert self.validator.validate(data_1, data_2, system_contract) == data_1[0].pythons
- def test_system_os_match_2(self, data_1:List[System], data_2:List[System]):
- assert self.validator.validate(data_2, data_1) is None
-
- def test_system_os_invalid(self):
- with pytest.raises(ValueError,
- match=re.escape(Errors.INVALID_OS.format(Constants.SUPPORTED_OS, "A invalid value"))):
- System(
- os="A invalid value",
- pythons=[]
- )
+ def test_system_os_match_2(self, data_1:List[System], data_2:List[System], system_contract: SystemContractViolation):
+ assert self.validator.validate(data_2, data_1, system_contract) == data_2[0].pythons
- def test_system_os_mismatch(self, data_1:List[System], data_2:List[System]):
- with pytest.raises(
- ValueError,
- match=re.escape(
- Errors.ENV_VAR_MISSING.format(data_1[0].envs)
+ def test_system_os_mismatch(self, data_1:List[System], data_2:List[System], system_contract: SystemContractViolation):
+ mock_contract = VariableContractViolation(
+ Errors.SCOPE_VARIABLE,
+ Contexts.ENVIRONMENT_CONTEXT,
+ Bundle(
+ state= {
+ Errors.KEY_ENVIRONMENT_1 : data_1[0].environment,
+ Errors.KEY_ENVIRONMENT_VARIABLE_NAME: "CI"
+ }
)
+ )
+ with pytest.raises(ValueError,
+ match=re.escape(
+ mock_contract.missing_error_handler(Errors.COLLECTIONS_MESSAGES)
+ )
):
- self.validator.validate(data_1, data_2)
+ self.validator.validate(data_1, data_2, system_contract)
@pytest.mark.usefixtures("os_windows_setter")
- def test_system_os_mismatch_1(self, data_1:List[System], data_2:List[System]):
- assert self.validator.validate(data_1, data_2) is None
+ def test_system_os_mismatch_1(self, data_1:List[System], data_2:List[System], system_contract: SystemContractViolation):
+ bundle: Bundle = Bundle()
+ bundle[Errors.KEY_SYSTEMS_1] = data_1
+ mock_contract = SystemContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ )
+ with pytest.raises(ValueError,
+ match=re.escape(
+ mock_contract.missing_error_handler(Errors.COLLECTIONS_MESSAGES)
+ )
+ ):
+ self.validator.validate(data_1, data_2, system_contract)
- def test_system_mismatch(self, data_2:List[System]):
- assert self.validator.validate(None, data_2) is None
+ def test_system_mismatch(self, data_2:List[System], system_contract: SystemContractViolation):
+ assert self.validator.validate(None, data_2, system_contract) is None
- def test_system_mismatch_1(self, data_2:List[System]):
- with pytest.raises(
- ValueError,
- match=re.escape(Errors.ELEMENT_MISSING.format(data_2))
+ def test_system_mismatch_1(self, data_2:List[System], system_contract: SystemContractViolation):
+ bundle: Bundle = Bundle()
+ bundle[Errors.KEY_SYSTEMS_1] = data_2
+ mock_contract = SystemContractViolation(
+ Contexts.RUNTIME_CONTEXT,
+ bundle
+ )
+ with pytest.raises(ValueError,
+ match=re.escape(
+ mock_contract.missing_error_handler(Errors.COLLECTIONS_MESSAGES)
+ )
):
- self.validator.validate(data_2, None)
\ No newline at end of file
+ self.validator.validate(data_2, None, system_contract)
\ No newline at end of file