Skip to content

Commit 3f59473

Browse files
committed
refactor: restructure docs, improve logging, and fix type annotations
1 parent ab2922f commit 3f59473

File tree

5 files changed

+120
-146
lines changed

5 files changed

+120
-146
lines changed

docs/errors/contract-violations.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,17 @@ Each message consists of:
7373

7474
Below is a comprehensive list of all possible error messages emitted by ImportSpy:
7575

76-
--8<-- "errors/error_table.md"
76+
| Category | Context | Error Message |
77+
|------------|---------------|---------------|
78+
| `missing` | `runtime` | The runtime `CPython 3.12` is declared but missing. Ensure it is properly defined and implemented. |
79+
| | `environment` | The environment variable `DEBUG` is declared but missing. Ensure it is properly defined and implemented. |
80+
| | `module` | The variable `plugin_name` in module `extension.py` is declared but missing. Ensure it is properly defined and implemented. |
81+
| | `class` | The method `run` in class `Plugin` is declared but missing. Ensure it is properly defined and implemented. |
82+
| `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. |
83+
| | `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. |
84+
| | `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. |
85+
| `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. |
86+
7787

7888
---
7989

docs/errors/error-table.md

Lines changed: 0 additions & 10 deletions
This file was deleted.

docs/index.md

Lines changed: 50 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,47 @@
11
# ImportSpy
22

3-
**Context-aware import validation for Python modules**
3+
**Contextaware import validation for Python modules**
44

5-
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.
5+
ImportSpy is an open‑source Python library that brings structural and environmental awareness to Python’s import system.
6+
It introduces a new concept: the **import contract** — a versioned, declarative `.yml` file that describes exactly how and where a module is allowed to be imported.
67

7-
It brings **modularity**, **predictability**, and **security** to Python ecosystems.
8+
This enables predictable, secure, and modular Python codebases, especially in complex or regulated environments.
89

910
---
1011

1112
## What is an Import Contract?
1213

13-
An import contract is a `.yml` file that defines:
14+
An import contract defines what a module expects from:
1415

15-
- The expected **structure** of a module: functions, classes, arguments, annotations, variables
16-
- The allowed **environments**: OS, architecture, Python version, interpreter
17-
- Optional conditions on runtime **environment variables** or **superclasses**
16+
- The importing environment: operating system, CPU architecture, Python version, interpreter type
17+
- Its internal structure: functions, classes, methods, arguments, annotations, variables
18+
- Optional runtime conditions: environment variables, base classes, or structural patterns
1819

19-
If these conditions are not met, ImportSpy can stop the import and raise a detailed, structured error — before any runtime failure can occur.
20+
If the context does not meet the declared conditions, ImportSpy blocks the import and raises a structured, human-readable error — before any runtime logic is executed.
2021

2122
---
2223

2324
## Key Features
2425

25-
- YAML-based **import contracts**
26-
- Embedded and CLI-based **validation**
27-
- Structural validation of **variables**, **functions**, **classes**
28-
- Runtime checks for **OS**, **architecture**, **Python version**, **interpreter**
29-
- Contract-driven plugin validation for **secure extensibility**
30-
- Clear, explainable **error reporting** on mismatch, missing, or invalid usage
31-
- Fully integrable in **CI/CD pipelines**
26+
- Declarative, YAML-based import contracts
27+
- Embedded and CLI validation modes
28+
- Structural enforcement: functions, classes, variables, method signatures
29+
- Runtime checks: OS, architecture, Python version, interpreter
30+
- Contract-driven plugin validation and safe extensibility
31+
- CI/CD‑friendly error reporting
32+
- Seamless integration with DevSecOps pipelines
3233

3334
---
3435

3536
## Use Cases
3637

37-
ImportSpy is built for teams that need:
38+
ImportSpy is designed for:
3839

39-
- **Plugin-based architectures** with strict interface enforcement
40-
- **Runtime protection** against incompatible environments
41-
- Early validation in **DevSecOps** or **regulatory** pipelines
42-
- Defensive boundaries between **internal components**
43-
- **Automated structure verification** during deployment
40+
- Plugin frameworks with strict interface enforcement
41+
- Runtime protection against unsupported environments
42+
- Early validation in CI/CD and regulated deployments
43+
- Defensive boundaries between internal components
44+
- Automated structural checks during deployment
4445

4546
---
4647

@@ -65,44 +66,37 @@ importspy src/mymodule.py -s contracts/spymodel.yml --log-level DEBUG
6566

6667
## Project Structure
6768

68-
ImportSpy is built around 3 key components:
69+
ImportSpy is built around:
6970

70-
- `SpyModel`: represents the structural and runtime definition of a module
71-
- `Spy`: the validation engine that compares real vs expected modules
72-
- `Violation System`: formal system for raising errors with human-readable messages
71+
- `SpyModel`: defines expected structure and runtime environment
72+
- `Spy`: the engine that validates real vs declared conditions
73+
- Violation system: structured, humanreadable error reporting
7374

7475
---
7576

7677
## Documentation Overview
7778

