Thanks for your interest in contributing to comlink-python! This guide covers everything you need to get started — from setting up a local dev environment to getting your pull request merged.
- Code of Conduct
- Getting Started
- Project Structure
- Making Changes
- Submitting a Pull Request
- Issue Guidelines
- Release Process
- Getting Help
Be respectful, constructive, and patient. We're all here because we enjoy the game and want to build useful tools for the community. Harassment, insults, and unconstructive negativity won't be tolerated.
This project follows the Contributor Covenant Code of Conduct. By participating, you are expected to uphold it.
Found a security vulnerability? Please don't open a public issue — follow the private reporting process in SECURITY.md.
- Python 3.10+ (CI tests 3.10 through 3.14)
- uv — used for dependency management, virtual environments, and running tools
- Git
- Docker (optional) — for running a local swgoh-comlink service for integration tests
- Node.js (optional) — only for the StatCalc parity tests against the JavaScript reference implementation
- GitHub CLI : https://cli.github.com/
-
Fork and clone the repository
gh auth login gh repo fork swgoh-utils/comlink-python --clone cd comlink-pythonOr, without the GitHub CLI:
git clone https://github.com/<your-username>/comlink-python.git cd comlink-python
git remote add upstream https://github.com/swgoh-utils/comlink-python.git git config --local branch.develop.remote upstream # Safety net: pushes to "upstream" go to your fork instead git remote set-url --push upstream git@github.com:<your-username>/comlink-python.git
-
Install uv (if you don't have it)
# macOS / Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Windows powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
-
Install dependencies
uv synccreates the.venvvirtual environment automatically:uv sync --group dev
Run tools with
uv run <command>— there is no need to activate the virtual environment. -
Install the pre-commit hooks (runs Ruff, ty, and commitlint before each commit)
uvx pre-commit install --hook-type pre-commit --hook-type commit-msg
-
Verify the installation
uv run python -c "from swgoh_comlink import SwgohComlink; print('OK')"
Some tests (integration tests) require a running swgoh-comlink instance. The easiest way is Docker:
docker run -d --name swgoh-comlink -p 3000:3000 \
-e APP_NAME=comlink-python-dev \
ghcr.io/swgoh-utils/swgoh-comlink:4.5.0This starts comlink on http://localhost:3000, which is the default URL the library connects to. CI pins the same image tag (see .github/workflows/integration.yml).
Note: Unit tests should use mocks and never require a running comlink service. See Writing Tests below.
comlink-python/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml # Bug report template
│ │ ├── config.yml # Issue chooser links (security, upstream comlink, Discord)
│ │ ├── documentation.yml # Documentation issue template
│ │ └── feature_request.yml # Feature request template
│ ├── release-pr/ # Release PR bodies written by prepare-release.yml
│ ├── workflows/
│ │ ├── backmerge.yml # Stages the main → develop back-merge after a release
│ │ ├── ci.yml # CI pipeline (lint, type-check, test, docs, build)
│ │ ├── commitlint.yml # Commit message validation (npm-based)
│ │ ├── docs.yml # Publishes the docs site to GitHub Pages from main
│ │ ├── integration.yml # Integration tests against live comlink services
│ │ ├── labeler.yml # Auto-label PRs by file path
│ │ ├── prepare-release.yml # Regenerates CHANGELOG.md and stages a release branch
│ │ └── release.yml # Build → PyPI → tag + GitHub Release (version via hatch-vcs)
│ ├── CODEOWNERS # Code ownership
│ ├── dependabot.yml # Automated dependency updates
│ ├── labeler.yml # Label-to-path configuration
│ └── pull_request_template.md # PR checklist template
├── docs/ # MkDocs site (guides + API reference)
│ ├── api/ # API reference pages (rendered from docstrings)
│ ├── examples.md # Examples overview
│ ├── index.md # Documentation home
│ ├── logging.md # Logging configuration guide
│ └── migration.md # v1.x → v2.x migration guide
├── examples/ # Runnable usage examples (Sync/ and Async/)
├── parity/ # StatCalc parity harness against the JS reference implementation
├── src/
│ └── swgoh_comlink/
│ ├── StatCalc/
│ │ ├── data_builder/ # GameDataBuilder / GameDataBuilderAsync (game data transformation)
│ │ ├── calculator.py # StatCalc — local stat/GP calculator (sync)
│ │ └── calculator_async.py # StatCalcAsync
│ ├── helpers/ # Public helper functions, Constants, DataItems (focused private submodules)
│ ├── migrate/ # swgoh-migrate: v1 → v2 migration scanner and rules
│ ├── __init__.py # Package entry point, public exports
│ ├── _base.py # SwgohComlinkBase (shared sync/async logic, HMAC signing)
│ ├── exceptions.py # Custom exception classes
│ ├── globals.py # Logging configuration
│ ├── py.typed # PEP 561 marker: the package ships inline type hints
│ ├── retry.py # Retry/backoff configuration
│ ├── swgoh_comlink.py # SwgohComlink (sync client)
│ ├── swgoh_comlink_async.py # SwgohComlinkAsync (async client)
│ └── version.py # Exposes __version__ (derived from the Git tag via hatch-vcs)
├── tests/
│ ├── examples/ # Tests for the scripts in examples/
│ ├── integration/ # Tests requiring a running comlink service (opt-in)
│ ├── resources/ # Test fixture data (example-player.json, gameData.json, ...)
│ ├── statcalc/ # StatCalc parity tests (opt-in, need Node.js)
│ ├── unit/ # Mocked unit tests — the bulk of the suite
│ └── conftest.py # Shared fixtures and opt-in marker handling
├── CHANGELOG.md # Auto-generated from commit history
├── CITATION.cff # Citation metadata
├── CODE_OF_CONDUCT.md # Contributor Covenant code of conduct
├── CONTRIBUTING.md # This file
├── LICENSE # MIT License
├── README.md
├── SECURITY.md # Supported versions and vulnerability reporting
├── SUPPORT.md # Where to get help / report what
├── mkdocs.yml # Docs site configuration
├── pyproject.toml # Project metadata, build config, tool settings
└── uv.lock # Locked dependency versions
| Module | Purpose |
|---|---|
_base.py |
SwgohComlinkBase — shared payload construction, HMAC signing, and config for both clients |
swgoh_comlink.py |
SwgohComlink — synchronous HTTP client (all endpoint methods) |
swgoh_comlink_async.py |
SwgohComlinkAsync — asynchronous HTTP client (mirrors sync API) |
retry.py |
Retry and backoff behavior for transient HTTP failures |
StatCalc/calculator.py |
StatCalc — local stat and GP calculation for game units |
helpers/ |
DataItems IntFlag enum, Constants class, and utility functions split across focused submodules |
exceptions.py |
SwgohComlinkException, SwgohComlinkValueError, and SwgohComlinkTypeError |
globals.py |
Shared logging setup (get_logger()) |
version.py |
Exposes __version__, derived from the Git tag at build time via hatch-vcs |
| Branch | Purpose |
|---|---|
develop |
Integration branch. All contribution PRs target this branch. |
main |
Released code. Only release PRs (from develop) and back-merges touch it. |
For most contributions:
git checkout develop
git pull upstream develop
git checkout -b <type>/<short-description>Use a descriptive branch name following the pattern <type>/<description>:
fix/param-alias-falsy-values
feat/async-client
refactor/split-helpers-module
docs/contributing-guide
This project follows standard Python conventions, enforced by Ruff (lint + format) and ty (type checking). Please keep these in mind:
General principles:
- Follow PEP 8 for formatting —
uvx ruff formathandles this for you - Use complete type annotations on all functions, parameters, and return values — the package ships a
py.typedmarker, so downstream users' type checkers rely on them - Use
from __future__ import annotationsat the top of each module for modern annotation syntax - Use docstrings (Google style) on all public classes and methods — they are rendered into the API reference
- Keep lines to 120 characters max (the project doesn't enforce 79)
Naming:
- Public methods use
snake_case:get_player(),get_guild() - camelCase aliases exist for backward compatibility but don't add new ones — they are legacy
- Private/internal methods are prefixed with underscore:
_post(),_get_game_version() - Constants use
UPPER_SNAKE_CASE
Docstring format:
def get_player(
self,
allycode: str | int | None = None,
player_id: str | None = None,
enums: bool = False,
) -> dict[str, Any]:
"""
Get player information from game. Either allycode or player_id must be provided.
Args:
allycode: integer or string representing player allycode
player_id: string representing player game ID
enums: boolean [Defaults to False]
Returns:
A dictionary containing the player information.
"""Imports:
- Standard library imports first, then third-party, then local — separated by blank lines (Ruff sorts them)
- Use
from __future__ import annotationsat the top of each module - Prefer specific imports over wildcard:
from json import dumps, loads
Tests live in the tests/ directory and use pytest (configured in pyproject.toml), with pytest-httpx for mocking HTTP and pytest-asyncio (in auto mode) for async tests. Run tests with:
# Run the default (mocked) suite
uv run pytest tests/
# Run a specific test file
uv run pytest tests/unit/test_client_requests.py
# Run with verbose output and coverage
uv run pytest tests/ -v --cov=swgoh_comlink --cov-report=term-missingTest categories:
| Type | Location | Requires | How to run |
|---|---|---|---|
| Unit | tests/unit/ |
Nothing | Runs by default |
| Integration | tests/integration/ |
Comlink on localhost:3000 (and 3001 for HMAC) |
RUN_INTEGRATION_TESTS=1 uv run pytest tests/integration/ |
| Parity | tests/statcalc/ |
Node.js | RUN_PARITY_TESTS=1 uv run pytest tests/statcalc/ |
All new code should have unit tests. Opt-in categories are skipped automatically unless their environment variable is set.
Writing a unit test (preferred):
Mock the HTTP layer with the httpx_mock fixture so tests run anywhere without a comlink service. The comlink and call fixtures in tests/unit/conftest.py are parametrized over both SwgohComlink and SwgohComlinkAsync, so one test body covers both clients:
import json
from pytest_httpx import HTTPXMock
async def test_get_player_by_allycode(comlink, call, httpx_mock: HTTPXMock) -> None:
"""get_player() builds the correct payload for an allycode lookup."""
httpx_mock.add_response(json={"name": "TestPlayer"})
result = await call(comlink, "get_player", allycode=123456789)
request = httpx_mock.get_request()
assert request is not None
assert str(request.url) == "http://localhost:3000/player"
assert json.loads(request.content) == {"payload": {"allyCode": "123456789"}, "enums": False}
assert result == {"name": "TestPlayer"}Test file naming: test_<feature_or_module>.py
What to test:
- Payload construction — verify the correct JSON payload is built for each method
- Parameter validation — edge cases, invalid inputs, boundary values
- Error handling — how the client handles HTTP errors, bad JSON, connection failures
- Helper functions — each utility function in the
helpers/package should have its own tests - Sync/async parity — new client behavior should be exercised against both clients
This project uses the Conventional Commits / Angular convention with git-changelog to auto-generate CHANGELOG.md. Your commit messages directly become release notes, so please follow this format:
<type>(<scope>): <short description>
Types that appear in the CHANGELOG:
| Type | Use for | CHANGELOG section |
|---|---|---|
feat |
New features or capabilities | Features |
fix |
Bug fixes | Bug Fixes |
perf |
Performance improvements | Performance Improvements |
refactor |
Code restructuring (no behavior change) | Code Refactoring |
revert |
Reverting an earlier change (revert: <original subject>) |
Reverts |
docs |
Documentation additions or updates | Docs |
Other accepted types — build, deps, chore, test, style, ci — are valid but not included in the generated changelog.
Changelog.
CHANGELOG.mdis generated bygit-changelogfrom Conventional Commit messages — do not edit it by hand. Preview locally withuv run git-changelog --bumped-versionanduv run git-changelog. See Release Process for how releases are cut.
Scope is optional but encouraged. Common scopes:
core— changes to the client classes (swgoh_comlink.py,swgoh_comlink_async.py,_base.py)helpers— changes to thehelpers/packagestatcalc— changes to theStatCalc/packagehmac,async,exceptions,typing— focused areas of the clientsdeps,ci,docs,tests,examples,release
Types and scopes are defined in .commitlintrc.json, the single config used by both the
pre-commit commit-msg hook and the CI Commit Lint check. Unknown scopes produce a warning, not a failure.
Examples:
# Good
feat(core): add verify_ssl parameter to SwgohComlink constructor
fix(helpers): remove debug print statement from human_time()
refactor(core): generalize _post() into _request() for GET/POST support
fix: correct param_alias decorator to handle falsy values
chore(deps): bump httpx to 0.28.1
docs: add contributing guide
test: add mocked unit tests for get_player and get_guild
# Bad — too vague, not conventional
updated stuff
fix bug
changesMulti-line commits (for complex changes):
feat(core): add async client support
Introduces SwgohComlinkAsync using httpx for non-blocking API calls.
Extracts shared payload construction into _ComlinkBase mixin.
Closes #12
Mark breaking changes with a BREAKING CHANGE: <description> footer — this drives the major version bump.
Don't use the feat!: shorthand: the changelog generator's Angular convention doesn't recognize it, so the
commit would be left out of the changelog entirely.
-
Sync your fork with upstream and rebase your branch:
git fetch upstream git rebase upstream/develop
If you have merge conflicts, resolve them locally before pushing.
-
Run the checks locally (the pre-commit hooks cover most of this):
# Lint and format uvx ruff check src/ tests/ uvx ruff format --check src/ tests/ # Type check uv run ty check src/swgoh_comlink/ # Tests uv run pytest tests/
-
Push your branch to your fork:
git push origin <your-branch-name>
-
Open a pull request against
develop:Using GitHub CLI:
gh pr create --repo swgoh-utils/comlink-python --base develop --fill
Or go to github.com/swgoh-utils/comlink-python/pulls and click "New pull request" → "compare across forks" → select
developas the base and your fork and branch as the head. -
Fill in the PR template. It asks for:
- What the change does and why
- Which issue it addresses (e.g., "Closes #51")
- Any breaking changes or migration notes
- How you tested the change
- A short checklist mirroring the CI checks
-
After submitting:
CI checks (lint, type check, tests on Python 3.10–3.14, docs build, package build, commit message validation, and integration tests) run automatically. Address any failures before requesting review — the maintainer is automatically assigned via CODEOWNERS when a PR is opened.
If you need to update your PR after feedback, push additional commits to the same branch on your fork. The PR updates automatically:
# Make changes, then: git add . git commit -m "fix(core): address review feedback" git push origin <your-branch-name>
Before opening an issue, check the existing issues to avoid duplicates. Usage questions are better asked on Discord — see SUPPORT.md.
Bug reports should include:
- Python version (
python --version) - Package version (
python -c "import swgoh_comlink; print(swgoh_comlink.version)") - Comlink service version (if relevant)
- Minimal code to reproduce the issue
- Expected vs actual behavior
- Full traceback (if applicable)
Never paste your comlink access_key / secret_key into an issue.
Feature requests should include:
- What you're trying to accomplish
- How you're currently working around it (if applicable)
- A proposed API or approach (if you have one in mind)
Labels used in this project:
| Label | Meaning |
|---|---|
bug |
Something isn't working correctly |
enhancement |
New feature or capability |
documentation |
Improvements or additions to documentation |
question |
Further information is requested |
good first issue |
Good for newcomers |
help wanted |
Extra attention is needed |
code maintenance |
Internal cleanup, refactoring, tech debt |
test |
Test coverage or infrastructure |
dependencies, ci, build |
Dependency, CI, and build changes (mostly applied automatically) |
Releases are handled by the maintainer through GitHub Actions. Contributors don't need to manage versioning or releases, but here's how it works for reference:
- prepare release (
prepare-release.yml) is dispatched ondevelop. It computes the next version from the Conventional Commits since the last tag (or a chosenpatch/minor/majorbump), regeneratesCHANGELOG.md, and pushes arelease/vX.Y.Zbranch. - The maintainer opens the release PR from that branch into
main(a link is in the workflow summary). CI runs as normal and the PR is merged. - comlink-python release (
release.yml) is dispatched onmain. It reads the version fromCHANGELOG.md, builds the package — hatch-vcs stamps that version, so no version string is committed to the repo — and publishes to PyPI via trusted publishing. - Only after a successful publish is the Git tag created and a GitHub Release cut, with release notes auto-generated from the PR history.
- back-merge main into develop (
backmerge.yml) then stages a branch somaincan be merged back intodevelop.
This is why conventional commit messages matter — they become the changelog and drive the version bump.
Version scheme: Semantic Versioning
- Patch (2.4.x): Bug fixes, documentation
- Minor (2.x.0): New features, backward-compatible changes
- Major (x.0.0): Breaking API changes
- Docs: swgoh-utils.github.io/comlink-python
- Issues: github.com/swgoh-utils/comlink-python/issues
- Discord: Join the server for real-time discussion
- Wiki: swgoh-comlink wiki for general comlink documentation
Thank you for contributing! Every fix, feature, test, and docs improvement helps the SWGOH developer community.