smartbox 2.1.3__tar.gz → 2.2.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.
- smartbox-2.2.0/.github/ISSUE_TEMPLATE/new-reseller.md +18 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/.github/workflows/publish-to-pypi.yml +1 -1
- {smartbox-2.1.3 → smartbox-2.2.0}/.github/workflows/python-package.yml +3 -3
- {smartbox-2.1.3 → smartbox-2.2.0}/PKG-INFO +7 -7
- {smartbox-2.1.3 → smartbox-2.2.0}/README.md +6 -6
- {smartbox-2.1.3 → smartbox-2.2.0}/pyproject.toml +1 -1
- {smartbox-2.1.3 → smartbox-2.2.0}/src/smartbox/__init__.py +5 -5
- {smartbox-2.1.3 → smartbox-2.2.0}/src/smartbox/cmd.py +5 -5
- {smartbox-2.1.3 → smartbox-2.2.0}/src/smartbox/error.py +2 -2
- smartbox-2.1.3/src/smartbox/resailer.py → smartbox-2.2.0/src/smartbox/reseller.py +41 -35
- {smartbox-2.1.3 → smartbox-2.2.0}/src/smartbox/session.py +13 -13
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/conftest.py +8 -8
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/test_cmd.py +8 -8
- smartbox-2.2.0/tests/test_reseller.py +58 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/test_session.py +11 -11
- {smartbox-2.1.3 → smartbox-2.2.0}/uv.lock +1 -1
- 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.0}/.github/FUNDING.yml +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/.github/dependabot.yml +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/.gitignore +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/.pre-commit-config.yaml +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/.vscode/settings.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/CHANGELOG.md +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/CODEOWNERS +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/LICENSE +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/api-notes.md +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/codecov.yml +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/src/smartbox/models.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/src/smartbox/py.typed +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/src/smartbox/socket.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/src/smartbox/update_manager.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/__init__.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/common.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/acm/1/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/acm/1/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/acm/1/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/connected.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/htr/0/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/htr/0/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/htr/0/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/htr_system/power_limit.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/mgr/away_status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/mgr/nodes.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/pmo/3/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/pmo/3/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device1/pmo/3/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/connected.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/0/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/0/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/0/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/1/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/1/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/1/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/2/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/2/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/2/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/3/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/3/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/3/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/4/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/4/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/4/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/5/samples.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/5/setup.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_mod/5/status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/htr_system/power_limit.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/mgr/away_status.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs/device2/mgr/nodes.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/devs.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/grouped_devs.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/groups/test_home/guest_users.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/fixtures/test_path.json +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/test_models.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/tests/test_update_manager.py +0 -0
- {smartbox-2.1.3 → smartbox-2.2.0}/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.5.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.5.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.5.0
|
|
51
51
|
with:
|
|
52
52
|
python-version: ${{ matrix.python-version }}
|
|
53
53
|
- name: Install the project
|
|
@@ -70,7 +70,7 @@ 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.5.0
|
|
74
74
|
with:
|
|
75
75
|
python-version: ${{ matrix.python-version }}
|
|
76
76
|
- name: Install the project
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: smartbox
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
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
|
|
@@ -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.
|
|
@@ -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,49 +21,55 @@ 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-lhz":
|
|
66
|
-
name="Technotherm",
|
|
71
|
+
"api-lhz": SmartboxReseller(
|
|
72
|
+
name="Technotherm / Lucht-LHZ",
|
|
67
73
|
web_url="https://ttiapp.technotherm.com/",
|
|
68
74
|
api_url="api-lhz",
|
|
69
75
|
serial_id=16,
|
|
@@ -78,7 +84,7 @@ class AvailableResailers:
|
|
|
78
84
|
serial_id: int | None = None,
|
|
79
85
|
name: str = "Smartbox",
|
|
80
86
|
) -> None:
|
|
81
|
-
"""Check if
|
|
87
|
+
"""Check if reseller is already available or try to create one."""
|
|
82
88
|
self._api_url = api_url
|
|
83
89
|
self._basic_auth = basic_auth
|
|
84
90
|
self._web_url = web_url
|
|
@@ -86,27 +92,27 @@ class AvailableResailers:
|
|
|
86
92
|
self._name = name
|
|
87
93
|
|
|
88
94
|
@property
|
|
89
|
-
def
|
|
90
|
-
"""Get the
|
|
91
|
-
|
|
92
|
-
if
|
|
95
|
+
def reseller(self) -> SmartboxReseller:
|
|
96
|
+
"""Get the reseller."""
|
|
97
|
+
reseller = self.resellers.get(self._api_url, None)
|
|
98
|
+
if reseller is None:
|
|
93
99
|
if (
|
|
94
100
|
self._basic_auth is None
|
|
95
101
|
or self._web_url is None
|
|
96
102
|
or self._serial_id is None
|
|
97
103
|
):
|
|
98
|
-
msg = "This
|
|
99
|
-
raise
|
|
104
|
+
msg = "This reseller is not yet available or some arguments are missing."
|
|
105
|
+
raise ResellerNotExistError(msg)
|
|
100
106
|
try:
|
|
101
107
|
_LOGGER.debug(
|
|
102
|
-
"Creating a new
|
|
108
|
+
"Creating a new reseller api_url (%s), name=%s, web_url %s, basic_auth=%s, serial_id=%s",
|
|
103
109
|
self._api_url,
|
|
104
110
|
self._name,
|
|
105
111
|
self._web_url,
|
|
106
112
|
self._basic_auth,
|
|
107
113
|
self._serial_id,
|
|
108
114
|
)
|
|
109
|
-
|
|
115
|
+
reseller = SmartboxReseller(
|
|
110
116
|
api_url=self._api_url,
|
|
111
117
|
basic_auth=self._basic_auth,
|
|
112
118
|
web_url=self._web_url,
|
|
@@ -114,26 +120,26 @@ class AvailableResailers:
|
|
|
114
120
|
name=self._name,
|
|
115
121
|
)
|
|
116
122
|
except ValidationError as e:
|
|
117
|
-
raise
|
|
123
|
+
raise ResellerNotExistError from e
|
|
118
124
|
_LOGGER.debug(
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
"Reseller api_url (%s), name=%s, web_url %s",
|
|
126
|
+
reseller.api_url,
|
|
127
|
+
reseller.name,
|
|
128
|
+
reseller.web_url,
|
|
123
129
|
)
|
|
124
|
-
return
|
|
130
|
+
return reseller
|
|
125
131
|
|
|
126
132
|
@property
|
|
127
133
|
def api_url(self) -> str:
|
|
128
134
|
"""Get the api sub domain url."""
|
|
129
|
-
return self.
|
|
135
|
+
return self.reseller.api_url
|
|
130
136
|
|
|
131
137
|
@property
|
|
132
138
|
def name(self) -> str:
|
|
133
|
-
"""Get the name of
|
|
134
|
-
return self.
|
|
139
|
+
"""Get the name of reseller."""
|
|
140
|
+
return self.reseller.name
|
|
135
141
|
|
|
136
142
|
@property
|
|
137
143
|
def web_url(self) -> str:
|
|
138
|
-
"""Get the public websit of the
|
|
139
|
-
return self.
|
|
144
|
+
"""Get the public websit of the reseller."""
|
|
145
|
+
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,21 +263,21 @@ async def test_node_samples(runner, mock_session):
|
|
|
263
263
|
|
|
264
264
|
|
|
265
265
|
@pytest.mark.anyio
|
|
266
|
-
async def
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
("
|
|
270
|
-
("
|
|
266
|
+
async def test_reseller(runner, mocker):
|
|
267
|
+
mock_resellers = mocker.patch("smartbox.cmd.AvailableResellers.resellers")
|
|
268
|
+
mock_resellers.items.return_value = [
|
|
269
|
+
("reseller1", "details1"),
|
|
270
|
+
("reseller2", "details2"),
|
|
271
271
|
]
|
|
272
272
|
|
|
273
273
|
result = await runner.invoke(
|
|
274
274
|
smartbox,
|
|
275
|
-
[*DEFAULT_ARGS, "
|
|
275
|
+
[*DEFAULT_ARGS, "resellers"],
|
|
276
276
|
)
|
|
277
277
|
assert result.exit_code == 0
|
|
278
|
-
assert "
|
|
278
|
+
assert "reseller1" in result.output
|
|
279
279
|
assert "details1" in result.output
|
|
280
|
-
assert "
|
|
280
|
+
assert "reseller2" in result.output
|
|
281
281
|
assert "details2" in result.output
|
|
282
282
|
|
|
283
283
|
|
|
@@ -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
|
|
@@ -721,7 +721,7 @@ async def test_async_session_init():
|
|
|
721
721
|
|
|
722
722
|
|
|
723
723
|
@pytest.mark.asyncio
|
|
724
|
-
async def test_async_session_init_defaults(
|
|
724
|
+
async def test_async_session_init_defaults(reseller):
|
|
725
725
|
api_name = "test_api"
|
|
726
726
|
username = "test_user"
|
|
727
727
|
password = "test_password"
|
|
@@ -777,7 +777,7 @@ async def test_authentication_success(async_session):
|
|
|
777
777
|
mock_post.assert_called_once_with(
|
|
778
778
|
url=f"{async_session._api_host}/client/token",
|
|
779
779
|
headers={
|
|
780
|
-
"authorization": f"Basic {async_session.
|
|
780
|
+
"authorization": f"Basic {async_session.reseller.basic_auth}",
|
|
781
781
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
782
782
|
"x-referer": "http",
|
|
783
783
|
"x-serialid": "10",
|
|
@@ -812,10 +812,10 @@ async def test_authentication_invalid_response(async_session):
|
|
|
812
812
|
mock_post.assert_called_once_with(
|
|
813
813
|
url=f"{async_session._api_host}/client/token",
|
|
814
814
|
headers={
|
|
815
|
-
"authorization": f"Basic {async_session.
|
|
815
|
+
"authorization": f"Basic {async_session.reseller.basic_auth}",
|
|
816
816
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
817
|
-
"x-serialid": f"{async_session.
|
|
818
|
-
"x-referer": f"{async_session.
|
|
817
|
+
"x-serialid": f"{async_session.reseller.serial_id}",
|
|
818
|
+
"x-referer": f"{async_session.reseller.web_url}",
|
|
819
819
|
},
|
|
820
820
|
data=credentials,
|
|
821
821
|
)
|
|
@@ -847,10 +847,10 @@ async def test_authentication_client_response_error(async_session):
|
|
|
847
847
|
mock_post.assert_called_once_with(
|
|
848
848
|
url=f"{async_session._api_host}/client/token",
|
|
849
849
|
headers={
|
|
850
|
-
"authorization": f"Basic {async_session.
|
|
850
|
+
"authorization": f"Basic {async_session.reseller.basic_auth}",
|
|
851
851
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
852
|
-
"x-serialid": f"{async_session.
|
|
853
|
-
"x-referer": f"{async_session.
|
|
852
|
+
"x-serialid": f"{async_session.reseller.serial_id}",
|
|
853
|
+
"x-referer": f"{async_session.reseller.web_url}",
|
|
854
854
|
},
|
|
855
855
|
data=credentials,
|
|
856
856
|
)
|
|
@@ -877,10 +877,10 @@ async def test_authentication_client_response_unavailable(async_session):
|
|
|
877
877
|
mock_post.assert_called_once_with(
|
|
878
878
|
url=f"{async_session._api_host}/client/token",
|
|
879
879
|
headers={
|
|
880
|
-
"authorization": f"Basic {async_session.
|
|
880
|
+
"authorization": f"Basic {async_session.reseller.basic_auth}",
|
|
881
881
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
882
|
-
"x-serialid": f"{async_session.
|
|
883
|
-
"x-referer": f"{async_session.
|
|
882
|
+
"x-serialid": f"{async_session.reseller.serial_id}",
|
|
883
|
+
"x-referer": f"{async_session.reseller.web_url}",
|
|
884
884
|
},
|
|
885
885
|
data=credentials,
|
|
886
886
|
)
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: New resailer
|
|
3
|
-
about: Add a new available resailer.
|
|
4
|
-
title: "[RESAILER] New available resailer"
|
|
5
|
-
labels: resailer
|
|
6
|
-
assignees: Delmael
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
> [!NOTE]
|
|
11
|
-
> If you do not see your current resailer 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 resailers` command that my resailer is not already present
|
|
14
|
-
* **Website url (mandatory)** :
|
|
15
|
-
* Name of the resailers:
|
|
16
|
-
* API url :
|
|
17
|
-
* Basic auth headers :
|
|
18
|
-
* Serial id:
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from smartbox import AsyncSmartboxSession, ResailerNotExistError
|
|
4
|
-
from smartbox.resailer import AvailableResailers
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def test_available_resailers_existing_resailer():
|
|
8
|
-
resailer = AvailableResailers(api_url="api").resailer
|
|
9
|
-
assert resailer.name == "Helki"
|
|
10
|
-
assert resailer.api_url == "api"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_available_resailers_non_existing_resailer():
|
|
14
|
-
with pytest.raises(ResailerNotExistError):
|
|
15
|
-
AvailableResailers(api_url="non-existing-api").resailer
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def test_available_resailers_custom_resailer():
|
|
19
|
-
serial_id = 99
|
|
20
|
-
_resailer = AvailableResailers(
|
|
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
|
-
resailer = _resailer.resailer
|
|
28
|
-
assert resailer.name == "Custom"
|
|
29
|
-
assert resailer.api_url == "custom-api"
|
|
30
|
-
assert resailer.basic_auth == "custom-auth"
|
|
31
|
-
assert resailer.web_url == "https://custom-url.com"
|
|
32
|
-
assert resailer.serial_id == serial_id
|
|
33
|
-
|
|
34
|
-
assert _resailer.name == "Custom"
|
|
35
|
-
assert _resailer.api_url == "custom-api"
|
|
36
|
-
assert _resailer.web_url == "https://custom-url.com"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def test_resailer_invalid_data():
|
|
40
|
-
with pytest.raises(ResailerNotExistError):
|
|
41
|
-
AvailableResailers(
|
|
42
|
-
api_url="invalid-api",
|
|
43
|
-
basic_auth="invalid-auth",
|
|
44
|
-
web_url="invalid-url",
|
|
45
|
-
serial_id="invalid-serial-id",
|
|
46
|
-
).resailer
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@pytest.mark.asyncio
|
|
50
|
-
async def test_all_resailers():
|
|
51
|
-
for resailer in AvailableResailers.resailers.values():
|
|
52
|
-
_session = AsyncSmartboxSession(
|
|
53
|
-
username="", password="", api_name=resailer.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|