78-
### 👣 Get Started
79-
80-
- [Quickstart](intro/quickstart.md)
81-
- [Install](intro/install.md)
79+
### Get Started
80+
- [Quickstart](intro/quickstart.md)
81+
- [Installation](intro/install.md)
8282
- [Overview](intro/overview.md)
8383

84-
### ⚙️ Modes of Operation
85-
86-
- [Embedded Mode](modes/embedded.md)
84+
### Modes of Operation
85+
- [Embedded Mode](modes/embedded.md)
8786
- [CLI Mode](modes/cli.md)
8887

89-
### 📄 Import Contracts
90-
91-
- [Contract Syntax](contracts/syntax.md)
88+
### Import Contracts
89+
- [Contract Syntax](contracts/syntax.md)
9290
- [SpyModel Specification](advanced/spymodel.md)
9391

94-
### 🧠 Validation Engine
95-
96-
- [Violation System](advanced/violations.md)
92+
### Validation Engine
93+
- [Violation System](advanced/violations.md)
9794
- [Contract Violations](errors/contract-violations.md)
98-
- [Error Table](errors/error-table.md)
99-
100-
### 📦 Use Cases
10195

96+
### Use Cases
10297
- [Plugin-based Architectures](use_cases/index.md)
10398

104-
### 📘 API Reference
105-
99+
### API Reference
106100
- [API Docs](api-reference.md)
107101

108102
---
@@ -115,25 +109,26 @@ ImportSpy is built around 3 key components:
115109

116110
## Why ImportSpy?
117111

118-
Python’s import system is powerful, but not context-aware. ImportSpy solves this by adding a **layer of structural governance** and **runtime filtering**.
112+
Python’s import mechanism is flexible, but not context-aware.
113+
ImportSpy adds a layer of governance and runtime validation, making your code more robust and secure.
119114

120-
This makes it ideal for:
115+
It’s ideal for:
121116

122-
- Plugin systems
123-
- Isolated runtimes
124-
- Package compliance
125-
- Security-aware applications
126-
- CI enforcement of expected module interfaces
117+
- Securing plugin boundaries
118+
- Enforcing internal interfaces
119+
- Preventing unsupported imports
120+
- CI/CD enforcement of import assumptions
121+
- Runtime compatibility in multi-environment systems
127122

128123
---
129124

130-
## Sponsorship & Community
125+
## Support and Community
131126

132-
If ImportSpy is useful in your infrastructure, help us grow by:
127+
If ImportSpy is useful in your infrastructure, consider:
133128

