Contributing¶
Contributions are welcome — bug fixes, tests, documentation improvements, and features aligned with the Roadmap. This page covers everything you need to get a development environment running and submit a pull request.
Quick setup¶
git clone https://github.com/irishsmurf/dlight-client.git
cd dlight-client
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
pip install pytest pytest-asyncio flake8 ruff
Run the test suite:
All tests pass against a real in-process TCP server — no network hardware required.
What we welcome¶
- Bug fixes with a regression test
- New tests for existing behaviour
- Documentation improvements (typos, clarity, missing examples)
- Features from the Roadmap — open an issue first to align on approach
- Performance improvements with benchmarks
For significant new features, please open a GitHub Issue before writing code so we can discuss design before you invest the time.
Project layout¶
dlightclient/ Public package
__init__.py Exports and __version__
client.py AsyncDLightClient
device.py DLightDevice facade
discovery.py UDP broadcast discovery
cli.py Command-line interface
models.py TypedDicts
exceptions.py Exception hierarchy
constants.py Ports, limits, protocol literals
_pool.py ConnectionPool (private)
_frame.py Wire-format codec (private)
tests/
fake_server.py In-process asyncio dLight server for testing
conftest.py pytest fixtures
test_dlight.py Core integration tests
test_device.py DLightDevice tests
test_frame.py Codec tests
test_cli.py CLI tests
test_retry.py Retry logic tests
test_pool_regressions.py Concurrency invariants (do not weaken)
tools/
fake_dlight_server.py Standalone test server (run separately)
docs/ MkDocs source
issues/ Roadmap feature specifications
Testing philosophy¶
The test suite uses FakeDLightServer (tests/fake_server.py) — a real asyncio.start_server instance that speaks the actual dLight wire protocol. It supports scriptable faults: connection hangs, TCP resets, truncated frames, and stale delayed replies.
Rules:
- Do not mock
asynciostreams directly. UseFakeDLightServerinstead — it tests the actual wire codec and connection lifecycle. - Assert observable behaviour (connection count, bytes on the wire, return values), not internal call sequences.
- Every new behaviour needs a new test. Every bug fix needs a regression test.
tests/test_pool_regressions.pyencodes the pool's concurrency invariants. If your change causes these to fail, fix the implementation — do not adjust the tests to pass.
To run the standalone server for manual testing:
Code style¶
- Line length: 120 characters maximum.
- Linting:
ruff check dlightclient/andflake8 dlightclient tests --max-line-length=120. - Type hints: required on all public class methods and module-level functions.
- Comments: only when the why is non-obvious. No docstrings describing what the code already says.
Commit conventions¶
We use Conventional Commits:
feat: add mDNS discovery support
fix: evict connection on partial frame read
docs: update CLI reference for --discover-duration
chore: bump version to 1.7.0
test: add regression test for concurrent commands to same device
refactor: extract retry backoff into helper
One logical change per commit.
Pull requests¶
- Branch from
main:git checkout -b feat/my-feature - Write tests first if fixing a bug.
- Ensure CI is green:
python -m pytest tests/andflake8 dlightclient tests --max-line-length=120. - Title the PR like a conventional commit:
feat: ...,fix: ..., etc. - In the PR description: what changed, why, and how you tested it.
- Link to a roadmap issue or GitHub Issue if applicable.
Release process (maintainers)¶
- Bump
__version__indlightclient/__init__.py. - Update
CHANGELOG.md: move items from[Unreleased]to a new[X.Y.Z] — YYYY-MM-DDheading. - Commit:
chore: bump version to X.Y.Z. - Tag:
git tag vX.Y.Z && git push origin vX.Y.Z. - GitHub Actions (
python-publish.yml) builds and publishes to PyPI automatically via OIDC trusted publishing.