# mixinforge
[![PyPI version](https://img.shields.io/pypi/v/mixinforge.svg?color=green)](https://pypi.org/project/mixinforge/)
[![Python versions](https://img.shields.io/pypi/pyversions/mixinforge.svg)](https://github.com/pythagoras-dev/mixinforge)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/pythagoras-dev/mixinforge/blob/master/LICENSE)
[![Downloads](https://img.shields.io/pypi/dm/mixinforge?color=blue)](https://pypistats.org/packages/mixinforge)
[![Documentation Status](https://app.readthedocs.org/projects/mixinforge/badge/?version=latest)](https://mixinforge.readthedocs.io/en/latest/)
[![Code style: pep8](https://img.shields.io/badge/code_style-pep8-blue.svg)](https://peps.python.org/pep-0008/)
[![Docstring Style: Google](https://img.shields.io/badge/docstrings_style-Google-blue)](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html)
[![Ruff](https://github.com/pythagoras-dev/mixinforge/actions/workflows/ruff.yml/badge.svg?branch=master)](https://github.com/pythagoras-dev/mixinforge/actions/workflows/ruff.yml)

A collection of Python mixins, metaclasses, utility functions, and CLI
tools for building robust, configurable classes.

## What Is It?

`mixinforge` is a lightweight library providing four key areas of
functionality:

1. **Mixins & Metaclasses** — Reusable components for parameter
   management, cache control, initialization contracts, thread safety,
   singleton pattern, and pickle prevention
2. **Utility Functions** — Tools for JSON serialization, nested
   collection processing, and dictionary operations
3. **Context Managers** — Helpers for temporary state/behavior changes
4. **CLI Tools** — Command-line utilities for project analysis and
   maintenance

## Quick Example

Here's an example showing thread safety enforcement:

```python
from mixinforge import SingleThreadEnforcerMixin

class DatabaseConnection(SingleThreadEnforcerMixin):
    def __init__(self, connection_string: str):
        super().__init__()
        self.connection_string = connection_string
        self.connection = self._connect()

    def _connect(self):
        return f"Connected to {self.connection_string}"

    def query(self, sql: str):
        self._restrict_to_single_thread()  # Enforces thread safety
        return f"Executing: {sql}"

# Works fine on the owner thread
db = DatabaseConnection("localhost:5432")
result = db.query("SELECT * FROM users")

# Calling from another thread raises RuntimeError
import threading
threading.Thread(
    target=lambda: db.query("SELECT *")
).start()  # Raises RuntimeError!
```

## Installation

The source code is hosted on GitHub at:
[https://github.com/pythagoras-dev/mixinforge](https://github.com/pythagoras-dev/mixinforge)

Binary installers for the latest released version are available at the
Python package index at:
[https://pypi.org/project/mixinforge](https://pypi.org/project/mixinforge)

Using uv:
```bash
uv add mixinforge
```

Using pip:
```bash
pip install mixinforge
```

## Requirements

- Python >= 3.11
- Runtime dependencies:
  - tabulate

For development:
- pytest (optional)

## API Overview

### Mixins & Metaclasses

| Component | Description |
|-----------|-------------|
| `ParameterizableMixin` | Base class for parameterizable objects with JSON serialization |
| `ImmutableMixin` | Base class for immutable objects with customizable identity keys |
| `ImmutableParameterizableMixin` | Immutable objects with params-based identity |
| `CacheablePropertiesMixin` | Auto discovery and invalidation of `cached_property` |
| `NotPicklableMixin` | Prevents pickling/unpickling of objects |
| `GuardedInitMeta` | Strict initialization control with lifecycle hooks |
| `SingleThreadEnforcerMixin` | Enforces single-threaded execution |
| `SingletonMixin` | Ensures each subclass has exactly one instance |

### Utility Functions

| Function | Description |
|----------|-------------|
| `dumpjs(obj)` | Serialize object to JSON string |
| `loadjs(js)` | Deserialize JSON string to object |
| `update_jsparams(js, **updates)` | Update params in JSON |
| `access_jsparams(js, *names)` | Extract params from JSON |
| `sort_dict_by_keys(d)` | Sort dictionary keys alphabetically |
| `flatten_nested_collection(obj)` | Find atomics in nested collections |
| `find_instances_inside_composite_object(obj, classinfo, deep_search=True)` | Find instances of type(s) in composite |
| `transform_instances_inside_composite_object(obj, classinfo, fn)` | Transform instances of type(s) in composite |
| `is_executed_in_notebook()` | Detect if running in Jupyter/IPython notebook |
| `is_valid_env_name(name)` | Validate environment variable names |
| `install_package(name, ...)` | Install a Python package from PyPI at runtime |
| `is_package_installed(name)` | Check if a Python package is currently installed |
| `uninstall_package(name, ...)` | Remove a Python package from the environment |


### Context Managers

| Component | Description |
|-----------|-------------|
| `OutputCapturer` | Captures stdout, stderr, and logging output while preserving display |
| `OutputSuppressor` | Suppresses stdout and stderr output by redirecting to /dev/null |

### CLI Tools

| Command | Description |
|---------|-------------|
| `mf-get-stats` | Analyze project metrics and generate reports |
| `mf-clear-cache` | Remove Python cache files and directories |
| `mf-clear-dist` | Remove distribution artifacts (dist/ directory) |

## Mixins & Metaclasses

### ParameterizableMixin

A base class for objects with configuration parameters. Enables
standardized parameter access and JSON serialization.

**Key features:**
- `get_params()` — Returns all parameters as a dictionary
- `get_default_params()` — Class method to get default parameter
  values from `__init__` signature
- `get_essential_params()` — Returns only essential configuration
  parameters
- `get_auxiliary_params()` — Returns auxiliary parameters (logging,
  verbosity, etc.)
- `essential_param_names` — Property to specify which parameters are
  core to the object's identity
- `get_jsparams()` — Get parameters as JSON string
- `get_essential_jsparams()` / `get_auxiliary_jsparams()` — Get
  filtered parameters as JSON
- Works seamlessly with `dumpjs()` and `loadjs()` for full object
  serialization

### ImmutableMixin

A base class for creating immutable objects with customizable identity
keys. Provides immutability guarantees through the `GuardedInitMeta`
metaclass and enables value-based hashing and equality.

**Key features:**
- `identity_key()` — Abstract method that subclasses override to define
  what makes an object unique
- `__hash__` / `__eq__` — Implements identity-based hashing and equality
  using cached identity keys
- `GuardedInitMeta` — Uses guarded initialization to prevent hash
  computation on uninitialized objects
- `__copy__` / `__deepcopy__` — Returns self since immutable objects
  don't need copying
- Flexible design allows any hashable value as identity key (strings,
  tuples, JSON, etc.)

### ImmutableParameterizableMixin

A mixin combining `ParameterizableMixin` and `ImmutableMixin` for
creating immutable objects defined by their parameters. Uses
JSON-serialized parameters as the identity key.

**Key features:**
- Inherits from both `ParameterizableMixin` and `ImmutableMixin`
- Automatically uses JSON-serialized parameters for hashing and equality
- Enables parameter-based identity for dictionary keys and set membership
- Combines parameter management with immutability guarantees

### CacheablePropertiesMixin

A mixin for managing `functools.cached_property` attributes with
automatic discovery and invalidation across the class hierarchy.

**Key methods:**
- `_get_all_cached_properties_status()` — Check which cached
  properties are currently cached (returns dict)
- `_get_cached_property_status(name)` — Check if a specific property
  is cached
- `_get_all_cached_properties()` — Retrieve all currently cached
  values
- `_get_cached_property(name)` — Get a specific cached value
- `_set_cached_properties(**kwargs)` — Manually set cached values
  (useful for testing/restoration)
- `_invalidate_cache()` — Clear all cached properties across the
  entire class hierarchy
- `_all_cached_properties_names` — Property returning all cached
  property names

Automatically discovers cached properties from all classes in the MRO,
including decorator-wrapped properties, making it reliable for complex
inheritance structures.

### NotPicklableMixin

A mixin that explicitly prevents objects from being pickled or
unpickled. Useful for objects that hold non-serializable resources like
database connections, file handles, or network sockets.

Raises `TypeError` on any pickling attempt, providing clear error
messages about why serialization is blocked.

### GuardedInitMeta

A metaclass that enforces strict initialization control and provides
lifecycle hooks. It ensures that `_init_finished` is `False` during
`__init__` and automatically sets it to `True` afterward, enabling
reliable initialization state checks.

**Key features:**
- Automatic `_init_finished` flag management (False during init, True
  after)
- `__post_init__()` hook — Called automatically after `__init__`
  completes
- `__post_setstate__()` hook — Called after unpickling when restoring
  object state
- Automatic `__setstate__` wrapping for proper unpickling behavior
- Validates `_init_finished=False` in pickled state to prevent
  corruption
- Compatible with `ABCMeta` for abstract base classes
- Prevents use with dataclasses (incompatible)
- Validates single GuardedInitMeta base in multiple inheritance

Enforces initialization contracts and provides clear error messages when
contracts are violated, making initialization bugs easier to catch.

### SingleThreadEnforcerMixin

A mixin to enforce single-threaded execution with multi-process support.
Ensures methods are called only from the thread that first instantiated
the object, while automatically supporting process-based parallelism
through fork detection.

**Key method:**
- `_restrict_to_single_thread()` — Call this at the start of methods
  that need thread enforcement

Automatically detects process forks and resets ownership, making it safe
for multiprocessing workflows.

### SingletonMixin

A mixin for implementing the singleton pattern. Ensures each subclass
maintains exactly one instance that is returned on every instantiation
attempt.

Useful for classes that should have only a single instance throughout
the application lifetime, such as configuration managers or resource
coordinators. Each subclass gets its own singleton instance.

## Utility Functions

### JSON Serialization

mixinforge provides a complete JSON serialization system for Python
objects:

- **`dumpjs(obj)`** — Serialize any Python object (including classes,
  instances, nested structures) to a JSON string
- **`loadjs(js)`** — Deserialize a JSON string back to its original
  Python object
- **`update_jsparams(js, **kwargs)`** — Modify parameters in serialized
  JSON without full deserialization
- **`access_jsparams(js, *names)`** — Extract specific parameters from
  serialized JSON

This system handles complex objects including class hierarchies,
`__slots__`, nested collections, and maintains object identity; it
seamlessly integrates with `ParameterizableMixin`.

### Nested Collection Processing

Tools for working with nested data structures:

- **`flatten_nested_collection(obj)`** — Recursively find all
  atomic-type objects (primitives, strings, etc.) within nested
  collections (returns iterator)
- **`find_instances_inside_composite_object(obj, classinfo, deep_search=True)`** —
  Recursively find all instances of the specified type(s) within
  composite structures (returns iterator). Accepts a single type or
  tuple of types, like `isinstance()`. Set `deep_search=False` to
  stop traversal at matched instances.
- **`transform_instances_inside_composite_object(obj, classinfo, transform_fn)`** —
  Transform all instances of the specified type(s) within composite
  structures, reconstructing the object graph with transformed instances
  (returns transformed object). Accepts a single type or tuple of types,
  like `isinstance()`.

These functions handle arbitrary nesting depths and complex object
graphs including cyclic references. Each object is visited only once
(deduplication by identity), making them safe for graphs with cycles
or shared references. Useful for introspection, validation, and
structural transformations.

### Environment Variable Name Validation

- **`is_valid_env_name(name)`** - Enforces a portable, shell-safe subset of 
environment variable names (POSIX identifiers).


### Dictionary Utilities

- **`sort_dict_by_keys(d)`** — Returns a new dictionary with keys
  sorted alphabetically, useful for consistent serialization and
  comparison

### Package Management

Tools for runtime package installation, checking, and removal:

- **`install_package(package_name, upgrade=False, version=None, use_uv=True, import_name=None, verify_import=True)`** —
  Install a Python package from PyPI into the current environment. Supports version pinning,
  upgrade mode, and handles packages where PyPI name differs from import name (e.g., "Pillow" vs "PIL").
  Uses `uv` by default for speed, falling back to `pip` when needed.

- **`is_package_installed(package_name)`** —
  Check if a Python package is currently installed in the environment. Returns `True` if installed,
  `False` otherwise. Handles package name variations (hyphens, underscores, capitalization) through
  canonicalization.

- **`uninstall_package(package_name, use_uv=True, import_name=None, verify_uninstall=True)`** —
  Remove a Python package from the current environment. Protects critical package managers
  (pip, uv) from accidental removal. Verifies complete removal by default.

These functions automatically bootstrap missing package managers and invalidate Python's
import caches after operations to ensure the import system reflects filesystem changes.

## Context Managers

### OutputCapturer

A context manager that simultaneously captures and displays stdout, stderr,
and logging output. Uses a "tee" strategy where output is duplicated: sent
to both the original destination (for normal display) and to an internal
buffer (for storage).

**Key features:**
- Captures `sys.stdout`, `sys.stderr`, and `logging` output
- Preserves normal output behavior (output is still visible in console)
- `get_output()` — Retrieve all captured output as a single string
- Ideal for testing CLI tools or logging execution traces without suppressing output

### OutputSuppressor

A context manager that suppresses stdout and stderr by redirecting them to
the system null device (`os.devnull`). Useful for silencing noisy operations
in background processes or tests.

**Key features:**
- Suppresses both `sys.stdout` and `sys.stderr`
- Uses `contextlib.ExitStack` for reliable cleanup even on exceptions
- Automatically restores original streams when exiting the context
- Ideal for background workers, batch processing, or tests that need silence

## CLI Tools

mixinforge provides two command-line tools for project analysis and
maintenance.

### mf-get-stats

Analyzes Python projects and generates comprehensive code metrics.

**Usage:**
```bash
# Analyze current directory
mf-get-stats

# Analyze specific directory
mf-get-stats /path/to/project

# Specify custom output filename
mf-get-stats --output my_metrics.md
```

**Features:**
- Generates markdown report with detailed statistics:
  - Lines of Code (LOC) and Source Lines of Code (SLOC)
  - Class and function counts
  - File counts
  - Breakdown by main code vs. unit tests
- Displays formatted summary table in console
- Auto-updates `README.md` and Sphinx documentation if special markers
  are present:
  - `<!-- MIXINFORGE_STATS_START -->` and `<!-- MIXINFORGE_STATS_END -->` for markdown
  - `.. MIXINFORGE_STATS_START` and `.. MIXINFORGE_STATS_END` for
    reStructuredText
- Returns list of updated files for CI/CD integration

This tool is ideal for tracking project growth, maintaining
documentation, and integrating metrics into automated workflows.

### mf-clear-cache

Removes all Python cache files and directories from a project.

**Usage:**
```bash
# Clean current directory
mf-clear-cache

# Clean specific directory
mf-clear-cache /path/to/project

# Specify custom report filename
mf-clear-cache --output cleanup_report.md
```

**What it removes:**
- `__pycache__` directories
- `.pyc` and `.pyo` compiled bytecode files
- Cache directories from:
  - pytest (`.pytest_cache`)
  - mypy (`.mypy_cache`)
  - ruff (`.ruff_cache`)
  - hypothesis (`.hypothesis`)
  - tox (`.tox`)
  - coverage (`.coverage`, `htmlcov`)

Generates a detailed markdown report categorizing removed items. Useful
for cleaning build artifacts before commits or releases.

### mf-clear-dist

Removes distribution artifacts (the `dist/` directory) created by build tools.

**Usage:**
```bash
# Clean current directory
mf-clear-dist

# Clean specific directory
mf-clear-dist /path/to/project
```

**What it removes:**
- `dist/` directory containing:
  - Source distributions (`.tar.gz`)
  - Wheel files (`.whl`)
  - Any other build artifacts

This tool is useful for cleaning up after `uv build`, `python -m build`,
or similar build commands. It reports the number of files removed and
total size freed.

## Project Statistics

<!-- MIXINFORGE_STATS_START -->
| Metric | Main code | Unit Tests | Total |
|--------|-----------|------------|-------|
| Lines Of Code (LOC) | 4941 | 12286 | 17227 |
| Source Lines Of Code (SLOC) | 2222 | 7141 | 9363 |
| Classes | 20 | 211 | 231 |
| Functions / Methods | 170 | 1030 | 1200 |
| Files | 27 | 85 | 112 |
<!-- MIXINFORGE_STATS_END -->

## Development

- Run tests:
  - With pytest: `pytest`
  - Or via Python: `python -m pytest`
- Supported Python versions: 3.11+
- See contributing guidelines:
  [contributing.md](https://github.com/pythagoras-dev/mixinforge/blob/master/CONTRIBUTING.md)
- See docstrings and comments guidelines:
  [docstrings_comments.md](https://github.com/pythagoras-dev/mixinforge/blob/master/docstrings_comments.md)
- See type hints guidelines:
  [type_hints.md](https://github.com/pythagoras-dev/mixinforge/blob/master/type_hints.md)
- See Read the Docs configuration guide:
  [readthedocs.md](https://github.com/pythagoras-dev/mixinforge/blob/master/readthedocs.md)

## License

This project is licensed under the MIT License — see
[LICENSE](https://github.com/pythagoras-dev/mixinforge/blob/master/LICENSE)
for details.

## Contact

* **Maintainer**: [Vlad (Volodymyr) Pavlov](https://www.linkedin.com/in/vlpavlov/)
* **Email**: vlpavlov@ieee.org
