Skip to content

Commit 5c440ae

Browse files
authored
Make boto3 and warrant-lite optional dependencies (#2020)
## Summary - Moves `boto3` and `warrant-lite` from core dependencies to a `nexity` optional extra (`pip install pyoverkiz[nexity]`) - Adds a clear `ImportError` with install instructions when `NexityAuthStrategy.login()` is called without the extra - Adds test coverage for the missing-dependency error path - Documents the optional extra in the getting-started guide and migration guide Resolves PY-1 from #2014. ## Test plan - [x] All 397 existing tests pass - [x] New `test_login_raises_import_error_without_nexity_extra` test verifies the error message - [x] Existing Nexity auth tests still pass (skipped gracefully when extra is not installed) - [x] Pre-commit hooks pass (ruff, mypy, ty)
1 parent dc914c8 commit 5c440ae

6 files changed

Lines changed: 67 additions & 13 deletions

File tree

docs/getting-started.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ uv add pyoverkiz
2525
pip install pyoverkiz
2626
```
2727

28+
### Optional extras
29+
30+
Some servers require additional dependencies that are not installed by default:
31+
32+
| Extra | Server | Packages |
33+
|-------|--------|----------|
34+
| `nexity` | Nexity | boto3, warrant-lite |
35+
36+
Install an extra with:
37+
38+
```bash
39+
uv add "pyoverkiz[nexity]"
40+
# or
41+
pip install "pyoverkiz[nexity]"
42+
```
43+
2844
## Choose your server
2945

3046
Use a cloud server when you want to connect through the vendor’s public API. Use a local server when you want LAN access to a gateway.

docs/migration-v2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,4 +297,4 @@ These are not breaking, but worth knowing about when migrating:
297297
- **Device helpers**`Device.get_command_definition()` for looking up command metadata.
298298
- **Reference endpoints** — query server metadata: `get_reference_ui_classes()`, `get_reference_ui_widgets()`, `get_reference_ui_profile()`, `get_reference_controllable_types()`, etc.
299299
- **Firmware management**`get_devices_not_up_to_date()`, `get_device_firmware_status()`, `update_device_firmware()`.
300-
- **boto3 lazy import**`boto3` is only imported when the Nexity auth strategy is actually used.
300+
- **Optional Nexity dependencies**`boto3` and `warrant-lite` are no longer installed by default. Install them with `pip install pyoverkiz[nexity]` if you use the Nexity server. A clear `ImportError` is raised at login time if the extra is missing.

pyoverkiz/auth/strategies.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,16 @@ class NexityAuthStrategy(SessionLoginStrategy):
246246

247247
async def login(self) -> None:
248248
"""Perform login using Nexity username and password."""
249-
import boto3
250-
from botocore.config import Config
251-
from botocore.exceptions import ClientError
252-
from warrant_lite import WarrantLite
249+
try:
250+
import boto3
251+
from botocore.config import Config
252+
from botocore.exceptions import ClientError
253+
from warrant_lite import WarrantLite
254+
except ImportError as err:
255+
raise ImportError(
256+
"Nexity authentication requires the 'nexity' extra. "
257+
'Install it with: pip install "pyoverkiz[nexity]"'
258+
) from err
253259

254260
loop = asyncio.get_running_loop()
255261

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ dependencies = [
1717
"aiohttp<4.0.0,>=3.10.3",
1818
"backoff<3.0,>=1.10.0",
1919
"attrs>=21.2",
20-
"boto3<2.0.0,>=1.18.59",
21-
"warrant-lite<2.0.0,>=1.0.4",
2220
"cattrs>=23.2",
2321
]
2422

2523
[project.optional-dependencies]
24+
nexity = [
25+
"boto3<2.0.0,>=1.18.59",
26+
"warrant-lite<2.0.0,>=1.0.4",
27+
]
2628
docs = [
2729
"mkdocs>=1.5.0,<2.0",
2830
"mkdocs-material>=9.5.0",

tests/test_auth.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77

88
import base64
99
import datetime
10+
import importlib.util
1011
import json
1112
import sys
1213
from unittest.mock import AsyncMock, MagicMock, patch
1314

1415
import pytest
1516
from aiohttp import ClientSession
16-
from botocore.exceptions import ClientError
1717

1818
from pyoverkiz.auth.base import AuthContext
1919
from pyoverkiz.auth.credentials import (
@@ -40,6 +40,11 @@
4040
from pyoverkiz.exceptions import InvalidTokenError, NexityBadCredentialsError
4141
from pyoverkiz.models import ServerConfig
4242

43+
HAS_NEXITY_DEPS = importlib.util.find_spec("boto3") is not None
44+
45+
if HAS_NEXITY_DEPS:
46+
from botocore.exceptions import ClientError
47+
4348

4449
class TestAuthContext:
4550
"""Test AuthContext functionality."""
@@ -499,6 +504,28 @@ def test_boto3_not_imported_at_module_load(self):
499504
sys.modules[mod] = value
500505

501506
@pytest.mark.asyncio
507+
async def test_login_raises_import_error_without_nexity_extra(self):
508+
"""Login raises ImportError with install hint when nexity extra is missing."""
509+
server_config = ServerConfig(
510+
server=Server.NEXITY,
511+
name="Nexity",
512+
endpoint="https://api.nexity.com",
513+
manufacturer="Nexity",
514+
api_type=APIType.CLOUD,
515+
)
516+
credentials = UsernamePasswordCredentials("user", "pass")
517+
session = AsyncMock(spec=ClientSession)
518+
519+
strategy = NexityAuthStrategy(credentials, session, server_config, True)
520+
521+
with (
522+
patch.dict(sys.modules, {"boto3": None}),
523+
pytest.raises(ImportError, match="pyoverkiz\\[nexity\\]"),
524+
):
525+
await strategy.login()
526+
527+
@pytest.mark.asyncio
528+
@pytest.mark.skipif(not HAS_NEXITY_DEPS, reason="nexity extra not installed")
502529
async def test_login_maps_invalid_credentials_client_error(self):
503530
"""Map Cognito bad-credential errors to NexityBadCredentialsError."""
504531
server_config = ServerConfig(
@@ -527,6 +554,7 @@ async def test_login_maps_invalid_credentials_client_error(self):
527554
await strategy.login()
528555

529556
@pytest.mark.asyncio
557+
@pytest.mark.skipif(not HAS_NEXITY_DEPS, reason="nexity extra not installed")
530558
async def test_login_propagates_non_auth_client_error(self):
531559
"""Propagate non-auth Cognito errors to preserve failure context."""
532560
server_config = ServerConfig(

uv.lock

Lines changed: 7 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)