smartbox 2.1.3__tar.gz → 2.2.1__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.
- smartbox-2.2.1/.github/ISSUE_TEMPLATE/new-reseller.md +18 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/.github/workflows/publish-to-pypi.yml +1 -1
- {smartbox-2.1.3 → smartbox-2.2.1}/.github/workflows/python-package.yml +5 -5
- {smartbox-2.1.3 → smartbox-2.2.1}/PKG-INFO +8 -8
- {smartbox-2.1.3 → smartbox-2.2.1}/README.md +6 -6
- {smartbox-2.1.3 → smartbox-2.2.1}/pyproject.toml +2 -2
- {smartbox-2.1.3 → smartbox-2.2.1}/src/smartbox/__init__.py +5 -5
- {smartbox-2.1.3 → smartbox-2.2.1}/src/smartbox/cmd.py +5 -5
- {smartbox-2.1.3 → smartbox-2.2.1}/src/smartbox/error.py +2 -2
- smartbox-2.1.3/src/smartbox/resailer.py → smartbox-2.2.1/src/smartbox/reseller.py +49 -34
- {smartbox-2.1.3 → smartbox-2.2.1}/src/smartbox/session.py +13 -13
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/conftest.py +8 -8
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/test_cmd.py +4 -12
- smartbox-2.2.1/tests/test_reseller.py +58 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/test_session.py +11 -11
- {smartbox-2.1.3 → smartbox-2.2.1}/uv.lock +295 -295
- smartbox-2.1.3/.github/ISSUE_TEMPLATE/new-resailer.md +0 -18
- smartbox-2.1.3/tests/test_resailer.py +0 -58
- {smartbox-2.1.3 → smartbox-2.2.1}/.github/FUNDING.yml +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/.github/dependabot.yml +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/.gitignore +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/.pre-commit-config.yaml +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/.vscode/settings.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/CHANGELOG.md +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/CODEOWNERS +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/LICENSE +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/api-notes.md +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/codecov.yml +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/src/smartbox/models.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/src/smartbox/py.typed +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/src/smartbox/socket.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/src/smartbox/update_manager.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/__init__.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/common.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/acm/1/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/acm/1/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/acm/1/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/connected.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/htr/0/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/htr/0/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/htr/0/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/htr_system/power_limit.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/mgr/away_status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/mgr/nodes.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/pmo/3/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/pmo/3/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device1/pmo/3/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/connected.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/0/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/0/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/0/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/1/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/1/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/1/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/2/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/2/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/2/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/3/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/3/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/3/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/4/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/4/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/4/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/5/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/5/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_mod/5/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/htr_system/power_limit.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/mgr/away_status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs/device2/mgr/nodes.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/devs.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/grouped_devs.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/groups/test_home/guest_users.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/fixtures/test_path.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/test_models.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tests/test_update_manager.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.1}/tox.ini +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: New reseller
|
|
3
|
+
about: Add a new available reseller.
|
|
4
|
+
title: "[RESELLER] New available reseller"
|
|
5
|
+
labels: reseller
|
|
6
|
+
assignees: Delmael
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
> [!NOTE]
|
|
11
|
+
> If you do not see your current reseller in the available list, you can ask to add it. We cannot ensure your device go through this platform
|
|
12
|
+
|
|
13
|
+
* [ ] I have check with the `smartbox resellers` command that my reseller is not already present
|
|
14
|
+
* **Website url (mandatory)** :
|
|
15
|
+
* Name of the resellers:
|
|
16
|
+
* API url :
|
|
17
|
+
* Basic auth headers :
|
|
18
|
+
* Serial id:
|
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
with:
|
|
23
23
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
24
24
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
|
25
|
-
uses: actions/setup-python@v5.
|
|
25
|
+
uses: actions/setup-python@v5.6.0
|
|
26
26
|
with:
|
|
27
27
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
28
28
|
- name: Build a binary wheel and a source tarball
|
|
@@ -25,7 +25,7 @@ jobs:
|
|
|
25
25
|
with:
|
|
26
26
|
python-version: ${{ matrix.python-version }}
|
|
27
27
|
- name: Set up Python ${{ matrix.python-version }}
|
|
28
|
-
uses: actions/setup-python@v5.
|
|
28
|
+
uses: actions/setup-python@v5.6.0
|
|
29
29
|
with:
|
|
30
30
|
python-version: ${{ matrix.python-version }}
|
|
31
31
|
- name: Install the project
|
|
@@ -47,7 +47,7 @@ jobs:
|
|
|
47
47
|
with:
|
|
48
48
|
python-version: ${{ matrix.python-version }}
|
|
49
49
|
- name: Set up Python ${{ matrix.python-version }}
|
|
50
|
-
uses: actions/setup-python@v5.
|
|
50
|
+
uses: actions/setup-python@v5.6.0
|
|
51
51
|
with:
|
|
52
52
|
python-version: ${{ matrix.python-version }}
|
|
53
53
|
- name: Install the project
|
|
@@ -70,13 +70,13 @@ jobs:
|
|
|
70
70
|
python-version: ${{ matrix.python-version }}
|
|
71
71
|
|
|
72
72
|
- name: Set up Python ${{ matrix.python-version }}
|
|
73
|
-
uses: actions/setup-python@v5.
|
|
73
|
+
uses: actions/setup-python@v5.6.0
|
|
74
74
|
with:
|
|
75
75
|
python-version: ${{ matrix.python-version }}
|
|
76
76
|
- name: Install the project
|
|
77
77
|
run: |
|
|
78
78
|
uv sync --all-extras --dev
|
|
79
|
-
tox -e py
|
|
79
|
+
uv run tox -e py
|
|
80
80
|
- name: "Upload coverage to Codecov"
|
|
81
81
|
uses: codecov/codecov-action@v5
|
|
82
82
|
with:
|
|
@@ -86,4 +86,4 @@ jobs:
|
|
|
86
86
|
if: ${{ !cancelled() }}
|
|
87
87
|
uses: codecov/test-results-action@v1
|
|
88
88
|
with:
|
|
89
|
-
token: ${{ secrets.CODECOV_TOKEN }}
|
|
89
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: smartbox
|
|
3
|
-
Version: 2.1
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: Python API to control heating 'smart boxes'
|
|
5
5
|
Project-URL: Homepage, https://github.com/ajtudela/smartbox
|
|
6
6
|
Project-URL: Issues, https://github.com/ajtudela/smartbox/issues
|
|
@@ -15,7 +15,7 @@ Requires-Dist: aiohttp>=3.11.11
|
|
|
15
15
|
Requires-Dist: asyncclick>=8.1.8
|
|
16
16
|
Requires-Dist: jq>=1.8.0
|
|
17
17
|
Requires-Dist: pydantic>=2.10.4
|
|
18
|
-
Requires-Dist: python-socketio==5.
|
|
18
|
+
Requires-Dist: python-socketio==5.13.0
|
|
19
19
|
Requires-Dist: requests>=2.32.3
|
|
20
20
|
Requires-Dist: websocket-client>=1.8.0
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
@@ -53,7 +53,7 @@ A few common options are required for all commands:
|
|
|
53
53
|
Verbose logging can be enabled with the `-v`/`--verbose` flag.
|
|
54
54
|
|
|
55
55
|
### Optional options
|
|
56
|
-
These options are useful if your
|
|
56
|
+
These options are useful if your reseller is not configured.
|
|
57
57
|
|
|
58
58
|
* `-b`/`--base-auth-creds`: An HTTP Basic Auth credential used to do initial
|
|
59
59
|
authentication with the server. Use the base64 encoded string directly. See
|
|
@@ -61,7 +61,7 @@ These options are useful if your resailer is not configured.
|
|
|
61
61
|
* `-a`/`--api-name`: The API name for your heater vendor. This is visible in
|
|
62
62
|
the 'API Host' entry in the 'Version' menu item in the mobile app/web app. If
|
|
63
63
|
the host name is of the form `api-foo.xxxx` or `api.xxxx` use the values
|
|
64
|
-
`api-foo` or `api` respectively. The
|
|
64
|
+
`api-foo` or `api` respectively. The reseller has to be declared in the package.
|
|
65
65
|
* `-r`/`--x-referer`: The referer of your request.
|
|
66
66
|
* `-i`/`--x-serial-id`: The serial-id of your request.
|
|
67
67
|
|
|
@@ -132,11 +132,11 @@ The `health-check` command can be used to know if the API is alived
|
|
|
132
132
|
|
|
133
133
|
smartbox <auth options...> health-check
|
|
134
134
|
|
|
135
|
-
### List available
|
|
136
|
-
The `
|
|
137
|
-
If your
|
|
135
|
+
### List available resellers
|
|
136
|
+
The `resellers` command can be used to know which resellers has an automatic configuration.
|
|
137
|
+
If your reseller is not present you can raise an issue in github, or use the optional options.
|
|
138
138
|
|
|
139
|
-
smartbox <auth options...>
|
|
139
|
+
smartbox <auth options...> resellers
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
See [api-notes.md](./api-notes.md) for notes on REST and socket.io endpoints.
|
|
@@ -31,7 +31,7 @@ A few common options are required for all commands:
|
|
|
31
31
|
Verbose logging can be enabled with the `-v`/`--verbose` flag.
|
|
32
32
|
|
|
33
33
|
### Optional options
|
|
34
|
-
These options are useful if your
|
|
34
|
+
These options are useful if your reseller is not configured.
|
|
35
35
|
|
|
36
36
|
* `-b`/`--base-auth-creds`: An HTTP Basic Auth credential used to do initial
|
|
37
37
|
authentication with the server. Use the base64 encoded string directly. See
|
|
@@ -39,7 +39,7 @@ These options are useful if your resailer is not configured.
|
|
|
39
39
|
* `-a`/`--api-name`: The API name for your heater vendor. This is visible in
|
|
40
40
|
the 'API Host' entry in the 'Version' menu item in the mobile app/web app. If
|
|
41
41
|
the host name is of the form `api-foo.xxxx` or `api.xxxx` use the values
|
|
42
|
-
`api-foo` or `api` respectively. The
|
|
42
|
+
`api-foo` or `api` respectively. The reseller has to be declared in the package.
|
|
43
43
|
* `-r`/`--x-referer`: The referer of your request.
|
|
44
44
|
* `-i`/`--x-serial-id`: The serial-id of your request.
|
|
45
45
|
|
|
@@ -110,11 +110,11 @@ The `health-check` command can be used to know if the API is alived
|
|
|
110
110
|
|
|
111
111
|
smartbox <auth options...> health-check
|
|
112
112
|
|
|
113
|
-
### List available
|
|
114
|
-
The `
|
|
115
|
-
If your
|
|
113
|
+
### List available resellers
|
|
114
|
+
The `resellers` command can be used to know which resellers has an automatic configuration.
|
|
115
|
+
If your reseller is not present you can raise an issue in github, or use the optional options.
|
|
116
116
|
|
|
117
|
-
smartbox <auth options...>
|
|
117
|
+
smartbox <auth options...> resellers
|
|
118
118
|
|
|
119
119
|
|
|
120
120
|
See [api-notes.md](./api-notes.md) for notes on REST and socket.io endpoints.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "smartbox"
|
|
3
|
-
version = "2.1
|
|
3
|
+
version = "2.2.1"
|
|
4
4
|
requires-python = ">=3.13"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name="Alberto Tudela", email="ajtudela@gmail.com" },
|
|
@@ -22,7 +22,7 @@ dependencies = [
|
|
|
22
22
|
"asyncclick>=8.1.8",
|
|
23
23
|
"jq>=1.8.0",
|
|
24
24
|
"pydantic>=2.10.4",
|
|
25
|
-
"python-socketio==5.
|
|
25
|
+
"python-socketio==5.13.0",
|
|
26
26
|
"requests>=2.32.3",
|
|
27
27
|
"websocket-client>=1.8.0",
|
|
28
28
|
]
|
|
@@ -5,7 +5,7 @@ import importlib.metadata
|
|
|
5
5
|
from .error import (
|
|
6
6
|
APIUnavailableError,
|
|
7
7
|
InvalidAuthError,
|
|
8
|
-
|
|
8
|
+
ResellerNotExistError,
|
|
9
9
|
SmartboxError,
|
|
10
10
|
)
|
|
11
11
|
from .models import (
|
|
@@ -21,7 +21,7 @@ from .models import (
|
|
|
21
21
|
NodeStatus,
|
|
22
22
|
SmartboxNodeType,
|
|
23
23
|
)
|
|
24
|
-
from .
|
|
24
|
+
from .reseller import AvailableResellers, SmartboxReseller
|
|
25
25
|
from .session import AsyncSmartboxSession, Session
|
|
26
26
|
from .socket import SocketSession
|
|
27
27
|
from .update_manager import UpdateManager
|
|
@@ -33,7 +33,7 @@ __all__ = [
|
|
|
33
33
|
"APIUnavailableError",
|
|
34
34
|
"AcmNodeStatus",
|
|
35
35
|
"AsyncSmartboxSession",
|
|
36
|
-
"
|
|
36
|
+
"AvailableResellers",
|
|
37
37
|
"DefaultNodeStatus",
|
|
38
38
|
"GuestUser",
|
|
39
39
|
"Guests",
|
|
@@ -44,11 +44,11 @@ __all__ = [
|
|
|
44
44
|
"NodeFactoryOptions",
|
|
45
45
|
"NodeSetup",
|
|
46
46
|
"NodeStatus",
|
|
47
|
-
"
|
|
47
|
+
"ResellerNotExistError",
|
|
48
48
|
"Session",
|
|
49
49
|
"SmartboxError",
|
|
50
50
|
"SmartboxNodeType",
|
|
51
|
-
"
|
|
51
|
+
"SmartboxReseller",
|
|
52
52
|
"SocketSession",
|
|
53
53
|
"UpdateManager",
|
|
54
54
|
]
|
|
@@ -7,7 +7,7 @@ from typing import Any
|
|
|
7
7
|
from aiohttp import ClientSession
|
|
8
8
|
import asyncclick as click
|
|
9
9
|
|
|
10
|
-
from smartbox.
|
|
10
|
+
from smartbox.reseller import AvailableResellers
|
|
11
11
|
from smartbox.session import AsyncSmartboxSession
|
|
12
12
|
from smartbox.socket import SocketSession
|
|
13
13
|
|
|
@@ -403,10 +403,10 @@ async def api_version(ctx) -> None:
|
|
|
403
403
|
_pretty_print(version)
|
|
404
404
|
|
|
405
405
|
|
|
406
|
-
@smartbox.command(help="Get the availables
|
|
407
|
-
def
|
|
408
|
-
"""Get the availables
|
|
409
|
-
for item in
|
|
406
|
+
@smartbox.command(help="Get the availables resellers.")
|
|
407
|
+
def resellers() -> None:
|
|
408
|
+
"""Get the availables resellers."""
|
|
409
|
+
for item in AvailableResellers.resellers.items():
|
|
410
410
|
print(item)
|
|
411
411
|
|
|
412
412
|
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Reseller of Smartbox."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from typing import ClassVar
|
|
5
5
|
|
|
6
6
|
from pydantic import BaseModel, ValidationError
|
|
7
7
|
|
|
8
|
-
from smartbox.error import
|
|
8
|
+
from smartbox.error import ResellerNotExistError
|
|
9
9
|
|
|
10
10
|
SMARTBOX_GENERIC_BASIC_AUTH = "NTRiY2NiZmI0MWE5YTUxMTNmMDQ4OGQwOnZkaXZkaQ=="
|
|
11
11
|
_LOGGER = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class
|
|
15
|
-
"""Model of Smartbox
|
|
14
|
+
class SmartboxReseller(BaseModel):
|
|
15
|
+
"""Model of Smartbox Reseller config."""
|
|
16
16
|
|
|
17
17
|
name: str = "Smartbox"
|
|
18
18
|
web_url: str = ""
|
|
@@ -21,53 +21,65 @@ class SmartboxResailer(BaseModel):
|
|
|
21
21
|
serial_id: int = 0
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
class
|
|
25
|
-
"""
|
|
24
|
+
class AvailableResellers:
|
|
25
|
+
"""Resellers that have been verified."""
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
"api":
|
|
27
|
+
resellers: ClassVar[dict[str, SmartboxReseller]] = {
|
|
28
|
+
"api": SmartboxReseller(
|
|
29
29
|
name="Helki",
|
|
30
30
|
web_url="https://app.helki.com/",
|
|
31
31
|
api_url="api",
|
|
32
32
|
serial_id=1,
|
|
33
33
|
),
|
|
34
|
-
"api-ehc":
|
|
34
|
+
"api-ehc": SmartboxReseller(
|
|
35
35
|
name="Electric Heating Company",
|
|
36
36
|
web_url="https://control.electric-heatingcompany.co.uk/",
|
|
37
37
|
api_url="api-ehc",
|
|
38
38
|
serial_id=3,
|
|
39
39
|
),
|
|
40
|
-
"api-climastar":
|
|
40
|
+
"api-climastar": SmartboxReseller(
|
|
41
41
|
name="Climastar",
|
|
42
42
|
web_url="https://avantwifi.climastar.es/",
|
|
43
43
|
api_url="api-climastar",
|
|
44
44
|
serial_id=5,
|
|
45
45
|
),
|
|
46
|
-
"api-elnur":
|
|
46
|
+
"api-elnur": SmartboxReseller(
|
|
47
47
|
name="Elnur",
|
|
48
48
|
web_url="https://remotecontrol.elnur.es/",
|
|
49
49
|
api_url="api-elnur",
|
|
50
50
|
serial_id=7,
|
|
51
51
|
),
|
|
52
|
-
"api-hjm":
|
|
52
|
+
"api-hjm": SmartboxReseller(
|
|
53
53
|
name="HJM",
|
|
54
54
|
web_url="https://api.calorhjm.com/",
|
|
55
55
|
api_url="api-hjm",
|
|
56
56
|
serial_id=10,
|
|
57
57
|
),
|
|
58
|
-
"api-
|
|
58
|
+
"api-evconfort": SmartboxReseller(
|
|
59
|
+
name="Electrorad",
|
|
60
|
+
web_url="https://app.controls-electrorad.co.uk/",
|
|
61
|
+
api_url="api-evconfort",
|
|
62
|
+
serial_id=12,
|
|
63
|
+
),
|
|
64
|
+
"api-haverland": SmartboxReseller(
|
|
59
65
|
name="Haverland",
|
|
60
66
|
web_url="https://i2control.haverland.com/",
|
|
61
67
|
api_url="api-haverland",
|
|
62
68
|
basic_auth="NTU2ZDc0MWI3OGUzYmU5YjU2NjA3NTQ4OnZkaXZkaQ==",
|
|
63
69
|
serial_id=14,
|
|
64
70
|
),
|
|
65
|
-
"api-
|
|
71
|
+
"api-technoterm": SmartboxReseller(
|
|
66
72
|
name="Technotherm",
|
|
67
73
|
web_url="https://ttiapp.technotherm.com/",
|
|
68
74
|
api_url="api-lhz",
|
|
69
75
|
serial_id=16,
|
|
70
76
|
),
|
|
77
|
+
"api-smartcontrol": SmartboxReseller(
|
|
78
|
+
name="SmartControl",
|
|
79
|
+
web_url="https://app.smart-control.eu/",
|
|
80
|
+
api_url="api-lhz",
|
|
81
|
+
serial_id=17,
|
|
82
|
+
),
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
def __init__(
|
|
@@ -78,7 +90,7 @@ class AvailableResailers:
|
|
|
78
90
|
serial_id: int | None = None,
|
|
79
91
|
name: str = "Smartbox",
|
|
80
92
|
) -> None:
|
|
81
|
-
"""Check if
|
|
93
|
+
"""Check if reseller is already available or try to create one."""
|
|
82
94
|
self._api_url = api_url
|
|
83
95
|
self._basic_auth = basic_auth
|
|
84
96
|
self._web_url = web_url
|
|
@@ -86,27 +98,30 @@ class AvailableResailers:
|
|
|
86
98
|
self._name = name
|
|
87
99
|
|
|
88
100
|
@property
|
|
89
|
-
def
|
|
90
|
-
"""Get the
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
def reseller(self) -> SmartboxReseller:
|
|
102
|
+
"""Get the reseller."""
|
|
103
|
+
reseller = next(
|
|
104
|
+
(r for r in self.resellers.values() if r.api_url == self._api_url),
|
|
105
|
+
None,
|
|
106
|
+
)
|
|
107
|
+
if reseller is None:
|
|
93
108
|
if (
|
|
94
109
|
self._basic_auth is None
|
|
95
110
|
or self._web_url is None
|
|
96
111
|
or self._serial_id is None
|
|
97
112
|
):
|
|
98
|
-
msg = "This
|
|
99
|
-
raise
|
|
113
|
+
msg = f"This reseller {self._api_url} is not yet available or some arguments are missing."
|
|
114
|
+
raise ResellerNotExistError(msg)
|
|
100
115
|
try:
|
|
101
116
|
_LOGGER.debug(
|
|
102
|
-
"Creating a new
|
|
117
|
+
"Creating a new reseller api_url (%s), name=%s, web_url %s, basic_auth=%s, serial_id=%s",
|
|
103
118
|
self._api_url,
|
|
104
119
|
self._name,
|
|
105
120
|
self._web_url,
|
|
106
121
|
self._basic_auth,
|
|
107
122
|
self._serial_id,
|
|
108
123
|
)
|
|
109
|
-
|
|
124
|
+
reseller = SmartboxReseller(
|
|
110
125
|
api_url=self._api_url,
|
|
111
126
|
basic_auth=self._basic_auth,
|
|
112
127
|
web_url=self._web_url,
|
|
@@ -114,26 +129,26 @@ class AvailableResailers:
|
|
|
114
129
|
name=self._name,
|
|
115
130
|
)
|
|
116
131
|
except ValidationError as e:
|
|
117
|
-
raise
|
|
132
|
+
raise ResellerNotExistError from e
|
|
118
133
|
_LOGGER.debug(
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
"Reseller api_url (%s), name=%s, web_url %s",
|
|
135
|
+
reseller.api_url,
|
|
136
|
+
reseller.name,
|
|
137
|
+
reseller.web_url,
|
|
123
138
|
)
|
|
124
|
-
return
|
|
139
|
+
return reseller
|
|
125
140
|
|
|
126
141
|
@property
|
|
127
142
|
def api_url(self) -> str:
|
|
128
143
|
"""Get the api sub domain url."""
|
|
129
|
-
return self.
|
|
144
|
+
return self.reseller.api_url
|
|
130
145
|
|
|
131
146
|
@property
|
|
132
147
|
def name(self) -> str:
|
|
133
|
-
"""Get the name of
|
|
134
|
-
return self.
|
|
148
|
+
"""Get the name of reseller."""
|
|
149
|
+
return self.reseller.name
|
|
135
150
|
|
|
136
151
|
@property
|
|
137
152
|
def web_url(self) -> str:
|
|
138
|
-
"""Get the public websit of the
|
|
139
|
-
return self.
|
|
153
|
+
"""Get the public websit of the reseller."""
|
|
154
|
+
return self.reseller.web_url
|
|
@@ -31,7 +31,7 @@ from smartbox.models import (
|
|
|
31
31
|
SmartboxNodeType,
|
|
32
32
|
Token,
|
|
33
33
|
)
|
|
34
|
-
from smartbox.
|
|
34
|
+
from smartbox.reseller import AvailableResellers, SmartboxReseller
|
|
35
35
|
|
|
36
36
|
_DEFAULT_RETRY_ATTEMPTS = 5
|
|
37
37
|
_DEFAULT_BACKOFF_FACTOR = 0.1
|
|
@@ -59,13 +59,13 @@ class AsyncSession:
|
|
|
59
59
|
x_referer: str | None = None,
|
|
60
60
|
) -> None:
|
|
61
61
|
"""Init the session."""
|
|
62
|
-
self.
|
|
62
|
+
self._reseller = AvailableResellers(
|
|
63
63
|
api_url=api_name,
|
|
64
64
|
basic_auth=basic_auth_credentials,
|
|
65
65
|
serial_id=x_serial_id,
|
|
66
66
|
web_url=x_referer,
|
|
67
|
-
).
|
|
68
|
-
self._api_host: str = f"https://{self.
|
|
67
|
+
).reseller
|
|
68
|
+
self._api_host: str = f"https://{self.reseller.api_url}.helki.com"
|
|
69
69
|
self._basic_auth_credentials: str | None = basic_auth_credentials
|
|
70
70
|
self._retry_attempts: int = retry_attempts
|
|
71
71
|
self._backoff_factor: float = backoff_factor
|
|
@@ -78,20 +78,20 @@ class AsyncSession:
|
|
|
78
78
|
"Authorization": f"Bearer {self._access_token}",
|
|
79
79
|
"Content-Type": "application/json",
|
|
80
80
|
}
|
|
81
|
-
if self.
|
|
82
|
-
self._headers.update({"x-serialid": str(self.
|
|
83
|
-
if self.
|
|
84
|
-
self._headers.update({"x-referer": self.
|
|
81
|
+
if self.reseller.serial_id:
|
|
82
|
+
self._headers.update({"x-serialid": str(self.reseller.serial_id)})
|
|
83
|
+
if self.reseller.web_url:
|
|
84
|
+
self._headers.update({"x-referer": self.reseller.web_url})
|
|
85
85
|
|
|
86
86
|
@property
|
|
87
|
-
def
|
|
88
|
-
"""Get the
|
|
89
|
-
return self.
|
|
87
|
+
def reseller(self) -> SmartboxReseller:
|
|
88
|
+
"""Get the reseller."""
|
|
89
|
+
return self._reseller
|
|
90
90
|
|
|
91
91
|
@property
|
|
92
92
|
def api_name(self) -> str:
|
|
93
93
|
"""Get the api sub domain url."""
|
|
94
|
-
return self.
|
|
94
|
+
return self.reseller.api_url
|
|
95
95
|
|
|
96
96
|
@property
|
|
97
97
|
def api_host(self) -> str:
|
|
@@ -150,7 +150,7 @@ class AsyncSession:
|
|
|
150
150
|
del token_headers["Authorization"]
|
|
151
151
|
token_headers.update(
|
|
152
152
|
{
|
|
153
|
-
"authorization": f"Basic {self.
|
|
153
|
+
"authorization": f"Basic {self.reseller.basic_auth}",
|
|
154
154
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
155
155
|
},
|
|
156
156
|
)
|
|
@@ -3,7 +3,7 @@ from unittest.mock import patch
|
|
|
3
3
|
from asyncclick.testing import CliRunner
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
|
-
from smartbox.
|
|
6
|
+
from smartbox.reseller import SmartboxReseller
|
|
7
7
|
from smartbox.session import AsyncSession, AsyncSmartboxSession, Session
|
|
8
8
|
from smartbox.update_manager import UpdateManager
|
|
9
9
|
from tests.common import fake_get_request
|
|
@@ -25,7 +25,7 @@ def update_manager(mock_session):
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@pytest.fixture
|
|
28
|
-
def async_smartbox_session(mocker,
|
|
28
|
+
def async_smartbox_session(mocker, reseller):
|
|
29
29
|
async_smartbox_session = AsyncSmartboxSession(
|
|
30
30
|
api_name="test_api",
|
|
31
31
|
username="test_user",
|
|
@@ -47,7 +47,7 @@ def async_smartbox_session(mocker, resailer):
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
@pytest.fixture
|
|
50
|
-
def session(
|
|
50
|
+
def session(reseller):
|
|
51
51
|
return Session(
|
|
52
52
|
api_name="test_api",
|
|
53
53
|
username="test_user",
|
|
@@ -56,7 +56,7 @@ def session(resailer):
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
@pytest.fixture
|
|
59
|
-
def async_session(
|
|
59
|
+
def async_session(reseller):
|
|
60
60
|
api_name = "test_api"
|
|
61
61
|
username = "test_user"
|
|
62
62
|
password = "test_password"
|
|
@@ -76,7 +76,7 @@ def async_session(resailer):
|
|
|
76
76
|
|
|
77
77
|
@pytest.fixture
|
|
78
78
|
def person():
|
|
79
|
-
return
|
|
79
|
+
return SmartboxReseller(
|
|
80
80
|
name="test",
|
|
81
81
|
api_url="test_api",
|
|
82
82
|
basic_auth="test_credentials",
|
|
@@ -86,12 +86,12 @@ def person():
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
@pytest.fixture
|
|
89
|
-
def
|
|
89
|
+
def reseller(mocker):
|
|
90
90
|
return mocker.patch(
|
|
91
|
-
"smartbox.
|
|
91
|
+
"smartbox.reseller.AvailableResellers.resellers",
|
|
92
92
|
new_callable=mocker.PropertyMock,
|
|
93
93
|
return_value={
|
|
94
|
-
"test_api":
|
|
94
|
+
"test_api": SmartboxReseller(
|
|
95
95
|
name="test",
|
|
96
96
|
api_url="test_api",
|
|
97
97
|
basic_auth="test_credentials",
|
|
@@ -263,22 +263,14 @@ async def test_node_samples(runner, mock_session):
|
|
|
263
263
|
|
|
264
264
|
|
|
265
265
|
@pytest.mark.anyio
|
|
266
|
-
async def
|
|
267
|
-
mock_resailers = mocker.patch("smartbox.cmd.AvailableResailers.resailers")
|
|
268
|
-
mock_resailers.items.return_value = [
|
|
269
|
-
("resailer1", "details1"),
|
|
270
|
-
("resailer2", "details2"),
|
|
271
|
-
]
|
|
272
|
-
|
|
266
|
+
async def test_reseller(runner, mocker, reseller):
|
|
273
267
|
result = await runner.invoke(
|
|
274
268
|
smartbox,
|
|
275
|
-
[*DEFAULT_ARGS, "
|
|
269
|
+
[*DEFAULT_ARGS, "resellers"],
|
|
276
270
|
)
|
|
277
271
|
assert result.exit_code == 0
|
|
278
|
-
assert "
|
|
279
|
-
assert "
|
|
280
|
-
assert "resailer2" in result.output
|
|
281
|
-
assert "details2" in result.output
|
|
272
|
+
assert "test_api" in result.output
|
|
273
|
+
assert "http" in result.output
|
|
282
274
|
|
|
283
275
|
|
|
284
276
|
@pytest.mark.anyio
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from smartbox import AsyncSmartboxSession, ResellerNotExistError
|
|
4
|
+
from smartbox.reseller import AvailableResellers
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_available_resellers_existing_reseller():
|
|
8
|
+
reseller = AvailableResellers(api_url="api").reseller
|
|
9
|
+
assert reseller.name == "Helki"
|
|
10
|
+
assert reseller.api_url == "api"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_available_resellers_non_existing_reseller():
|
|
14
|
+
with pytest.raises(ResellerNotExistError):
|
|
15
|
+
AvailableResellers(api_url="non-existing-api").reseller
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_available_resellers_custom_reseller():
|
|
19
|
+
serial_id = 99
|
|
20
|
+
_reseller = AvailableResellers(
|
|
21
|
+
api_url="custom-api",
|
|
22
|
+
basic_auth="custom-auth",
|
|
23
|
+
web_url="https://custom-url.com",
|
|
24
|
+
serial_id=serial_id,
|
|
25
|
+
name="Custom",
|
|
26
|
+
)
|
|
27
|
+
reseller = _reseller.reseller
|
|
28
|
+
assert reseller.name == "Custom"
|
|
29
|
+
assert reseller.api_url == "custom-api"
|
|
30
|
+
assert reseller.basic_auth == "custom-auth"
|
|
31
|
+
assert reseller.web_url == "https://custom-url.com"
|
|
32
|
+
assert reseller.serial_id == serial_id
|
|
33
|
+
|
|
34
|
+
assert _reseller.name == "Custom"
|
|
35
|
+
assert _reseller.api_url == "custom-api"
|
|
36
|
+
assert _reseller.web_url == "https://custom-url.com"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_reseller_invalid_data():
|
|
40
|
+
with pytest.raises(ResellerNotExistError):
|
|
41
|
+
AvailableResellers(
|
|
42
|
+
api_url="invalid-api",
|
|
43
|
+
basic_auth="invalid-auth",
|
|
44
|
+
web_url="invalid-url",
|
|
45
|
+
serial_id="invalid-serial-id",
|
|
46
|
+
).reseller
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.asyncio
|
|
50
|
+
async def test_all_resellers():
|
|
51
|
+
for reseller in AvailableResellers.resellers.values():
|
|
52
|
+
_session = AsyncSmartboxSession(
|
|
53
|
+
username="", password="", api_name=reseller.api_url
|
|
54
|
+
)
|
|
55
|
+
check = await _session.health_check()
|
|
56
|
+
assert check is not None
|
|
57
|
+
version = await _session.api_version()
|
|
58
|
+
assert "major" in version
|