From bf4e309a698592cdb815580f97db97b268fd6668 Mon Sep 17 00:00:00 2001 From: Roberto Bertó <463349+robertoberto@users.noreply.github.com> Date: Tue, 19 May 2026 02:39:01 +0000 Subject: docs: prepare v0.4.0 release Rewrite the README around the supported public API, add a public API stability policy, document the deprecation timeline, and refresh the CHANGELOG with the full 0.4.0 scope. Add a short CONTRIBUTING guide. Apply minor pending fixes to LICENSE and .env.example. Fix a couple of verify=False antipatterns in the docs/ pages. README: - Badges (PyPI version, Python versions, license, CI). - Quick start that actually runs (no missing import, no global disable_warnings, correct VYDEVICE_VERIFY_SSL parse). - Environment variable table with defaults. - ApiResponse contract documented as a dataclass. - Compact API overview (configure / retrieve / show / generate / reset / config-file / system / image). - Public API stability section with the 0.4 -> 0.5 -> 0.6 -> 1.0 deprecation timeline. - Logging section: NullHandler default, pyvyos logger, redacted key. - VyOS compatibility note (1.4 LTS, 1.5 rolling). - Development with uv; optional pre-commit. CHANGELOG: - Consolidated 0.4.0 entry covering both the architecture refactor and this cleanup, since 0.4.0 was tagged in git but never published. - Explicit Added / Changed / Removed / Fixed / Compatibility / Notes sections. - 0.3.0 entry annotated as 'tagged but never released to PyPI'. CONTRIBUTING.md: - Scope statement (thin HTTPS API wrapper). - Pull request rules; explicit 'do not change HTTP payload in passing'. - Development setup with uv. - Public API stability summary. Fixes from the previous review-fixes branch: - LICENSE copyright now reads 'GravScale, Roberto Berto'. - .env.example default flipped to VYDEVICE_VERIFY_SSL=true with field descriptions. - docs/getting-started.md: VERIFY_SSL default to true; urllib3 disable_warnings now passes the specific InsecureRequestWarning. - docs/index.md: quick example uses verify=True; removed RTD link (RTD config was removed in this release because the build never worked). --- .env.example | 16 ++- CHANGELOG.md | 109 +++++++++++++------ CONTRIBUTING.md | 71 +++++++++++++ LICENSE | 2 +- README.md | 272 +++++++++++++++++++++++++++++++----------------- docs/getting-started.md | 4 +- docs/index.md | 4 +- 7 files changed, 342 insertions(+), 136 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.env.example b/.env.example index 328f4c0..d3a2448 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,19 @@ +# pyvyos device configuration. +# Copy this file to .env and fill in the values. + +# Hostname or IP address of the VyOS device. VYDEVICE_HOSTNAME= + +# API key configured on the device with: +# set service https api keys id key VYDEVICE_APIKEY= + +# HTTPS port of the VyOS API (default: 443). VYDEVICE_PORT=443 + +# Protocol: "https" (recommended) or "http". VYDEVICE_PROTOCOL=https -VYDEVICE_VERIFY_SSL=False \ No newline at end of file + +# Verify the TLS certificate of the device. +# Set to "false" ONLY in lab environments using self-signed certificates. +VYDEVICE_VERIFY_SSL=true diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b9314..3132e02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,50 +5,97 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.4.0] - 2025-01-XX +## [0.4.0] - 2025-11-20 + +`0.4.0` is a cleanup and consolidation release. It is the first version +since `0.2.2` published to PyPI. The git tag `v0.4.0` previously pointed at +the intermediate refactor commit `6b4e901`; it has been moved to the +release commit that includes the cleanup described below. + +This release **does not change HTTP payload generation or request/response +behavior**. ### Added -- Exception hierarchy (`SDKError`, `HttpError`, `ApiError`, `ValidationError`) in `pyvyos.exceptions` -- Utility functions in `pyvyos.utils`: - - `json.redact_key()` and `json.safe_dumps()` for secure JSON handling - - `ids.request_id()` for request tracing - - `paths.build_path()` for building configuration paths -- Structured logging in `RestClient` with request ID tracking and elapsed time -- Optional Pydantic validation models in `pyvyos.specs.commands.*` for request/response validation -- Development documentation: - - Architecture guide (`docs/development/architecture.md`) - - Refactor roadmap (`docs/development/refactor-roadmap.md`) - - Quality and utils guidelines (`docs/development/quality-and-utils.md`) -- Comprehensive test suite for backward compatibility (19 tests for shims, 16 tests for utils, 6 tests for exceptions) -- `[tool.uv] package = true` in `pyproject.toml` for editable installation via `uv sync` +- `pyvyos/py.typed` PEP 561 marker, advertising the package as typed. +- `.pre-commit-config.yaml` with neutral hooks (trailing whitespace, + end-of-file fixer, YAML/TOML syntax check, large-file guard). +- GitHub Actions ecosystem entry in `.github/dependabot.yml` so workflow + versions stay current. +- Documented public API stability and deprecation timeline in `README.md`. ### Changed -- Moved JSON API specifications from `pyvyos/vyos-api/` to `docs/development/vyos_api/` (reference only) -- Updated `pyproject.toml` to include optional `validation` dependency group for Pydantic -- Enhanced `RestClient` logging with structured fields (request_id, command, op, status, elapsed_ms) -- Refactored internal structure to `pyvyos.core.*` while maintaining full backward compatibility via shims +- `pyvyos.core.*` is now the internal implementation layer; the supported + public API is `from pyvyos import VyDevice, ApiResponse`. The legacy + `pyvyos.device` and `pyvyos.rest` shims continue to work without + warnings. +- `RestClient` logging is now plain and structured: command, op, status, + elapsed milliseconds. No request-ID field. +- `pyproject.toml`: + - declared the wheel package explicitly, + - removed duplicated dependency blocks, + - dropped the unused `validation` extra, + - raised the pytest floor for Python 3.13. +- PR validation workflow upgraded to `actions/checkout@v4`, + `actions/setup-python@v5`, and `astral-sh/setup-uv@v3`. +- `.env.example` default flipped to `VYDEVICE_VERIFY_SSL=true`; commented + with field descriptions. + +### Removed +- `pyvyos.specs` package (experimental Pydantic models). It was never + imported by the runtime and had 0% test coverage. Pydantic is no longer + an optional dependency. +- `pyvyos.exceptions` module (`SDKError`, `HttpError`, `ApiError`, + `ValidationError`). The hierarchy was defined but never raised anywhere + in the codebase. Error reporting continues through `ApiResponse.error`. +- `pyvyos.utils.ids.request_id` helper. The generated UUIDs were attached + to log records but never propagated to callers — half-implemented + tracing is worse than none. It will be reintroduced if and when real + observability hooks land. +- `pyvyos.utils` no longer re-exports `request_id`. +- Obsolete tooling: `Makefile` (hard-coded `env/bin/python`), + `run_tests.sh`, `run_tests.py`. +- Dead workflow `.github/workflows/python-app.yml` (Python 3.12, only ran + flake8 with pytest commented out, referenced a non-existent + `requirements.txt`). +- `sphinx/` source tree and `.readthedocs.yaml`: the RTD configuration + pointed to `docs/source/conf.py` while the Sphinx tree lived under + `sphinx/source/`, so the build never worked and no documentation was + ever published. The hand-written Markdown docs under `docs/` are + retained. +- Stale development notes under `docs/development/` (architecture, + refactor roadmap, quality-and-utils) — they described the pre-cleanup + proposal that included specs/exceptions/request_id. +- Tests for the removed modules (`tests/test_exceptions.py`, + `tests/utils/test_ids.py`). ### Fixed -- **Fixed #25**: `config_file_save()` and `config_file_load()` now correctly include `path: []` in payload as required by VyOS API -- Path parameter handling for `config-file` commands (always includes `path: []`) -- Secret redaction in logs and sanitized payloads +- `LICENSE` copyright now reads `2023 GravScale, Roberto Bertó`. -### Security -- API keys are automatically redacted in logs and response payloads +### Compatibility +- Public imports are unchanged: + - `from pyvyos import VyDevice, ApiResponse` + - `from pyvyos.device import VyDevice` + - `from pyvyos.rest import RestClient, ApiResponse` +- HTTP payload generation is unchanged. +- Request and response behaviour is unchanged. +- No compatibility shim has been deprecated in this release. ### Notes -- This release maintains 100% backward compatibility with version 0.3.0 -- All existing imports and code will continue to work without changes -- New internal structure (`pyvyos.core.*`) is available but not required for existing code +- This release prepares the project for the upcoming `0.5.x` work: + contract tests for public payloads, fixes to the public method edges + (`image_add`/`image_delete`, `timeout`, mutable defaults), stdlib-based + validators, and a tidier internal core. ## [0.3.0] - 2024-XX-XX +Tagged in git but never published to PyPI. Released to PyPI as part of +`0.4.0`. + ### Added -- Initial release -- Core functionality for VyOS REST API interaction -- Support for configure, retrieve, show, generate, reset, config-file, reboot, poweroff, and image operations +- Initial public release of the SDK structure. +- Core functionality for VyOS HTTPS API: configure, retrieve, show, + generate, reset, config-file, reboot, poweroff, and image operations. [Unreleased]: https://github.com/vyos-contrib/pyvyos/compare/v0.4.0...HEAD -[0.4.0]: https://github.com/vyos-contrib/pyvyos/compare/v0.3.0...v0.4.0 +[0.4.0]: https://github.com/vyos-contrib/pyvyos/compare/v0.2.2...v0.4.0 [0.3.0]: https://github.com/vyos-contrib/pyvyos/releases/tag/v0.3.0 - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2bfaaa0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing to pyvyos + +Thanks for considering a contribution. `pyvyos` is a small library with a +narrow scope, which makes review and maintenance easier when contributions +follow a few simple rules. + +## Scope + +`pyvyos` is a thin Python wrapper around the VyOS HTTPS API. Changes that +fit naturally into this scope are welcome. Anything that adds a new +dependency, introduces a parallel client (SSH, NETCONF, …), or expands +beyond the HTTPS API surface is best discussed in an issue first. + +## Before opening a pull request + +1. **Open an issue first** for anything beyond a small fix or a typo. It + saves everyone time if the direction is agreed before the patch lands. +2. **Keep the diff focused.** One topic per pull request. Refactors and + bug fixes are easier to review separately. +3. **Do not change HTTP payload generation in passing.** Payload changes + require a deliberate review pass; they should be their own pull + request and include tests. +4. **Run the test suite locally:** `uv run pytest`. +5. **Match the existing style.** No formatter is enforced yet; just keep + diffs minimal and readable. + +## Development setup + +```bash +git clone https://github.com/vyos-contrib/pyvyos.git +cd pyvyos +uv sync --extra dev +uv run pytest +``` + +Optional pre-commit hooks (whitespace, EOF, YAML/TOML syntax): + +```bash +pip install pre-commit +pre-commit install +``` + +## Public API stability + +The supported public API is: + +```python +from pyvyos import VyDevice, ApiResponse +``` + +Compatibility shims at `pyvyos.device` and `pyvyos.rest` are kept while +the cost is trivial. Anything under `pyvyos.core.*` is internal and may +change between minor releases. + +If your change touches the public API surface, please flag it explicitly +in the pull request description. + +## Reporting bugs + +When reporting a bug, please include: + +- The `pyvyos` version (`pip show pyvyos`). +- The Python version. +- The VyOS version of the device. +- A minimal reproducer and the resulting error (or, for behaviour bugs, + the observed vs expected behaviour). + +## License + +By contributing you agree that your contribution will be licensed under +the [MIT License](LICENSE). diff --git a/LICENSE b/LICENSE index 6b976b2..9784c86 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 GravScale +Copyright (c) 2023 GravScale, Roberto Bertó Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index af1f9fa..2afffea 100644 --- a/README.md +++ b/README.md @@ -1,149 +1,225 @@ -# pyvyos Documentation +# pyvyos -pyvyos is a Python library for interacting with VyOS devices via their API. This documentation provides a guide on how to use pyvyos to manage your VyOS devices programmatically. +[![PyPI version](https://img.shields.io/pypi/v/pyvyos.svg)](https://pypi.org/project/pyvyos/) +[![Python versions](https://img.shields.io/pypi/pyversions/pyvyos.svg)](https://pypi.org/project/pyvyos/) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![PR Validation](https://github.com/vyos-contrib/pyvyos/actions/workflows/python-pr-validation.yml/badge.svg)](https://github.com/vyos-contrib/pyvyos/actions/workflows/python-pr-validation.yml) -You can find the complete pyvyos documentation on [Read the Docs](https://pyvyos.readthedocs.io/en/latest/). +Python SDK for the [VyOS](https://vyos.io/) HTTPS API. -## Installation +`pyvyos` is a small, focused library that wraps the VyOS HTTPS API in an +idiomatic Python interface. It is intended for automation scripts, internal +tooling, and integrations with configuration management systems. -You can install pyvyos using pip https://pypi.org/project/pyvyos/: +## Installation ```bash pip install pyvyos ``` -## Getting Started +Requires **Python 3.13 or newer**. -### Importing and Disabling Warnings for verify=False -Before using pyvyos, it's a good practice to disable urllib3 warnings and import the required modules, IF you use verify=False: - -``` -import urllib3 -urllib3.disable_warnings() -``` +## Quick start -### Using API Response Class -pyvyos uses a custom ApiResponse data class to handle API responses: +Enable the HTTPS API on the VyOS device and create an API key: -``` -@dataclass -class ApiResponse: - status: int - request: dict - result: dict - error: str +```text +set service https api keys id my-key key 'your-secret-key' +commit ``` -### Initializing a VyDevice Object +Then, from Python: +```python +import os +from pyvyos import VyDevice -#### Configuring Your Environment for VyDevice -1. Rename the file .env.example to .env. -1. Open the .env file in a text editor. -1. Replace the placeholder values with your VyOS device credentials: - - **VYDEVICE_HOSTNAME**: Your device's hostname or IP address. - - **VYDEVICE_APIKEY**: Your API key for authentication. - - **VYDEVICE_PORT**: The port number for the API. Default 443 - - **VYDEVICE_PROTOCOL**: The protocol (e.g., http or https). Default https - - **VYDEVICE_VERIFY_SSL**: Set to True or False for SSL verification. +device = VyDevice( + hostname=os.environ["VYDEVICE_HOSTNAME"], + apikey=os.environ["VYDEVICE_APIKEY"], + port=int(os.environ.get("VYDEVICE_PORT", "443")), + protocol=os.environ.get("VYDEVICE_PROTOCOL", "https"), + verify=os.environ.get("VYDEVICE_VERIFY_SSL", "true").lower() == "true", +) +response = device.show(path=["system", "image"]) +if response.error: + print(f"Error {response.status}: {response.error}") +else: + print(response.result) +``` +If you use self-signed certificates, set `verify=False` **only in lab +environments** and silence the urllib3 warning explicitly: + +```python +import urllib3 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) ``` -# Retrieve VyOS device connection details from environment variables and configure VyDevice -from dotenv import load_dotenv -load_dotenv() -hostname = os.getenv('VYDEVICE_HOSTNAME') -apikey = os.getenv('VYDEVICE_APIKEY') -port = os.getenv('VYDEVICE_PORT') -protocol = os.getenv('VYDEVICE_PROTOCOL') -verify_ssl = os.getenv('VYDEVICE_VERIFY_SSL') +## Environment variables -# Convert the verify_ssl value to a boolean -verify = verify_ssl.lower() == "true" if verify_ssl else True +A `.env.example` is shipped with the project. The recognised variables are: -device = VyDevice(hostname=hostname, apikey=apikey, port=port, protocol=protocol, verify=verify) -``` +| Variable | Default | Purpose | +| ---------------------- | ------- | -------------------------------------------------- | +| `VYDEVICE_HOSTNAME` | — | Hostname or IP address of the VyOS device. | +| `VYDEVICE_APIKEY` | — | API key configured on the device. | +| `VYDEVICE_PORT` | `443` | HTTPS port of the VyOS API. | +| `VYDEVICE_PROTOCOL` | `https` | `https` (recommended) or `http`. | +| `VYDEVICE_VERIFY_SSL` | `true` | Verify the TLS certificate of the device. | -## Using pyvyos +`pyvyos` does not read these variables on its own — your application is +responsible for loading them (for example with `python-dotenv`) and passing +the values to `VyDevice`. -### configure, then set -The configure_set method sets a VyOS configuration: +## API overview -``` -# Set a VyOS configuration -response = device.configure_set(path=["interfaces", "ethernet", "eth0", "address", "192.168.1.1/24"]) +All methods return an `ApiResponse` dataclass with four fields: -# Check for errors and print the result -if not response.error: - print(response.result) -``` -### configure, then show a single OBJECT value -``` -# Retrieve VyOS return values for a specific interface -response = device.retrieve_return_values(path=["interfaces", "dummy", "dum1", "address"]) -print(response.result) +```python +@dataclass +class ApiResponse: + status: int # HTTP status code + request: dict # the request payload (API key removed) + result: dict | list # parsed `data` field from the response + error: str | bool # error message, or False on success ``` -### configure, then show OBJECT -The retrieve_show_config method retrieves the VyOS configuration: +The recommended usage pattern is: +```python +response = device.retrieve_show_config(path=["interfaces"]) +if response.error: + raise RuntimeError(response.error) +do_something_with(response.result) ``` -# Retrieve the VyOS configuration -response = device.retrieve_show_config(path=[]) -# Check for errors and print the result -if not response.error: - print(response.result) -``` +### Configuration -### configure, then delete OBJECT -``` -# Delete a VyOS interface configuration -response = device.configure_delete(path=["interfaces", "dummy", "dum1"]) +```python +device.configure_set(path=["interfaces", "ethernet", "eth0", "address", "192.0.2.1/24"]) +device.configure_delete(path=["interfaces", "dummy", "dum1"]) +device.configure_multiple_op(path=[ + {"op": "set", "path": ["interfaces", "dummy", "dum2", "address", "203.0.113.1/24"]}, + {"op": "delete", "path": ["interfaces", "dummy", "dum1"]}, +]) ``` -### configure, then save -``` -# Save VyOS configuration without specifying a file (default location) -response = device.config_file_save() -``` +### Retrieval -### configure, then save FILE +```python +device.retrieve_show_config(path=["system"]) +device.retrieve_return_values(path=["interfaces", "dummy", "dum1", "address"]) ``` -# Save VyOS configuration to a specific file -response = device.config_file_save(file="/config/test300.config") + +### Operational + +```python +device.show(path=["system", "image"]) +device.generate(path=["ssh", "client-key", "/tmp/key"]) +device.reset(path=["conntrack-sync", "internal-cache"]) ``` -## show OBJECT +### Configuration files + +```python +device.config_file_save() # default location +device.config_file_save(file="/config/backup.config") +device.config_file_load(file="/config/backup.config") ``` -# Show VyOS system image information -response = device.show(path=["system", "image"]) -print(response.result) + +### System control + +```python +device.reboot() # equivalent to device.reboot(path=["now"]) +device.poweroff() # equivalent to device.poweroff(path=["now"]) ``` -### generate OBJECT +### Image management + +```python +device.image_add(url="https://downloads.vyos.io/.../vyos-1.4-image.iso") +device.image_delete(name="1.4-rolling-...") ``` -# Generate an SSH key with a random string in the name -randstring = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(20)) -keyrand = f'/tmp/key_{randstring}' -response = device.generate(path=["ssh", "client-key", keyrand]) + +## Public API stability + +The supported public API of pyvyos is: + +```python +from pyvyos import VyDevice, ApiResponse ``` -### reset OBJECT -The reset method allows you to run a reset command: +These compatibility imports continue to work without warnings and will be +kept while the migration cost remains trivial: +```python +from pyvyos.device import VyDevice +from pyvyos.rest import RestClient, ApiResponse ``` -# Execute the reset command -response = device.reset(path=["conntrack-sync", "internal-cache"]) -# Check for errors and print the result -if not response.error: - print(response.result) +Anything under `pyvyos.core.*` is internal implementation detail and may +change between minor releases. + +The deprecation timeline is: + +| Release | Status | +| ------- | ----------------------------------------------------------------- | +| `0.4.x` | Compatibility shims work without warnings. | +| `0.5.x` | Internal solidity work; shims still silent. | +| `0.6.x` | Compatibility shims emit a `DeprecationWarning`. | +| `1.0.0` | Shims are removed or kept, depending on observed usage. | + +## Logging + +`pyvyos` uses the standard `logging` module under the `pyvyos` namespace and +attaches a `NullHandler` so a default install does not print anything. + +To see request/response activity, configure the logger in your application: + +```python +import logging +logging.basicConfig(level=logging.INFO) +logging.getLogger("pyvyos").setLevel(logging.DEBUG) ``` -### configure, then load FILE +Request payloads are sanitised before logging — the `key` field is replaced +with `***REDACTED***`. + +## VyOS compatibility + +Tested against: + +- VyOS 1.4 LTS (stable) +- VyOS 1.5 rolling + +The library only depends on the HTTPS API surface, so versions that expose +the same endpoints should work without changes. + +## Development + +The project uses [uv](https://docs.astral.sh/uv/) for environment +management: + +```bash +uv sync --extra dev +uv run pytest ``` -# Load VyOS configuration from a specific file -response = device.config_file_load(file="/config/test300.config") + +Optional code-style hooks: + +```bash +pip install pre-commit +pre-commit install ``` + +## Contributing + +Bug reports and pull requests are welcome. Please open an issue first to +discuss anything beyond a small fix, and keep changes focused — payload and +public-API changes go through a separate review cycle. + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/docs/getting-started.md b/docs/getting-started.md index 183c109..3cf4891 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -21,7 +21,7 @@ VYDEVICE_HOSTNAME=192.168.1.1 VYDEVICE_APIKEY=your-api-key-here VYDEVICE_PORT=443 VYDEVICE_PROTOCOL=https -VYDEVICE_VERIFY_SSL=False +VYDEVICE_VERIFY_SSL=true ``` ## Step 3: Create Your First Script @@ -54,7 +54,7 @@ If using `verify=False`, disable urllib3 warnings: ```python import urllib3 -urllib3.disable_warnings() +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) ``` ## Understanding API Responses diff --git a/docs/index.md b/docs/index.md index 1bc9cb7..7576f20 100644 --- a/docs/index.md +++ b/docs/index.md @@ -37,7 +37,7 @@ device = VyDevice( apikey="your-api-key", port=443, protocol="https", - verify=False + verify=True ) response = device.show(path=["system", "image"]) @@ -48,8 +48,6 @@ print(response.result) - [GitHub Repository](https://github.com/vyos-contrib/pyvyos) - [PyPI Package](https://pypi.org/project/pyvyos/) -- [Read the Docs](https://pyvyos.readthedocs.io/) - ## License MIT License - See LICENSE file for details -- cgit v1.2.3