Contributing guide for apywire developers.
- Python 3.12 or later
- Git
- Make (optional but recommended)
Clone the repository and set up your development environment:
git clone https://github.com/alganet/apywire.git
cd apywire
# Install dev dependencies
uv sync --extra devThis will:
- Create (or update) a
.venvproject environment - Install all development dependencies including mkdocs, pytest, mypy, etc.
# Run all tests with pytest
make test
# Run tests with coverage
make coverage
# Run verbose tests
pytest -xvsThe project enforces 95% test coverage or higher. New code should include comprehensive tests.
- Compiled Code Testing: When testing compiled code, use the pre-instantiated
compiledobject from the execution namespace instead of manually instantiating the class.exec(code, execd) wired = execd["compiled"] # Use this instance
- Type Safety in Tests: Avoid
Anyandtype: ignore. Usetyping.Protocolto define the expected interface of dynamic or compiled objects.class MockConfig(Protocol): def config(self) -> Config: ... execd: dict[str, MockConfig] = {}
- Coverage: The CI gate enforces a 95% branch-coverage floor (
fail_underinpyproject.toml); aim for 100% on new code. - Naming: Use descriptive test names:
test_<feature>_<scenario>_<expected_behavior>(). - Module Mocking: When testing custom classes, inject them into
sys.modulesusing atry...finallyblock to ensure cleanup.class MockModule(ModuleType): ... sys.modules["mymod"] = MockModule() try: # test code finally: del sys.modules["mymod"]
The project uses black and isort for code formatting:
# Format all code
make formatThis command:
- Runs
reuse annotateto add SPDX headers - Formats code with
black(79-character line length) - Sorts imports with
isort
# Run all linters
make lintThis runs:
reuse lint- Validates SPDX licensing headersflake8- Python style checkermypy- Static type checker
All linting must pass before submitting a pull request.
# Build Cython extension
make build
# Build distribution packages
make distThe build process compiles apywire/wiring.py to C using Cython for performance.
Run all checks before committing:
make allThis runs: format → lint → coverage → build
apywire/
├── apywire/ # Main package
│ ├── __init__.py # Public API exports
│ ├── wiring.py # Base wiring functionality
│ ├── runtime.py # Runtime wiring implementation
│ ├── compiler.py # Code generation
│ ├── threads.py # Thread safety utilities
│ ├── exceptions.py # Exception classes
│ ├── constants.py # Constants and configuration
│ └── py.typed # PEP 561 marker file
├── tests/ # Test suite
│ ├── test_single.py # Runtime tests
│ ├── test_compile_aio.py # Async compilation tests
│ ├── test_threading.py # Thread safety tests
│ ├── test_factory_methods.py # Factory method tests
│ ├── test_edge_cases.py # Edge case tests
│ ├── test_stdlib_compat.py # Stdlib compatibility tests
│ └── test_internals.py # Internal implementation tests
├── docs/ # MkDocs documentation
├── pyproject.toml # Project configuration
├── setup.py # Build configuration (Cython)
├── Makefile # Development commands
└── README.md # Project readme
apywire uses strict mypy with very aggressive settings:
[tool.mypy]
strict = true
disallow_any_unimported = true
disallow_any_decorated = true
disallow_any_explicit = true
disallow_subclassing_any = true
disallow_any_expr = true
warn_return_any = trueGuidelines:
- No
Any: Useobjectinstead - Full type annotations on all functions and methods
- Type all variables where type can't be inferred
- Use type aliases for complex types
Example:
from typing import TypeAlias
# Good
def process(data: dict[str, str]) -> list[str]:
return list(data.values())
# Bad - missing return type
def process(data: dict[str, str]):
return list(data.values())- Line length: 79 characters (enforced by
black) - Imports: Sorted with
isort, profileblack - Docstrings: Google style
- Naming:
- Classes:
PascalCase - Functions/methods:
snake_case - Constants:
UPPER_CASE - Private:
_leading_underscore
- Classes:
All source files must have SPDX headers:
# SPDX-FileCopyrightText: 2025 Alexandre Gomes Gaigalas <alganet@gmail.com>
#
# SPDX-License-Identifier: ISCRun make format to automatically add headers to new files.
Objects are instantiated via __getattr__ only when accessed:
def __getattr__(self, name: str):
if name in self._values:
return Accessor(lambda: self._values[name])
# Instantiate and cache...Strings like {name} are converted to _WiredRef markers during parsing, and {aio.name} to _AioWiredRef markers:
class _WiredRef:
__slots__ = ("name",)
class _AioWiredRef:
__slots__ = ("name",)
def _resolve(self, obj):
if isinstance(obj, str) and self._is_placeholder(obj):
ref_name = self._extract_placeholder_name(obj)
if ref_name.startswith("aio."):
return _AioWiredRef(ref_name[4:])
return _WiredRef(ref_name)
return objAt instantiation time, _WiredRef markers are resolved to actual objects, and _AioWiredRef markers are resolved to async accessor callables.
Thread-safe mode uses:
- Per-attribute locks:
dict[str, threading.Lock] - Global fallback lock: Single
threading.Lock - Thread-local state: For lock-related per-thread state used by the thread-safety mixin (mode, held locks)
See apywire/threads.py for details.
The compiler generates Python code that:
- Imports all required modules
- Creates a
Compiledclass - Generates accessor methods for each wired object
- Optionally includes async accessors and thread safety
See apywire/compiler.py for the implementation.
wiring.py: Base class, type system, placeholder parsingruntime.py: RuntimeWiringimplementation,Accessor/AioAccessorcompiler.py: Code generation viaWiringCompilerthreads.py: Thread safety mixins and utilitiesexceptions.py: Custom exception classesconstants.py: Shared constants (delimiter, placeholder markers)
test_single.py: Runtime wiring tests (non-thread-safe)test_threading.py: Thread-safe wiring teststest_compile_aio.py: Async compilation teststest_factory_methods.py: Factory method teststest_edge_cases.py: Edge cases and error handlingtest_stdlib_compat.py: Standard library integrationtest_internals.py: Internal implementation details
Test naming convention:
def test_<feature>_<scenario>_<expected_behavior>():
"""Brief description of what is being tested."""
# Arrange
spec = {...}
# Act
wired = Wiring(spec)
result = wired.something()
# Assert
assert result == expectedAlways test:
- Runtime behavior
- Compiled behavior (when applicable)
- Async variants (when applicable)
- Thread-safe variants (when applicable)
- Error cases
Target: 95% branch coverage or higher
Check coverage:
make coverageView HTML coverage report:
open htmlcov/index.html# Serve docs locally
make docs-serve
# Build static site
make docs-build- Use GitHub Flavored Markdown
- Include code examples that actually work
- Add SPDX headers to all
.mdfiles - Use admonitions for important notes:
!!! note
This is a note
!!! warning
This is a warning-
Create a branch from
main:git checkout -b feature/my-feature
-
Make changes and ensure tests pass:
make all
-
Commit changes with descriptive messages:
git commit -m "Add feature X to handle Y" -
Push to GitHub:
git push origin feature/my-feature
-
Create pull request on GitHub
-
Address review feedback if any
(For maintainers)
- Update version in
pyproject.toml - Update CHANGELOG (if present)
- Run full test suite:
make all - Build distribution:
make dist - Tag release:
git tag v0.x.x - Push tag:
git push origin v0.x.x - Publish to PyPI:
make publish
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Full Documentation
apywire is licensed under the ISC License. See LICENSE for details.
- API Reference - Detailed API documentation
- User Guide - Learn how to use apywire
- Examples - Practical examples