aiofortiosapi 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ name: Lint & type-check
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - run: pip install ruff mypy aiohttp
18
+ - run: ruff check src tests
19
+ - run: ruff format --check src tests
20
+ - run: mypy src
21
+
22
+ test:
23
+ name: Tests (Python ${{ matrix.python-version }})
24
+ runs-on: ubuntu-latest
25
+ strategy:
26
+ matrix:
27
+ python-version: ["3.12", "3.13", "3.14"]
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: actions/setup-python@v5
31
+ with:
32
+ python-version: ${{ matrix.python-version }}
33
+ - run: pip install -e ".[test]"
34
+ - run: pytest
35
+
36
+ build:
37
+ name: Package build check
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+ - uses: actions/setup-python@v5
42
+ with:
43
+ python-version: "3.12"
44
+ - run: pip install build twine
45
+ - run: python -m build
46
+ - run: twine check dist/*
@@ -0,0 +1,31 @@
1
+ # BEFORE FIRST USE: configure a PyPI Trusted Publisher for this repo.
2
+ # Go to https://pypi.org/manage/account/publishing/ and add:
3
+ # owner/repo: kimfrellsen/aiofortiosapi
4
+ # workflow: release.yml
5
+ # environment: release
6
+ # No API token needs to be stored — OIDC handles auth.
7
+
8
+ name: Release
9
+
10
+ on:
11
+ push:
12
+ tags:
13
+ - "v*"
14
+
15
+ permissions:
16
+ id-token: write # required for OIDC trusted publishing
17
+ contents: read
18
+
19
+ jobs:
20
+ build-and-publish:
21
+ name: Build & publish to PyPI
22
+ runs-on: ubuntu-latest
23
+ environment: release
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: actions/setup-python@v5
27
+ with:
28
+ python-version: "3.12"
29
+ - run: pip install build
30
+ - run: python -m build
31
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,47 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .eggs/
12
+ wheels/
13
+
14
+ # Virtual environments
15
+ .venv/
16
+ venv/
17
+ env/
18
+ ENV/
19
+
20
+ # Testing / coverage
21
+ .pytest_cache/
22
+ .coverage
23
+ .coverage.*
24
+ htmlcov/
25
+ coverage.xml
26
+ *.cover
27
+
28
+ # Typing
29
+ .mypy_cache/
30
+ .dmypy.json
31
+ dmypy.json
32
+
33
+ # Editors
34
+ .vscode/
35
+ .idea/
36
+ *.swp
37
+ *.swo
38
+ *~
39
+ .DS_Store
40
+
41
+ # Packaging
42
+ MANIFEST
43
+ pip-log.txt
44
+ pip-delete-this-directory.txt
45
+
46
+ # Ruff
47
+ .ruff_cache/
@@ -0,0 +1,43 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-08-26
10
+
11
+ ### Added
12
+ - Initial scaffold: `FortiOSClient` with Bearer token auth and injected `aiohttp.ClientSession`.
13
+ - `get_system_status()`, `get_resource_usage()`, `get_detected_devices()` endpoints.
14
+ - `async_validate()` for cheap credential validation in HA config_flow.
15
+ - `FortiOSClient.get(path, *, params=None)` — generic raw-envelope GET for
16
+ endpoints without a dedicated typed method (e.g. HA coordinator polling).
17
+ - Exception hierarchy: `FortiOSConnectionError`, `FortiOSAuthenticationError`,
18
+ `FortiOSNotFoundError`, `FortiOSResponseError`.
19
+ - Typed frozen dataclasses: `SystemStatus`, `ResourceUsage`, `DetectedDevice`.
20
+ - `verify_ssl` flag for self-signed FortiGate certificates.
21
+ - `vdom` parameter support.
22
+ - Full test suite using `aresponses` (no live network required).
23
+ - Ruff + mypy strict configuration.
24
+ - GitHub Actions CI (lint + test on Python 3.12/3.13/3.14) and OIDC-based PyPI release workflow.
25
+
26
+ ### Changed
27
+ - `DetectedDevice.is_online` uses the API-reported `is_online` flag; when a
28
+ firmware omits the field it falls back to `last_seen` freshness (seen within
29
+ the new `device_online_threshold` client argument, default 300 s).
30
+ `DetectedDevice.list_from_api()` accepts keyword-only `now` and
31
+ `online_threshold` for deterministic parsing.
32
+ - `ResourceUsage` model matches real firmware: memory under `"mem"`/`"current"`,
33
+ `sessions` and `session_setup_rate` sum IPv4+IPv6 buckets; `uptime` was
34
+ dropped (not present on `monitor/system/resource/usage`).
35
+
36
+ ### Fixed
37
+ - `DetectedDevice.ip` no longer becomes the string `"None"` when the API returns
38
+ a null `ipv4_address`; all string fields map null to `""`.
39
+ - Parsing no longer raises on scalar list entries (`_first`), null envelopes
40
+ (`"results": null`), or non-numeric stat values; safe defaults are used.
41
+ - Error messages for failed requests surface FortiOS detail keys (`cli_error`,
42
+ `message`, `error`, `http_message`) instead of an often-empty `error`,
43
+ falling back to the truncated raw body.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kim <kim@frellsen.se>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.5
2
+ Name: aiofortiosapi
3
+ Version: 0.1.0
4
+ Summary: Async FortiOS REST API client for Home Assistant
5
+ Project-URL: Homepage, https://github.com/kimfrellsen/aiofortiosapi
6
+ Project-URL: Repository, https://github.com/kimfrellsen/aiofortiosapi
7
+ Project-URL: Changelog, https://github.com/kimfrellsen/aiofortiosapi/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/kimfrellsen/aiofortiosapi/issues
9
+ Author-email: Kim <kim@frellsen.se>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: api-client,asyncio,fortigate,fortinet,fortios,home-assistant
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Framework :: AsyncIO
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: System :: Networking :: Monitoring
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.12
23
+ Requires-Dist: aiohttp>=3.9
24
+ Provides-Extra: test
25
+ Requires-Dist: aresponses>=3; extra == 'test'
26
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'test'
27
+ Requires-Dist: pytest-cov>=5; extra == 'test'
28
+ Requires-Dist: pytest>=8; extra == 'test'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # aiofortiosapi
32
+
33
+ Async Python client for the FortiOS REST API, built for the [Home Assistant](https://www.home-assistant.io/) `fortios` integration.
34
+
35
+ > **Community project** — not affiliated with or supported by Fortinet TAC.
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install aiofortiosapi
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ The library requires an injected `aiohttp.ClientSession` — Home Assistant provides one via
46
+ `async_get_clientsession(hass)`. You own the session lifecycle; this library never creates or closes it.
47
+
48
+ ```python
49
+ import asyncio
50
+ import aiohttp
51
+ from aiofortiosapi import FortiOSClient, FortiOSAuthenticationError, FortiOSConnectionError
52
+
53
+ async def main() -> None:
54
+ session = aiohttp.ClientSession()
55
+ try:
56
+ client = FortiOSClient(
57
+ host="192.168.1.1",
58
+ token="your-rest-api-token",
59
+ session=session,
60
+ verify_ssl=False, # set True in production with a valid cert
61
+ )
62
+ status = await client.get_system_status()
63
+ print(status.hostname, status.version)
64
+
65
+ usage = await client.get_resource_usage()
66
+ print(f"CPU {usage.cpu_percent}% MEM {usage.memory_percent}%")
67
+
68
+ devices = await client.get_detected_devices()
69
+ for d in devices:
70
+ print(d.mac, d.hostname, d.ip, "online" if d.is_online else "offline")
71
+ except FortiOSAuthenticationError:
72
+ print("Bad token — re-enter credentials")
73
+ except FortiOSConnectionError:
74
+ print("Cannot reach the FortiGate — check host/port")
75
+ finally:
76
+ await session.close()
77
+
78
+ asyncio.run(main())
79
+ ```
80
+
81
+ ## Generating a FortiOS REST API token
82
+
83
+ 1. In the FortiGate GUI go to **System → Administrators → Create New → REST API Admin**.
84
+ 2. Set a **Trusted Host** (the IP of your Home Assistant instance) to restrict token use.
85
+ 3. Assign a read-only profile (`prof_admin` or custom).
86
+ 4. Copy the generated token — it is shown only once.
87
+
88
+ The library uses `Authorization: Bearer <token>` (not the legacy `?access_token=` query string).
89
+
90
+ ### Device online state
91
+
92
+ `DetectedDevice.is_online` uses the flag reported by FortiOS. On firmware that
93
+ omits the field, it falls back to deriving online state from `last_seen`
94
+ freshness: a device counts as online when seen within
95
+ `DEFAULT_ONLINE_THRESHOLD` seconds (300). Tune the fallback per client:
96
+
97
+ ```python
98
+ client = FortiOSClient(..., device_online_threshold=600)
99
+ ```
100
+
101
+ ## Scope
102
+
103
+ `aiofortiosapi` is intentionally minimal:
104
+
105
+ - **Three typed monitor endpoints** (`get_system_status`, `get_resource_usage`,
106
+ `get_detected_devices`) used by the Home Assistant integration.
107
+ - A generic `get(path)` for any other endpoint that returns the raw JSON envelope.
108
+ - **No** config-write, CMDB, file upload, SSH fallback, or CLI helpers.
109
+ - **No** session/cookie login flow — Bearer token only.
110
+
111
+ This keeps the dependency tree small (runtime dep: `aiohttp` only) and passes HA integration
112
+ quality review requirements.
113
+
114
+ ## Exception hierarchy
115
+
116
+ | Exception | When raised | HA mapping |
117
+ |---|---|---|
118
+ | `FortiOSConnectionError` | Transport/timeout failure | `ConfigEntryNotReady` |
119
+ | `FortiOSAuthenticationError` | HTTP 401 / 403 | `ConfigEntryAuthFailed` |
120
+ | `FortiOSNotFoundError` | HTTP 404 | log / inspect |
121
+ | `FortiOSResponseError` | Bad JSON, 5xx, other 4xx | log / raise |
122
+
123
+ ## License
124
+
125
+ MIT
@@ -0,0 +1,95 @@
1
+ # aiofortiosapi
2
+
3
+ Async Python client for the FortiOS REST API, built for the [Home Assistant](https://www.home-assistant.io/) `fortios` integration.
4
+
5
+ > **Community project** — not affiliated with or supported by Fortinet TAC.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install aiofortiosapi
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ The library requires an injected `aiohttp.ClientSession` — Home Assistant provides one via
16
+ `async_get_clientsession(hass)`. You own the session lifecycle; this library never creates or closes it.
17
+
18
+ ```python
19
+ import asyncio
20
+ import aiohttp
21
+ from aiofortiosapi import FortiOSClient, FortiOSAuthenticationError, FortiOSConnectionError
22
+
23
+ async def main() -> None:
24
+ session = aiohttp.ClientSession()
25
+ try:
26
+ client = FortiOSClient(
27
+ host="192.168.1.1",
28
+ token="your-rest-api-token",
29
+ session=session,
30
+ verify_ssl=False, # set True in production with a valid cert
31
+ )
32
+ status = await client.get_system_status()
33
+ print(status.hostname, status.version)
34
+
35
+ usage = await client.get_resource_usage()
36
+ print(f"CPU {usage.cpu_percent}% MEM {usage.memory_percent}%")
37
+
38
+ devices = await client.get_detected_devices()
39
+ for d in devices:
40
+ print(d.mac, d.hostname, d.ip, "online" if d.is_online else "offline")
41
+ except FortiOSAuthenticationError:
42
+ print("Bad token — re-enter credentials")
43
+ except FortiOSConnectionError:
44
+ print("Cannot reach the FortiGate — check host/port")
45
+ finally:
46
+ await session.close()
47
+
48
+ asyncio.run(main())
49
+ ```
50
+
51
+ ## Generating a FortiOS REST API token
52
+
53
+ 1. In the FortiGate GUI go to **System → Administrators → Create New → REST API Admin**.
54
+ 2. Set a **Trusted Host** (the IP of your Home Assistant instance) to restrict token use.
55
+ 3. Assign a read-only profile (`prof_admin` or custom).
56
+ 4. Copy the generated token — it is shown only once.
57
+
58
+ The library uses `Authorization: Bearer <token>` (not the legacy `?access_token=` query string).
59
+
60
+ ### Device online state
61
+
62
+ `DetectedDevice.is_online` uses the flag reported by FortiOS. On firmware that
63
+ omits the field, it falls back to deriving online state from `last_seen`
64
+ freshness: a device counts as online when seen within
65
+ `DEFAULT_ONLINE_THRESHOLD` seconds (300). Tune the fallback per client:
66
+
67
+ ```python
68
+ client = FortiOSClient(..., device_online_threshold=600)
69
+ ```
70
+
71
+ ## Scope
72
+
73
+ `aiofortiosapi` is intentionally minimal:
74
+
75
+ - **Three typed monitor endpoints** (`get_system_status`, `get_resource_usage`,
76
+ `get_detected_devices`) used by the Home Assistant integration.
77
+ - A generic `get(path)` for any other endpoint that returns the raw JSON envelope.
78
+ - **No** config-write, CMDB, file upload, SSH fallback, or CLI helpers.
79
+ - **No** session/cookie login flow — Bearer token only.
80
+
81
+ This keeps the dependency tree small (runtime dep: `aiohttp` only) and passes HA integration
82
+ quality review requirements.
83
+
84
+ ## Exception hierarchy
85
+
86
+ | Exception | When raised | HA mapping |
87
+ |---|---|---|
88
+ | `FortiOSConnectionError` | Transport/timeout failure | `ConfigEntryNotReady` |
89
+ | `FortiOSAuthenticationError` | HTTP 401 / 403 | `ConfigEntryAuthFailed` |
90
+ | `FortiOSNotFoundError` | HTTP 404 | log / inspect |
91
+ | `FortiOSResponseError` | Bad JSON, 5xx, other 4xx | log / raise |
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,77 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "aiofortiosapi"
7
+ dynamic = ["version"]
8
+ description = "Async FortiOS REST API client for Home Assistant"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Kim", email = "kim@frellsen.se" }]
13
+ requires-python = ">=3.12"
14
+ dependencies = ["aiohttp>=3.9"]
15
+ keywords = ["fortios", "fortigate", "fortinet", "home-assistant", "asyncio", "api-client"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: System :: Networking :: Monitoring",
24
+ "Framework :: AsyncIO",
25
+ "Typing :: Typed",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/kimfrellsen/aiofortiosapi"
30
+ Repository = "https://github.com/kimfrellsen/aiofortiosapi"
31
+ Changelog = "https://github.com/kimfrellsen/aiofortiosapi/blob/main/CHANGELOG.md"
32
+ Issues = "https://github.com/kimfrellsen/aiofortiosapi/issues"
33
+
34
+ [project.optional-dependencies]
35
+ test = [
36
+ "pytest>=8",
37
+ "pytest-asyncio>=0.24",
38
+ "aresponses>=3",
39
+ "pytest-cov>=5",
40
+ ]
41
+
42
+ [tool.hatch.version]
43
+ path = "src/aiofortiosapi/__init__.py"
44
+
45
+ [tool.hatch.build.targets.wheel]
46
+ packages = ["src/aiofortiosapi"]
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # pytest
50
+ # ---------------------------------------------------------------------------
51
+ [tool.pytest.ini_options]
52
+ asyncio_mode = "auto"
53
+ asyncio_default_fixture_loop_scope = "function"
54
+ testpaths = ["tests"]
55
+ addopts = "--cov=src/aiofortiosapi --cov-report=term-missing"
56
+
57
+ # ---------------------------------------------------------------------------
58
+ # ruff
59
+ # ---------------------------------------------------------------------------
60
+ [tool.ruff]
61
+ line-length = 99
62
+ target-version = "py312"
63
+
64
+ [tool.ruff.lint]
65
+ select = ["E", "F", "I", "UP", "B", "ASYNC", "SIM"]
66
+ ignore = ["SIM102"] # nested if acceptable in parse code
67
+
68
+ [tool.ruff.lint.isort]
69
+ known-first-party = ["aiofortiosapi"]
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # mypy
73
+ # ---------------------------------------------------------------------------
74
+ [tool.mypy]
75
+ python_version = "3.12"
76
+ strict = true
77
+ files = ["src"]
@@ -0,0 +1,31 @@
1
+ """aiofortiosapi — async FortiOS REST API client for Home Assistant."""
2
+
3
+ from .client import FortiOSClient
4
+ from .const import DEFAULT_ONLINE_THRESHOLD
5
+ from .exceptions import (
6
+ FortiOSAuthenticationError,
7
+ FortiOSConnectionError,
8
+ FortiOSError,
9
+ FortiOSNotFoundError,
10
+ FortiOSResponseError,
11
+ )
12
+ from .models import DetectedDevice, ResourceUsage, SystemStatus
13
+
14
+ __version__ = "0.1.0"
15
+
16
+ __all__ = [
17
+ "FortiOSClient",
18
+ # exceptions
19
+ "FortiOSError",
20
+ "FortiOSConnectionError",
21
+ "FortiOSAuthenticationError",
22
+ "FortiOSNotFoundError",
23
+ "FortiOSResponseError",
24
+ # models
25
+ "SystemStatus",
26
+ "ResourceUsage",
27
+ "DetectedDevice",
28
+ # constants
29
+ "DEFAULT_ONLINE_THRESHOLD",
30
+ "__version__",
31
+ ]