Metadata-Version: 2.1
Name: wexample-helpers
Version: 19.1.0
Summary: Provides helper functions for strings, dicts, files, shell commands, and JSON, plus a BaseClass enforcing typed field visibility
Author-Email: weeger <contact@wexample.com>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Project-URL: homepage, https://github.com/wexample/python-helpers
Requires-Python: >=3.10
Requires-Dist: attrs>=23.1.0
Requires-Dist: cattrs>=23.1.0
Requires-Dist: jinja2>=3.0.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-benchmark>=5.2.3; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

# helpers

Version: 19.1.0

`wexample-helpers` is a Python utility library for developers who want a consistent, typed foundation across their projects. It ships pure-function helpers covering strings, dicts, arrays, files, paths, shell commands, JSON, HTML, Jinja, ANSI, CLI, Docker, and more, alongside a `@base_class` decorator (built on `attrs`) that enforces typed field visibility through `Field`, `PrivateField`, and `ProtectedField` descriptors. The package stands alone or as the base layer of the broader Wexample suite.

## Table of Contents

- [Installation](#installation)
- [Quickstart](#quickstart)
- [Tests](#tests)
- [Architecture](#architecture)
- [Integration in the Suite](#integration-in-the-suite)
- [Dependencies](#dependencies)
- [Versioning & Compatibility Policy](#versioning--compatibility-policy)
- [License](#license)
- [About us](#about-us)
- [Known Limitations & Roadmap](#known-limitations--roadmap)
- [Status & Compatibility](#status--compatibility)
- [Useful Links](#useful-links)
- [Migration Notes](#migration-notes)

## Installation

```bash
pip install wexample-helpers
```

Requires Python >=3.10.

## Quickstart

Install from PyPI:

```bash
pip install wexample-helpers
```

Import and call a helper — here, converting any string to snake_case regardless of its original format:

```python
from wexample_helpers.helper.string import string_to_snake_case

print(string_to_snake_case("MyClassName"))
# my_class_name
```

The same normalisation engine powers every other case converter. Pass a target format name to `string_convert_case` instead of calling a specific function:

```python
from wexample_helpers.helper.string import string_convert_case

print(string_convert_case("my-class-name", "pascal"))
# MyClassName
```

Valid target names are `snake`, `kebab`, `camel`, `pascal`, `constant`, `title`, `dot`, and `path`.

For dict work, `dict_merge` recurses into nested dicts rather than overwriting them:

```python
from wexample_helpers.helper.dict import dict_merge

result = dict_merge({"a": 1, "b": {"x": 10}}, {"b": {"y": 20}, "c": 3})
# {"a": 1, "b": {"x": 10, "y": 20}, "c": 3}
```

## Tests

This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.

### Installation

First, install the required testing dependencies:
```bash
.venv/bin/python -m pip install pytest pytest-cov
```

### Basic Usage

Run all tests with coverage:
```bash
.venv/bin/python -m pytest --cov --cov-report=html
```

### Common Commands
```bash
# Run tests with coverage for a specific module
.venv/bin/python -m pytest --cov=your_module

# Show which lines are not covered
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing

# Generate an HTML coverage report
.venv/bin/python -m pytest --cov=your_module --cov-report=html

# Combine terminal and HTML reports
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html

# Run specific test file with coverage
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
```

### Viewing HTML Reports

After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.

### Coverage Threshold

To enforce a minimum coverage percentage:
```bash
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
```

This will cause the test suite to fail if coverage drops below 80%.

## Architecture

`wexample-helpers` is a pure Python library that ships two things: a collection of stateless helper modules, and a class-building system that enforces typed field visibility at definition time. Every concrete class the library defines follows the same pattern; the helpers stand independently and carry no class-level state.

### Package layout

```
src/wexample_helpers/
├── decorator/        # @base_class — thin attrs.define wrapper
├── classes/          # BaseClass, field system, class-level mixins
│   └── mixin/        # opt-in behaviours (logging, two-step init, serialization …)
├── common/
│   ├── debug/        # DebugDump, DebugDumpClass, DebugBreakpoint
│   └── exception/    # ExceptionFrame, TraceCollector, TraceFormatter, ExceptionHandler
├── exception/        # UndefinedException and concrete exception classes
│   └── mixin/
├── service/          # Registry, SingletonRegistry, SharedRegistry, Registrable
│   └── mixin/        # RegistryContainerMixin
├── helper/           # One module per domain — pure functions, no shared state
├── validator/        # AbstractValidator, RegexValidator, RangeValidator
├── enums/            # DebugPathStyle, FieldVisibility, ErrorTruncateRules
├── const/            # Type aliases, ANSI codes, terminal constants
├── mixin/            # Standalone mixins (WithEntrypointPathMixin)
└── testing/          # Fixture classes and pytest plugin
    └── plugin/
```

### The `@base_class` decorator

src/wexample_helpers/decorator/base_class.py wraps `attrs.define(kw_only=True)`:

```python
def base_class(_cls=None, *, slots=False):
    def wrap(cls):
        return attrs.define(kw_only=True, slots=slots)(cls)
    ...
```

Every class in the library that carries fields carries this decorator. It makes construction keyword-only and hands attribute management to attrs.

### BaseClass and field validation

src/wexample_helpers/classes/base_class.py is the common root for library classes. Its `__init_subclass__` hook fires at class definition time and rejects any non-uppercase, non-method attribute that is not a `BaseField` instance:

```python
raise TypeError(
    f"Property '{name}' in class '{cls.__name__}' must inherit from BaseField. "
    f"Use Field(), PrivateField(), or ProtectedField() instead of raw attrs.field()"
)
```

A typo or bare `attrs.field()` call is therefore a hard error before the first object is constructed.

### Field system

src/wexample_helpers/classes/base_field.py defines `BaseField`, which wraps an attrs field with a description, optional validator, and visibility metadata. Three concrete subclasses live in their own files:

- src/wexample_helpers/classes/field.py — `Field` / `public_field()`: no prefix constraint.
- src/wexample_helpers/classes/private_field.py — `PrivateField` / `private_field()`: name must start with `_`, `init=False` by default.
- src/wexample_helpers/classes/protected_field.py — `ProtectedField` / `protected_field()`: name must start with `_`.

Each exposes a factory function (`public_field(description, ...)`) that returns an attrs field. The `BaseField.to_attrs_field()` method sets `metadata["field_type"]` to the concrete class name so the `BaseClass` validator can read it back from attrs field descriptors.

### Helper modules

src/wexample_helpers/helper contains one module per domain. Each exports free functions with no shared state. The full set at the time of writing: `ansi`, `args`, `array`, `classes`, `cli`, `debug`, `dict`, `directory`, `docker`, `error`, `file`, `html`, `jinja`, `json`, `module`, `parallel`, `path`, `polyfill`, `python`, `shell`, `string`, `system`, `trace`, `type`, `user`, `variable`, `version`, plus the attempt-manager helpers (see below).

src/wexample_helpers/helper/parallel.py provides `parallel_map` and `parallel_for_each` backed by `ThreadPoolExecutor`, intended for I/O-bound work. Single-item inputs run inline.

src/wexample_helpers/helper/shell.py provides `shell_run`, `shell_run_async`, and `shell_stream_async`. All three resolve the command through `shlex`, spawn the process in a fresh process group (`start_new_session=True`), and return or raise `ShellCommandFailedException` on non-zero exit.

### Debug and trace pipeline

Calling `debug_trace()` in src/wexample_helpers/helper/debug.py delegates through src/wexample_helpers/helper/trace.py:

1. `trace_get_frames()` calls `TraceCollector.from_stack()` in src/wexample_helpers/common/exception/collector.py, which walks `inspect.stack()` and builds a list of `ExceptionFrame` objects.
2. `ExceptionFrame` (src/wexample_helpers/common/exception/frame.py) is a frozen dataclass that holds `filename`, `lineno`, `function`, `code`, and a `DebugPathStyle` controlling how the path is rendered.
3. `trace_format()` passes the frame list to `TraceFormatter.format()` in src/wexample_helpers/common/exception/formatter.py, which filters internal frames and returns a printable string.

Exception formatting takes the same path with a different entry point: `ExceptionHandler.format_exception(err)` in src/wexample_helpers/common/exception/handler.py calls `TraceCollector.from_traceback()`, then optionally truncates frames according to the rules in `ErrorTruncateRules` before calling `TraceFormatter.format()`.

### Exception system

src/wexample_helpers/exception/undefined_exception.py is the base exception class. It is an attrs class (`@base_class`) that also inherits from `Exception`. Fields include `message`, `cause`, `previous`, `suggestions`, `data`, and an auto-generated `exception_id`. Subclasses override `_build_message()` to derive their message from their own fields; `render_message()` calls it lazily, so field declaration order never matters.

`collect_data()` merges the explicit `data` dict with every public field declared by the subclass, giving a complete structured payload without hand-building a `data={...}` dict in each subclass.

### Registry system

src/wexample_helpers/service/registrable.py defines the `Registrable` protocol: items expose `get_registry_key()`, `dependencies()`, `init_sync()`, and `init_async()`.

src/wexample_helpers/service/registry.py is the base generic registry. `register(item)` auto-derives the key by probing `get_registry_key()`, then `get_snake_short_class_name()`, then the class name. `get(key)` returns `None` or raises `KeyError` when `_fail_if_missing` is set.

src/wexample_helpers/service/singleton_registry.py stores classes rather than instances. `init_all_sync()` and `init_all_async()` instantiate them in topological order derived from `Registrable.dependencies()`. The async variant gathers `init_async()` calls within each independent layer.

src/wexample_helpers/service/shared_registry.py adds a class-level `.shared()` class method that returns a lazily created, per-subclass singleton. `reset_shared()` drops it, which is useful in tests.

src/wexample_helpers/service/mixin/registry_container_mixin.py provides `RegistryContainerMixin`, which an object can inherit to host multiple named registries internally.

### Attempt managers

src/wexample_helpers/helper/abstract_attempt_manager.py defines `AbstractAttemptManager`, a generic retry loop. Subclasses implement `_attempt(attempt) → AttemptOutcome` and `_handle_exhaustion()`. The loop applies exponential backoff (`backoff_base_seconds ** attempt`) unless `delay_seconds_callback` overrides it.

Two concrete subclasses live in the same `helper/` directory:

- src/wexample_helpers/helper/polling_callback_manager.py — `PollingCallbackManager`: retries until the callback returns a non-`None` value; raises `TimeoutError` on exhaustion.
- src/wexample_helpers/helper/retryable_callback_manager.py — `RetryableCallbackManager`: retries on any exception when `should_retry_callback` approves; re-raises the last exception on exhaustion.

### Validator system

src/wexample_helpers/validator/abstract_validator.py defines `AbstractValidator` as an attrs class with an optional `error_message`. Concrete validators implement `validate(value) → bool` and `_get_default_error_message(value) → str`. Two ship with the library: `RegexValidator` (one or more `re` patterns, OR logic) and `RangeValidator` (inclusive numeric bounds).

### Testing support

src/wexample_helpers/testing/plugin/runtest_makereport.py is a pytest hook that replaces the default failure repr with output from `ExceptionHandler.format_exception()`. It is registered automatically when the package is installed with `pytest` enabled and the plugin is activated.

`testing/classes/` and `testing/mixin/` contain concrete classes used by the test suite to exercise attrs inheritance, pydantic integration, multiple-inheritance MRO, and circular forward references. They carry no production logic.

## Integration in the Suite

This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.

### Related Packages

The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.

Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.

## Dependencies

- attrs: >=23.1.0
- cattrs: >=23.1.0
- jinja2: >=3.0.0

## Versioning & Compatibility Policy

Wexample packages follow **Semantic Versioning** (SemVer):

- **MAJOR**: Breaking changes
- **MINOR**: New features, backward compatible
- **PATCH**: Bug fixes, backward compatible

We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

Free to use in both personal and commercial projects.

## About us

[Wexample](https://wexample.com) stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.

This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.

Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.

## Known Limitations & Roadmap

Current limitations and planned features are tracked in the GitHub issues.

See the [project roadmap](https://github.com/wexample/python-helpers/issues) for upcoming features and improvements.

## Status & Compatibility

**Maturity**: Production-ready

**Python Support**: >=3.10

**OS Support**: Linux, macOS, Windows

**Status**: Actively maintained

## Useful Links

- **Homepage**: https://github.com/wexample/python-helpers
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
- **Issue Tracker**: https://github.com/wexample/python-helpers/issues
- **Discussions**: https://github.com/wexample/python-helpers/discussions
- **PyPI**: [pypi.org/project/wexample-helpers](https://pypi.org/project/wexample-helpers/)

## Migration Notes

When upgrading between major versions, refer to the migration guides in the documentation.

Breaking changes are clearly documented with upgrade paths and examples.
