Contributing to pyvesync↲
Contributions are welcome! Please follow the guidelines below to ensure a quick and smooth review process.
Getting Started↲
Install the Development Environment↲
# Clone the repository
git clone https://github.com/webdjoe/pyvesync.git
cd pyvesync
# Create a virtual environment (Python 3.11+)
python -m venv venv
source venv/bin/activate # Linux/macOS
# .\venv\Scripts\activate.ps1 # Windows PowerShell
# Install with dev dependencies
pip install -e .[dev]
Pre-commit Hooks↲
The project uses pre-commit to enforce code quality on every commit. The pre-commit.ci service also runs these checks automatically on pull requests.
The hooks include:
- check-yaml / check-toml / check-ast - Validates file syntax
- trailing-whitespace / end-of-file-fixer - Whitespace cleanup
- mypy - Static type checking
- ruff-check - Linting with auto-fix
- ruff-format - Code formatting
To install and run pre-commit locally:
pre-commit install # Install hooks (runs on every git commit)
pre-commit run # Run on staged files only
pre-commit run --all-files # Run on all files
Note
Changes must be staged (git add) before running pre-commit run for it to check the correct files.
Pull Request Process↲
Semantic PR Titles↲
Pull request titles must follow the Conventional Commits format. This is enforced by a GitHub Action on all PRs. Valid prefixes:
feat:- New feature or device supportfix:- Bug fixdocs:- Documentation changesrefactor:- Code refactoring (no functional change)test:- Adding or updating testschore:- Maintenance, dependency updates, CI changes
Examples:
feat: Add support for LAP-C601S air purifier
fix: Handle token expiration during device update
docs: Update contributing guidelines
What Happens on a PR↲
When you open a pull request targeting master or dev, the Run Linting and Unit Tests workflow runs automatically:
- Ruff - Lints the codebase with
ruff check --output-format=github - Pylint - Runs pylint on
src/pyvesync - Pytest - Runs the full test suite across Python 3.11, 3.12, and 3.13
- MkDocs Build - Builds the documentation (only on PRs to
master, Python 3.12)
All four checks must pass for the PR to be merged.
Code Style↲
Ruff Configuration↲
The project uses ruff as the primary linter and formatter. The configuration is in ruff.toml with the following key settings:
- Line length: 90 characters
- Indent: 4 spaces
- Rule selection:
ALL(all rules enabled, with specific ignores) - Docstring convention: Google style
- Quote style: Single quotes (double quotes for docstrings)
Ruff runs with auto-fix enabled in pre-commit, so many issues are corrected automatically on commit.
General Style Guidelines↲
-
Quotes: Single quotes for all strings. Double quotes for docstrings.
-
String formatting: Use f-strings.
-
Type hints: Required for all function signatures. Use
|union syntax (Python 3.10+), notUnionorOptional. Usefrom __future__ import annotationsat the top of every module. -
TYPE_CHECKING imports: Imports used only for type hints should be guarded behind
if TYPE_CHECKING:to avoid circular imports at runtime. -
Import ordering: Imports are grouped and sorted by: (1)
__future__, (2) standard library, (3) third-party, (4) local imports. Each group is separated by a blank line. -
Docstrings: Required for all public classes, methods, and functions. Use Google-style format. Not required for inherited/overridden methods.
class OutletState(DeviceState): """Base state class for Outlets. This class holds all of the state information for the outlet devices. Args: device (VeSyncOutlet): The device object. details (ResponseDeviceDetailsModel): The device details. feature_map (OutletMap): The feature map for the device. Attributes: energy (float): Energy usage in kWh. power (float): Power usage in Watts. voltage (float): Voltage in Volts. Note: Not all attributes are available on all devices. """ -
Line length: 90 characters maximum.
-
Naming conventions:
Element Convention Example Classes PascalCase VeSyncOutlet7A,OutletStateMethods / functions snake_case turn_on(),set_brightness()Properties snake_case device_status,fan_levelConstants UPPER_SNAKE_CASE DEFAULT_TZ,STATUS_OKEnums PascalCase class, UPPER members DeviceStatus.ONModule-level logger loggerlogger = logging.getLogger(__name__) -
Constants: All constants, default values, and device modes must be defined in the
pyvesync.constmodule. No hardcoded strings or magic numbers in device code. UseStrEnumorIntEnumfor enum values. -
__slots__: Used on state classes and theVeSyncmanager class to restrict attribute creation and improve memory usage.
Device Method and Attribute Naming↲
- All states specific to a device type must be stored in the
DeviceStatesubclass in the base device type module. For example,SwitchStatefor switches,PurifierStatefor purifiers, etc. - All device properties and methods are to be created in the specific device type base class, not in the implementation device class.
-
Binary state methods follow this naming pattern:
Pattern Usage Example turn_on()/turn_off()Power on/off Inherited from VeSyncBaseToggleDeviceturn_<state>_on()/turn_<state>_off()Named binary state turn_child_lock_on(),turn_child_lock_off()toggle_<state>(bool)Toggle binary state toggle_child_lock(),toggle_display()set_<attribute>(value)Set non-binary state set_brightness(),set_fan_level() -
The
turn_on()andturn_off()methods are specific to power and call thetoggle_switch()method internally.
Models Directory↲
Data model files in pyvesync/models/ have relaxed naming rules (N803, N804, N802, N815 ignored) because model field names must match the VeSync API's JSON keys exactly (e.g., traceId, accountID, configModule).
Testing and Linting↲
Running Tests Locally↲
# Run all tests
pytest
# Run a specific test file
pytest src/tests/test_outlets.py
# Write API fixtures for new devices
pytest --write_api
pytest --write_api --overwrite # Overwrite existing fixtures
Running with Tox↲
For convenience, tox can be used to run tests and linting. This requires tox to be installed in your Python environment.
# Run all environments
tox
# Specific environments
tox -e 3.11 # Run tests with Python 3.11
tox -e 3.12 # Run tests with Python 3.12
tox -e 3.13 # Run tests with Python 3.13
tox -e lint # Run pylint checks
tox -e flake8 # Run flake8 checks
tox -e ruff # Run ruff checks
tox -e mypy # Run mypy type checks
Running Linters Directly↲
ruff check src/pyvesync # Lint
ruff format src/pyvesync # Format
mypy src/pyvesync # Type check
pylint src/pyvesync # Pylint
See the Testing documentation for details on the test architecture, fixtures, and adding tests for new devices.
Release Process↲
Releases are triggered automatically when code is merged to master. The Release and Publish workflow:
- Extracts the version from
pyproject.toml - Validates the new version is greater than the latest git tag
- Builds the distribution package (
python -m build) - Creates a GitHub Release with auto-generated release notes and the version as the tag (e.g.,
3.4.1) - Publishes to PyPI via the
pypa/gh-action-pypi-publishaction - Deploys documentation using
miketo GitHub Pages, updating thelatestalias
Versioning↲
The project version is defined in pyproject.toml under [project].version. The version follows semantic versioning:
- Major (x.0.0) - Breaking changes
- Minor (0.x.0) - New features, new device support
- Patch (0.0.x) - Bug fixes
When preparing a release PR to master, bump the version in pyproject.toml. The release workflow will fail if the new version is not greater than the previous tag.
Documentation Deployment↲
Documentation is built with MkDocs and deployed to GitHub Pages using mike for version management. Each release creates a versioned deployment and updates the latest alias. The documentation site is at pyvesync.github.io.
Dependency Management↲
Dependabot is configured to check for updates weekly to both pip dependencies and GitHub Actions versions.
Requests to Add Devices↲
Please see Capturing for instructions on how to capture the necessary information to add a new device.