134-
- [Starring the project on GitHub](https://github.com/your-org/importspy)
135-
- [Becoming a GitHub Sponsor](https://github.com/sponsors/your-org)
129+
- [Starring the project on GitHub](https://github.com/atellaluca/ImportSpy)
130+
- [Becoming a GitHub Sponsor](https://github.com/sponsors/atellaluca)
136131

137132
---
138133

139-
> ImportSpy is more than a validator — it's a contract of trust between Python modules.
134+
> ImportSpy is more than a validator — its a contract of trust between Python modules.

src/importspy/log_manager.py

Lines changed: 54 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,41 @@
1-
"""
2-
importspy.log_manager
3-
======================
1+
"""importspy.log_manager
2+
3+
Centralized logging system for ImportSpy.
44
5-
This module defines ImportSpy’s centralized logging system.
5+
This module defines logging utilities that ensure consistent formatting,
6+
traceability, and developer-friendly output across both embedded and CLI modes.
7+
The `LogManager` provides a global configuration entry point for logging,
8+
while `CustomFormatter` enhances each message with context such as file name,
9+
line number, and function.
610
7-
Logging is a critical part of ImportSpy: it tracks validation steps, configuration issues,
8-
execution context, and errors — all with enhanced formatting for debugging and traceability.
11+
Used in ImportSpy to provide high-fidelity logs during validation steps,
12+
contract resolution, and runtime introspection.
913
10-
The `LogManager` ensures that all logs across ImportSpy are consistent and traceable to their source.
11-
It works in both **embedded validation** and **CLI validation**, enabling readable, structured output
12-
for developers, testers, and CI pipelines.
14+
Example:
15+
>>> from importspy.log_manager import LogManager
16+
>>> log_manager = LogManager()
17+
>>> log_manager.configure()
18+
>>> logger = log_manager.get_logger("importspy.test")
19+
>>> logger.info("Validation started.")
1320
"""
1421

1522
import logging
1623

1724

1825
class CustomFormatter(logging.Formatter):
19-
"""
20-
Enhanced formatter for ImportSpy log messages.
26+
"""Enhanced formatter for ImportSpy log messages.
2127
22-
This formatter extends the default logging format by appending the exact
23-
filename, line number, and function name where each log was triggered.
28+
Adds contextual metadata (file, line, function) to each log entry, improving
29+
traceability across CLI execution and embedded imports.
2430
2531
Format:
26-
-------
27-
[timestamp] [LEVEL] [logger name]
28-
[caller: file, line, function] message
32+
"[timestamp] LEVEL logger name"
33+
"[caller: file, line, function] message"
2934
3035
Example:
31-
--------
32-
2024-02-24 14:30:12 [INFO] [my_logger]
33-
[caller: example.py, line: 42, function: my_function] This is a log message.
36+
2025-08-07 14:30:12 INFO importspy.module
37+
caller: loader.py, line: 42, function: validate_import
38+
Validation passed.
3439
"""
3540

3641
LOG_FORMAT = (
@@ -40,24 +45,20 @@ class CustomFormatter(logging.Formatter):
4045
)
4146

4247
def __init__(self):
43-
"""
44-
Initializes the formatter with the ImportSpy logging format.
45-
"""
48+
"""Initialize the formatter with ImportSpy's extended log format."""
4649
super().__init__(self.LOG_FORMAT)
4750

48-
def format(self, record):
49-
"""
50-
Enriches the log record with caller information.
51+
def format(self, record) -> str:
52+
"""Format the log record with caller information.
5153
52-
Parameters:
53-
-----------
54-
record : logging.LogRecord
55-
The original log record to be formatted.
54+
Adds custom attributes to the `LogRecord` for file name, line number,
55+
and function name.
56+
57+
Args:
58+
record (logging.LogRecord): The log record to format.
5659
5760
Returns:
58-
--------
59-
str
60-
A fully formatted log message including file, line, and function context.
61+
str: Formatted log message string.
6162
"""
6263
record.caller_file = record.pathname.split("/")[-1]
6364
record.caller_line = record.lineno
@@ -66,55 +67,37 @@ def format(self, record):
6667

6768

6869
class LogManager:
69-
"""
70-
Centralized manager for all logging within ImportSpy.
70+
"""Centralized manager for logging configuration in ImportSpy.
7171
72-
This class ensures:
73-
- Uniform formatting across all loggers
74-
- Avoidance of duplicate configuration
75-
- Consistent output in both CLI and embedded contexts
72+
Ensures that all logs share a consistent format and output behavior,
73+
avoiding duplicate configuration across modules. Designed for both
74+
embedded validation flows and CLI analysis.
7675
7776
Attributes:
78-
-----------
79-
default_level : int
80-
The current log level derived from the root logger.
81-
82-
default_handler : logging.StreamHandler
83-
Default handler for logging output, using the `CustomFormatter`.
84-
85-
configured : bool
86-
Whether the logging system has already been configured.
77+
default_level (int): Default log level from the root logger.
78+
default_handler (logging.StreamHandler): Stream handler with ImportSpy formatting.
79+
configured (bool): Whether the logger has already been initialized.
8780
"""
8881

8982
def __init__(self):
90-
"""
91-
Sets up the default logging handler and format.
92-
93-
Uses `CustomFormatter` and logs to standard output by default.
94-
"""
83+
"""Initialize the default logging handler and formatter."""
9584
self.default_level = logging.getLogger().getEffectiveLevel()
9685
self.default_handler = logging.StreamHandler()
9786
self.default_handler.setFormatter(CustomFormatter())
9887
self.configured = False
9988

10089
def configure(self, level: int = None, handlers: list = None):
101-
"""
102-
Applies logging configuration globally.
90+
"""Apply logging configuration globally.
10391
104-
Prevents duplicate setup. This method should be called once per application.
92+
Should be called once per execution to avoid duplicated handlers.
93+
Adds provided handlers or defaults to the built-in stream handler.
10594
106-
Parameters:
107-
-----------
108-
level : int, optional
109-
Log level (e.g., logging.DEBUG). Defaults to current system level.
110-
111-
handlers : list of logging.Handler, optional
112-
Custom handlers to use. Falls back to `default_handler` if none provided.
95+
Args:
96+
level (int, optional): Logging level (e.g., logging.DEBUG). Defaults to current level.
97+
handlers (list[logging.Handler], optional): Custom logging handlers.
11398
11499
Raises:
115-
-------
116-
RuntimeError
117-
If logging is already configured.
100+
RuntimeError: If configuration is attempted more than once.
118101
"""
119102
if self.configured:
120103
raise RuntimeError("LogManager has already been configured.")
@@ -131,20 +114,16 @@ def configure(self, level: int = None, handlers: list = None):
131114
self.configured = True
132115

133116
def get_logger(self, name: str) -> logging.Logger:
134-
"""
135-
Returns a named logger with ImportSpy's formatting applied.
117+
"""Return a named logger configured with ImportSpy's formatter.
136118
137-
Ensures the logger is properly configured and ready for use.
119+
Ensures consistent behavior across all log invocations. If the logger
120+
does not yet have handlers, it assigns the default handler.
138121
139-
Parameters:
140-
-----------
141-
name : str
142-
The name of the logger (e.g., a module name).
122+
Args:
123+
name (str): Name of the logger (typically a module or subpackage name).
143124
144125
Returns:
145-
--------
146-
logging.Logger
147-
The initialized logger instance.
126+
logging.Logger: A logger instance ready for use.
148127
"""
149128
logger = logging.getLogger(name)
150129
if not logger.handlers:

0 commit comments

Comments
 (0)