Code Style
VoiceGateway enforces consistent code style through automated tooling. This page documents the rules and conventions.Tooling overview
| Tool | Purpose | Config location |
|---|---|---|
| ruff | Linting + formatting (replaces flake8, isort, Black) | pyproject.toml [tool.ruff] |
| mypy | Static type checking | pyproject.toml [tool.mypy] |
| pre-commit | Runs checks before each commit | .pre-commit-config.yaml |
Ruff
Ruff handles both linting and formatting. It is configured inpyproject.toml:
Running ruff
Import sorting
Ruff’sI rule handles import sorting (replacing isort). Imports are grouped in this order:
- Standard library (
import os,from typing import ...) - Third-party (
import pytest,from fastapi import ...) - Local (
from voicegateway.core import ...)
import statements come before from ... import.
mypy
Static type checking catches bugs before runtime. VoiceGateway’s mypy config:Type annotation guidelines
- All public functions must have type annotations
- Use
from __future__ import annotationsat the top of every module (enables PEP 604X | Ysyntax) - Use
dict,list,tuple(lowercase) instead ofDict,List,Tuplefromtyping - Use
X | Noneinstead ofOptional[X] - Use
TYPE_CHECKINGguards for import-only types to avoid circular imports:
Docstrings
Use Google-style docstrings for all public classes, methods, and functions:- First line is a concise imperative summary (no period for one-liners)
- Blank line between summary and
Args/Returns/Raisessections Args,Returns,Raisessections as needed- Private methods (
_foo) may use shorter docstrings
Conventional Commits
All commit messages must follow Conventional Commits:Types
| Type | When to use |
|---|---|
feat | New feature |
fix | Bug fix |
docs | Documentation only |
test | Adding or modifying tests |
refactor | Code change that neither fixes a bug nor adds a feature |
perf | Performance improvement |
chore | Build process, dependency updates, tooling |
ci | CI/CD changes |
Scopes
Use the module or area affected:core,providers,middleware,storage,pricingdashboard,mcp,cli,serverconfig,docker- Provider names:
openai,deepgram, etc.
Examples
Multi-scope commits
If a change spans multiple scopes, list the primary scope and mention others in the body:File organization
- One class per file for providers (
openai_provider.py, notproviders.py). - Group related functions in a module (
middleware/cost_tracker.py). - Keep
__init__.pyfiles minimal — a docstring, re-exports of the subpackage’s public API, and an__all__declaration. Nothing else. - Use
from __future__ import annotationsin every module.
Internal modules
Files whose names start with a leading underscore are internal implementation details and not part of the public import surface:src/voicegateway/_version.py— hatch-vcs generated, do not edit.src/voicegateway/tests/fixtures/streaming/_loader.py— private test helper.src/voicegateway/inference/_llm.py,_stt.py,_tts.py— private factories; the public surface isvoicegateway.inference.{LLM,STT,TTS}.
src/voicegateway/ imports a leading-underscore module from a different
subpackage; for now the convention is documentation-only.
Public API contract
Every package and subpackage__init__.py declares an explicit
__all__ list. This is the public surface:
__all__ is the empty list (__all__: list[str] = []), the
subpackage exposes nothing at its top level and callers reach into
submodules directly:
__all__, and any leading-underscore module, are
internal. They may be renamed or removed in any minor release without
a deprecation cycle.
Module-level patterns
The codebase converged on a small set of patterns. New code should follow them unless there is a concrete reason not to.typing.Protocol vs ABC
Prefer typing.Protocol for structural typing where multiple
implementations need to satisfy an interface without sharing helper
code (see src/voicegateway/cli/tui/data for a real example — the
DataClient Protocol is satisfied by both HttpClient and
LocalClient without inheritance). Use an abstract base class only
when the base genuinely supplies shared behaviour
(src/voicegateway/providers/base.py’s BaseProvider is the
canonical example: every concrete provider inherits real helper
methods).
Pydantic for config
Anything parsed from YAML or environment variables is a Pydantic model. Seesrc/voicegateway/core/config.py and
src/voicegateway/core/schema.py for the project-wide config shape;
the validators there are the single source of truth for what
voicegw.yaml accepts.
Async throughout
Every I/O path usesasync / await. Storage reads, provider
calls, HTTP handlers, MCP tools, the dashboard backend — all async.
Synchronous helpers exist only for pure data transformation (parsing,
formatting). When in doubt, make it async; mixing sync and async
boundaries is the most common source of subtle bugs in this codebase.
Exception handling
Catch specific exception types where possible.except Exception is
acceptable at top-level boundaries (provider call sites, MCP tool
dispatch, middleware fallback) where the catch is paired with structured
logging and a controlled fallback. Avoid broad excepts in narrow code
paths — they hide real bugs and bypass the type system.
Test patterns
See Testing for the full guide. Quick reference:- Tests live under
src/voicegateway/tests/mirroring the package layout (src/voicegateway/tests/middleware/forsrc/voicegateway/middleware/tests, etc.). pytest+pytest-asynciowithasyncio_mode = "auto"(configured inpyproject.toml). No@pytest.mark.asynciois needed; async tests are detected automatically.- Shared fixtures live in
src/voicegateway/tests/conftest.py. Per-subpackage fixtures live in that subpackage’sconftest.py. - File-name pattern:
test_<thing-under-test>.py. Function-name pattern:test_<behaviour>. - Subprocess CLI tests use the patterns in
src/voicegateway/tests/cli/test_record_streaming_fixtures_cli.py. - Coverage stays at or above the project gate (see
[tool.coverage.run]inpyproject.toml).
Naming conventions
| Item | Convention | Example |
|---|---|---|
| Modules | snake_case | cost_tracker.py |
| Classes | PascalCase | CostTracker |
| Functions | snake_case | create_provider |
| Constants | UPPER_SNAKE_CASE | DEFAULT_DB_PATH |
| Private | _leading_underscore | _PROVIDER_REGISTRY |
| Type vars | PascalCase or single letter | T |