Contributing
Thanks for your interest in contributing to AirVPN! To keep the library consistent and reliable, please follow the guidelines below before opening a pull request.
Requirements for new additions
Any new addition — a new method, service, or exception — needs to include:
- A test function. New behavior should be covered by a corresponding test. See tests for how the test suite is structured and how to run it locally.
- Proper documentation. Public methods, classes, and exceptions should have complete docstrings (see the existing services for the expected format:
Args,Returns, andRaisessections where applicable). You don't need to manually add a page underdocs/for a new module or class —gen_pages.pywalks theairvpnpackage on build and generates anmkdocstringspage for every submodule and package (via each__init__.py) automatically, mirroring the package's folder structure. As long as your addition lives inside theairvpnpackage and has a docstring, it'll show up in the nav on its own.
Pull requests that add functionality without either of these will be asked for changes before merging.
Documentation generation
A quick note on how the docs site is built, since it affects how your additions will appear:
- Every file and folder inside
airvpn/gets its own generated page (<name>.md) titled after the module, with anmkdocstringsmodule.pathdirective pulling in your docstrings. Folders (packages) get anindex.mdgenerated from their__init__.py. - The page title defaults to the module's name, snake_case-split into words and capitalized (e.g.
dns_lists→ "Dns lists"). If you want a specific display name instead, set__title__ = "..."in the module — the generator will use that if present. Note that__title__only affects the nav label and page heading; it doesn't change themodule.pathreference shown under the heading, so the module's actual dotted path is still visible on the page regardless. - Guide pages under
docs/guides/are picked up the same way and added under the "Guides" nav section, withdocs/guides/index.mdbecoming the "Guides" landing page itself rather than a nested entry. Guide filenames also have underscores converted to spaces for the nav title (e.g.getting_started.md→ "Getting started"), matching the module title behavior. - Module-level docstrings matter, not just class/method ones. Because each generated page uses
module.path(anmkdocstringsdirective that renders everything in that module), any docstring placed at the top of the file — before your imports/classes — will be rendered on the page too, typically as introductory text above the class/function documentation. If you're adding a new module, consider adding a short module docstring describing its purpose; it'll show up on the generated page for free and gives readers context before they hit the API reference.
If you add a new top-level guide or module, you shouldn't need to touch gen_pages.py or summary.md at all — just make sure the file lands in the right place (airvpn/ for code, docs/guides/ for guides) with a proper docstring or content.
Workflow
- Fork the repository and create a branch for your change.
- Make your changes, including a test function and documentation as described above.
- Run the test suite locally to confirm everything passes — see tests for instructions.
- Open a pull request describing what changed and why.
Style
- Follow the existing code style and docstring conventions already used throughout the codebase.
- Keep exceptions consistent with the existing hierarchy (see exceptions.md) — prefer raising a specific, well-documented exception over a generic one.
- Prefer small, focused pull requests over large ones covering multiple unrelated changes.
Naming conventions
To keep the codebase predictable, please follow the conventions already established:
- Classes use
PascalCase(e.g.Devices,Generator,ConfigList). - Methods, functions, and variables use
snake_case(e.g.create_config,send_notification,output_dir). - Enums use
PascalCasefor the class andUPPER_CASEfor members (e.g.DeviceAction.LIST,DeviceAction.ADD). - Exceptions:
- Base exceptions for a subsystem end in
Exception(e.g.DeviceException,GeneratorException), and should subclassAirVPNException. - Specific exceptions end in
Errorand describe the failure, not the subsystem twice —DeviceAPIError, notDeviceAPIException(e.g.DeviceAPIError,GeneratorResponseError). - Prefix specific exceptions with the subsystem they belong to, so it's clear which base exception they fall under at a glance.
- Private/internal attributes and methods are prefixed with a single underscore (e.g.
_diff,_cache_devices,_create_config), consistent with the rest of the codebase. - Files and modules use
snake_casematching their primary export where practical (e.g.models.py,exceptions.py). Since module/page titles are auto-generated from the filename (see "Documentation generation" above), avoid overly abbreviated or ambiguous names — or set__title__if the auto-generated title wouldn't read well.
If you're adding something that doesn't cleanly fit an existing pattern, match the convention of the nearest analogous class or exception rather than introducing a new one.
If you're unsure whether a change fits the scope of the project, feel free to open an issue first to discuss it before submitting a pull request.