Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 11 additions & 15 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,20 @@ SPDX-FileCopyrightText: 2025 Alexandre Gomes Gaigalas <alganet@gmail.com>

SPDX-License-Identifier: ISC
-->
# Contributing
# Contributing to apywire

## Development Setup
Thanks for your interest in contributing!

1. Clone the repository
2. Install dependencies: `pip install -e .[dev]`
3. Run tests: `make test`

## Commit Messages
For the complete development guide, please see **[docs/development.md](../docs/development.md)**.

Use meaningful titles and a brief body describing your changes.
## Quick Start

Commit titles must not exceed 72 characters.
Commit body lines must not exceed 72 characters.
1. Clone the repository
2. Run `make .venv && source .venv/bin/activate && make pip`
3. Run `make all` to verify everything works

## Code Quality
## Before Submitting a Pull Request

- Format: `make format`
- Lint: `make lint`
- Test: `make test`
- All: `make all`
- **Development**: See [docs/development.md](../docs/development.md) for setup, testing, and workflow
- **Code Standards**: All code must pass `make lint` and maintain test coverage requirements
- **Commit Messages**: Keep titles ≤ 72 characters with meaningful descriptions
7 changes: 4 additions & 3 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ Brief description of changes.
## Type of Change

- [ ] Bug fix
- [ ] Refactoring
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

## Checklist

- [ ] Tests pass
- [ ] Code formatted
- [ ] Documentation updated
- [ ] Descriptive and concise commit message and PR details
- [ ] Docstrings updated
- [ ] Documentation updated
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

# Project Specific
apywire/**.c
site/

# General
.venv/
Expand Down
85 changes: 54 additions & 31 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,58 @@ SPDX-License-Identifier: ISC
-->
# AI Coding Guidelines for apywire

## Overview
For detailed development workflow and standards, see **[docs/development.md](docs/development.md)**.

## Project Context

apywire is a Python 3.12+ library for lazy object wiring and dependency injection. It uses spec-based configuration to instantiate objects on-demand with support for async access, thread safety, and code generation via Cython compilation.

## Core API
**Core API:**
- **`Wiring(spec, thread_safe=False)`**: Main container for lazy instantiation from a spec dict
- **`wired.name()`**: Returns callable `Accessor` that instantiates object on invocation
- **`await wired.aio.name()`**: Async access via `AioAccessor` using `run_in_executor`
- **`wired.compile(aio=False, thread_safe=False)`**: Generates equivalent Python code as a `Compiled` class
- **`compiler = WiringCompiler(spec)`**: For compiling into python code instead of runtime
- **`code = compiler.compile(aio=True, thread_safe=True)`**: Generate python code for the spec
- **Spec Format**: `{"module.Class name": {"param": "value", "ref": "{otherName}"}}`

## Architecture
- **Lazy Loading**: Objects instantiated via `__getattr__` only when accessed, cached after first use
- **Placeholders**: `{name}` in spec values resolve to other wired objects at instantiation
- **Thread Safety**: Optional `thread_safe=True` uses optimistic per-attribute locking with global fallback via `CompiledThreadSafeMixin`
- **Cython Build**: `setup.py` uses Cython to compile `wiring.py` → `wiring.c` with SPDX headers
- **Equivalence**: Compiled output behaves identically to runtime `Wiring` (lazy, async, thread-safe)

## Development Workflow
- **Setup**: `make .venv` and `source .venv/bin/activate`, then `make pip`
- **Check**: `make all` (format, lint, coverage, build)
- **Test**: `pytest -xvs` (verbose), `make coverage` (95% required)
- **Build**: `make build` (Cython compile), `make dist` (package)
- **Clean**: `make clean` (remove artifacts/caches)

## Code Standards
- **Typing**: Strict mypy with `disallow_any_*=true`, use `object` not `Any`
- **Style**: 79-char lines, `black` + `isort` formatting
- **Licensing**: SPDX headers required on all files, validate with `make reuse`
- **Tests**: Cover sync, async (`test_compile_aio.py`), threading (`test_threading.py`), edge cases

## Key Components
- `apywire/wiring.py`: `Wiring`, `Accessor`, `AioAccessor`, `compile()` method
- `apywire/thread_safety.py`: `CompiledThreadSafeMixin` with optimistic/global locking
- `apywire/exceptions.py`: `WiringError`, `CircularWiringError`, `UnknownPlaceholderError`, `LockUnavailableError`
- `setup.py`: Cython build config with SPDX header injection

## Examples
## Critical Architectural Patterns

**Lazy Loading via `__getattr__`:**
Objects are instantiated only when accessed, using `__getattr__` to intercept attribute access and return `Accessor` callables that handle instantiation and caching.

**Placeholder Resolution:**
Strings like `{name}` in spec values are converted to `_WiredRef` markers during parsing, then resolved to actual objects at instantiation time. See `constants.py` for delimiter patterns.

**Thread Safety Model:**
Uses optimistic per-attribute locking (`dict[str, threading.Lock]`) with a global fallback lock. Thread-local state tracks the resolving stack for circular dependency detection. See `threads.py` for `ThreadSafeMixin` implementation.

**Compilation Equivalence:**
Generated code must behave identically to runtime `Wiring`. The compiler generates accessor methods that replicate lazy loading, caching, and optional async/thread-safe behavior.

## AI-Specific Development Notes

**Strict Mypy Configuration:**
The project uses extremely strict mypy settings including `disallow_any_expr=true`. Never use `Any`—use `object` instead. All functions need complete type annotations. See [Code Standards](docs/development.md#type-annotations) for details.

**Module Structure:**
- `wiring.py`: Base class `WiringBase`, type system, placeholder parsing
- `runtime.py`: Runtime `Wiring` class with `Accessor`/`AioAccessor`
- `compiler.py`: Code generation via `WiringCompiler`
- `threads.py`: Thread safety mixins and utilities
- `exceptions.py`: Custom exception classes
- `constants.py`: Shared constants (placeholder patterns, delimiters)

**Testing Requirements:**
All changes must maintain 100% branch coverage. Test both runtime and compiled behavior, plus async and thread-safe variants where applicable. Use descriptive test names: `test_<feature>_<scenario>_<expected_behavior>()`.
Comment thread
alganet marked this conversation as resolved.
Comment thread
alganet marked this conversation as resolved.

**Common Gotchas:**
- SPDX headers required on all files (run `make format` to auto-add)
- 79-character line limit enforced by `black`
- Compiled output is verified against runtime behavior in tests
- Thread-safe tests use actual threading to verify lock behavior

## Quick Reference

```python
# Basic lazy access
wired = Wiring({"datetime.datetime now": {"year": 2025}})
Expand All @@ -54,5 +69,13 @@ obj = await wired.aio.now()
wired = Wiring(spec, thread_safe=True)

# Code generation
code = wired.compile(aio=True, thread_safe=True)
compiler = WiringCompiler(spec)
code = compiler.compile(aio=True, thread_safe=True)
```

**Make Commands:**
- `make all` - Complete check (format, lint, coverage, build)
- `make test` - Run pytest suite
- `make coverage` - Check coverage requirements
- `make lint` - Run reuse, flake8, mypy
- `make format` - Run black, isort, add SPDX headers
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: ISC

.PHONY: all format lint test coverage clean publish pip reuse .venv
.PHONY: all format lint test coverage clean publish pip reuse .venv docs-serve docs-build

all: format lint coverage build

Expand Down Expand Up @@ -50,3 +50,9 @@ dist:

publish:
python -m twine upload dist/*

docs-serve:
python -m mkdocs serve

docs-build:
python -m mkdocs build
55 changes: 51 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,61 @@ SPDX-FileCopyrightText: 2025 Alexandre Gomes Gaigalas <alganet@gmail.com>

SPDX-License-Identifier: ISC
-->

# apywire

A Python package to wire up objects.
[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)

Lazy object wiring and dependency injection for Python 3.12+

## Features

- 🚀 Lazy Loading
- ⚡ Async Support
- 🔒 Thread Safety
- 📦 Code Generation
- 📄 Naturally Configurable
- 🎯 Zero Dependencies

## Installation

```bash
pip install apywire
```

## Quick Example

---
```python
from apywire import Wiring

Under development and unstable, API might change at any moment.
spec = {
"datetime.datetime now": {"year": 2025, "month": 1, "day": 1},
"MyService service": {"start_time": "{now}"}, # Dependency injection
}

wired = Wiring(spec)
service = wired.service() # Lazy instantiation + caching
```

## Documentation

📚 **[Full Documentation](docs/index.md)** • [Getting Started](docs/getting-started.md) • [API Reference](docs/api-reference.md) • [Examples](docs/examples.md)

Build docs locally:
```bash
make docs-serve # http://127.0.0.1:8000
```

## Development

See [AGENTS.md](AGENTS.md) for coding guidelines and architecture overview.
```bash
make .venv && source .venv/bin/activate # Setup
make all # Format, lint, test, build
```

See [AGENTS.md](AGENTS.md) for guidelines.

## License

ISC License - see [LICENSES/ISC.txt](LICENSES/ISC.txt)
Loading