hardpy 0.10.1__tar.gz → 0.11.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.
- {hardpy-0.10.1 → hardpy-0.11.1}/PKG-INFO +1 -1
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/__init__.py +10 -2
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/cli/cli.py +91 -13
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/cli/template.py +0 -4
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/common/config.py +17 -21
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/common/stand_cloud/__init__.py +2 -1
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/common/stand_cloud/connector.py +32 -38
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/api.py +24 -2
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/asset-manifest.json +3 -3
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/index.html +1 -1
- hardpy-0.11.1/hardpy/hardpy_panel/frontend/dist/static/js/main.fb8b84a3.js +3 -0
- hardpy-0.11.1/hardpy/hardpy_panel/frontend/dist/static/js/main.fb8b84a3.js.map +1 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/db/base_store.py +7 -2
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/db/const.py +3 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/db/schema/v1.py +22 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/plugin.py +27 -20
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/pytest_call.py +30 -41
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/pytest_wrapper.py +21 -17
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/reporter/base.py +9 -4
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/reporter/hook_reporter.py +7 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/result/__init__.py +4 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/result/couchdb_config.py +6 -8
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/result/report_loader/stand_cloud_loader.py +6 -9
- hardpy-0.11.1/hardpy/pytest_hardpy/result/report_reader/stand_cloud_reader.py +84 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/utils/__init__.py +2 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/utils/connection_data.py +0 -4
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/utils/dialog_box.py +61 -8
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/utils/exception.py +1 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/pyproject.toml +1 -1
- hardpy-0.10.1/hardpy/hardpy_panel/frontend/dist/static/js/main.8a7d8f7d.js +0 -3
- hardpy-0.10.1/hardpy/hardpy_panel/frontend/dist/static/js/main.8a7d8f7d.js.map +0 -1
- {hardpy-0.10.1 → hardpy-0.11.1}/.gitignore +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/LICENSE +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/README.md +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/cli/__init__.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/common/__init__.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/common/stand_cloud/exception.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/common/stand_cloud/oauth_callback.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/common/stand_cloud/registration.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/common/stand_cloud/token_storage.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/__init__.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/favicon.ico +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/logo192.png +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/logo512.png +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/manifest.json +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/css/main.e8a862f1.css +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/css/main.e8a862f1.css.map +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/808.ce070002.chunk.js +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/808.ce070002.chunk.js.map +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-16px-paths.d605910e.chunk.js +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-16px-paths.d605910e.chunk.js.map +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-20px-paths.7ee05cc8.chunk.js +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-20px-paths.7ee05cc8.chunk.js.map +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js.map +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths.f63155c9.chunk.js +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-all-paths.f63155c9.chunk.js.map +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js.map +0 -0
- /hardpy-0.10.1/hardpy/hardpy_panel/frontend/dist/static/js/main.8a7d8f7d.js.LICENSE.txt → /hardpy-0.11.1/hardpy/hardpy_panel/frontend/dist/static/js/main.fb8b84a3.js.LICENSE.txt +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.520846c6beb41df528c8.eot +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.5c52b39c697f2323ce8b.svg +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.84db1772f4bfb529f64f.woff +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.b67ee1736e20e37a3225.woff2 +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-16.e02ecf515378db143652.ttf +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.429cacb8accf72488451.ttf +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.6ae3791ee2d86fc228a6.svg +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.8cecf62de42997e4d82f.woff2 +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.afbadb627d43b7857223.eot +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/blueprint-icons-20.e857f5a5132b8bfa71a1.woff +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/hardpy_panel/frontend/dist/static/media/logo_smol.5b16f92447a4a9e80331.png +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/__init__.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/db/__init__.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/db/base_connector.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/db/base_server.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/db/runstore.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/db/schema/__init__.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/db/statestore.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/reporter/__init__.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/reporter/runner_reporter.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/result/report_loader/__init__.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/result/report_loader/couchdb_loader.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/result/report_reader/__init__.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/result/report_reader/couchdb_reader.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/utils/const.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/utils/machineid.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/utils/node_info.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/utils/progress_calculator.py +0 -0
- {hardpy-0.10.1 → hardpy-0.11.1}/hardpy/pytest_hardpy/utils/singleton.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Copyright (c) 2024 Everypin
|
|
2
2
|
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
3
|
|
|
4
|
-
from hardpy.common.stand_cloud import StandCloudError
|
|
4
|
+
from hardpy.common.stand_cloud import StandCloudConnector, StandCloudError
|
|
5
5
|
from hardpy.pytest_hardpy.pytest_call import (
|
|
6
6
|
clear_operator_message,
|
|
7
7
|
get_current_attempt,
|
|
@@ -20,7 +20,11 @@ from hardpy.pytest_hardpy.pytest_call import (
|
|
|
20
20
|
set_stand_location,
|
|
21
21
|
set_stand_name,
|
|
22
22
|
)
|
|
23
|
-
from hardpy.pytest_hardpy.result import
|
|
23
|
+
from hardpy.pytest_hardpy.result import (
|
|
24
|
+
CouchdbLoader,
|
|
25
|
+
StandCloudLoader,
|
|
26
|
+
StandCloudReader,
|
|
27
|
+
)
|
|
24
28
|
from hardpy.pytest_hardpy.result.couchdb_config import CouchdbConfig
|
|
25
29
|
from hardpy.pytest_hardpy.utils import (
|
|
26
30
|
BaseWidget,
|
|
@@ -30,6 +34,7 @@ from hardpy.pytest_hardpy.utils import (
|
|
|
30
34
|
DuplicateSerialNumberError,
|
|
31
35
|
DuplicateTestStandLocationError,
|
|
32
36
|
DuplicateTestStandNameError,
|
|
37
|
+
HTMLComponent,
|
|
33
38
|
ImageComponent,
|
|
34
39
|
MultistepWidget,
|
|
35
40
|
NumericInputWidget,
|
|
@@ -48,12 +53,15 @@ __all__ = [
|
|
|
48
53
|
"DuplicateSerialNumberError",
|
|
49
54
|
"DuplicateTestStandLocationError",
|
|
50
55
|
"DuplicateTestStandNameError",
|
|
56
|
+
"HTMLComponent",
|
|
51
57
|
"ImageComponent",
|
|
52
58
|
"MultistepWidget",
|
|
53
59
|
"NumericInputWidget",
|
|
54
60
|
"RadiobuttonWidget",
|
|
61
|
+
"StandCloudConnector",
|
|
55
62
|
"StandCloudError",
|
|
56
63
|
"StandCloudLoader",
|
|
64
|
+
"StandCloudReader",
|
|
57
65
|
"StepWidget",
|
|
58
66
|
"TextInputWidget",
|
|
59
67
|
"clear_operator_message",
|
|
@@ -6,11 +6,12 @@ import sys
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Annotated, Optional
|
|
8
8
|
|
|
9
|
+
import requests
|
|
9
10
|
import typer
|
|
10
11
|
from uvicorn import run as uvicorn_run
|
|
11
12
|
|
|
12
13
|
from hardpy.cli.template import TemplateGenerator
|
|
13
|
-
from hardpy.common.config import ConfigManager
|
|
14
|
+
from hardpy.common.config import ConfigManager, HardpyConfig
|
|
14
15
|
from hardpy.common.stand_cloud import (
|
|
15
16
|
StandCloudConnector,
|
|
16
17
|
StandCloudError,
|
|
@@ -31,6 +32,10 @@ default_config = ConfigManager().get_config()
|
|
|
31
32
|
@cli.command()
|
|
32
33
|
def init( # noqa: PLR0913
|
|
33
34
|
tests_dir: Annotated[Optional[str], typer.Argument()] = None,
|
|
35
|
+
tests_name: str = typer.Option(
|
|
36
|
+
default="",
|
|
37
|
+
help="Specify a tests suite name.",
|
|
38
|
+
),
|
|
34
39
|
create_database: bool = typer.Option(
|
|
35
40
|
default=True,
|
|
36
41
|
help="Create CouchDB database.",
|
|
@@ -59,14 +64,6 @@ def init( # noqa: PLR0913
|
|
|
59
64
|
default=default_config.frontend.port,
|
|
60
65
|
help="Specify a frontend port.",
|
|
61
66
|
),
|
|
62
|
-
socket_host: str = typer.Option(
|
|
63
|
-
default=default_config.socket.host,
|
|
64
|
-
help="Specify a socket host.",
|
|
65
|
-
),
|
|
66
|
-
socket_port: int = typer.Option(
|
|
67
|
-
default=default_config.socket.port,
|
|
68
|
-
help="Specify a socket port.",
|
|
69
|
-
),
|
|
70
67
|
sc_address: str = typer.Option(
|
|
71
68
|
default="",
|
|
72
69
|
help="Specify a StandCloud address.",
|
|
@@ -80,6 +77,7 @@ def init( # noqa: PLR0913
|
|
|
80
77
|
|
|
81
78
|
Args:
|
|
82
79
|
tests_dir (str | None): Tests directory. Current directory + `tests` by default
|
|
80
|
+
tests_name (str): Tests suite name, "Tests" by default
|
|
83
81
|
create_database (bool): Flag to create database
|
|
84
82
|
database_user (str): Database user name
|
|
85
83
|
database_password (str): Database password
|
|
@@ -87,22 +85,20 @@ def init( # noqa: PLR0913
|
|
|
87
85
|
database_port (int): Database port
|
|
88
86
|
frontend_host (str): Panel operator host
|
|
89
87
|
frontend_port (int): Panel operator port
|
|
90
|
-
socket_host (str): Socket host
|
|
91
|
-
socket_port (int): Socket port
|
|
92
88
|
sc_address (str): StandCloud address
|
|
93
89
|
sc_connection_only (bool): Flag to check StandCloud service availability
|
|
94
90
|
"""
|
|
95
91
|
_tests_dir = tests_dir if tests_dir else default_config.tests_dir
|
|
92
|
+
_tests_name = tests_name if tests_name else default_config.tests_name
|
|
96
93
|
ConfigManager().init_config(
|
|
97
94
|
tests_dir=str(_tests_dir),
|
|
95
|
+
tests_name=_tests_name,
|
|
98
96
|
database_user=database_user,
|
|
99
97
|
database_password=database_password,
|
|
100
98
|
database_host=database_host,
|
|
101
99
|
database_port=database_port,
|
|
102
100
|
frontend_host=frontend_host,
|
|
103
101
|
frontend_port=frontend_port,
|
|
104
|
-
socket_host=socket_host,
|
|
105
|
-
socket_port=socket_port,
|
|
106
102
|
sc_address=sc_address,
|
|
107
103
|
sc_connection_only=sc_connection_only,
|
|
108
104
|
)
|
|
@@ -164,6 +160,48 @@ def run(tests_dir: Annotated[Optional[str], typer.Argument()] = None) -> None:
|
|
|
164
160
|
)
|
|
165
161
|
|
|
166
162
|
|
|
163
|
+
@cli.command()
|
|
164
|
+
def start(tests_dir: Annotated[Optional[str], typer.Argument()] = None) -> None:
|
|
165
|
+
"""Start HardPy tests.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
tests_dir (Optional[str]): Test directory. Current directory by default
|
|
169
|
+
"""
|
|
170
|
+
config = _get_config(tests_dir)
|
|
171
|
+
_check_config(config)
|
|
172
|
+
|
|
173
|
+
url = f"http://{config.frontend.host}:{config.frontend.port}/api/start"
|
|
174
|
+
_request_hardpy(url)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@cli.command()
|
|
178
|
+
def stop(tests_dir: Annotated[Optional[str], typer.Argument()] = None) -> None:
|
|
179
|
+
"""Stop HardPy tests.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
tests_dir (Optional[str]): Test directory. Current directory by default
|
|
183
|
+
"""
|
|
184
|
+
config = _get_config(tests_dir)
|
|
185
|
+
_check_config(config)
|
|
186
|
+
|
|
187
|
+
url = f"http://{config.frontend.host}:{config.frontend.port}/api/stop"
|
|
188
|
+
_request_hardpy(url)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@cli.command()
|
|
192
|
+
def status(tests_dir: Annotated[Optional[str], typer.Argument()] = None) -> None:
|
|
193
|
+
"""Get HardPy test launch status.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
tests_dir (Optional[str]): Test directory. Current directory by default
|
|
197
|
+
"""
|
|
198
|
+
config = _get_config(tests_dir)
|
|
199
|
+
_check_config(config)
|
|
200
|
+
|
|
201
|
+
url = f"http://{config.frontend.host}:{config.frontend.port}/api/status"
|
|
202
|
+
_request_hardpy(url)
|
|
203
|
+
|
|
204
|
+
|
|
167
205
|
@cli.command()
|
|
168
206
|
def sc_login(
|
|
169
207
|
address: Annotated[str, typer.Argument()],
|
|
@@ -207,5 +245,45 @@ def sc_logout() -> None:
|
|
|
207
245
|
print("HardPy logout failed")
|
|
208
246
|
|
|
209
247
|
|
|
248
|
+
def _get_config(tests_dir: str | None = None) -> HardpyConfig:
|
|
249
|
+
dir_path = Path.cwd() / tests_dir if tests_dir else Path.cwd()
|
|
250
|
+
config = ConfigManager().read_config(dir_path)
|
|
251
|
+
|
|
252
|
+
if not config:
|
|
253
|
+
print(f"Config at path {dir_path} not found.")
|
|
254
|
+
sys.exit()
|
|
255
|
+
|
|
256
|
+
return config
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _check_config(config: HardpyConfig) -> None:
|
|
260
|
+
url = f"http://{config.frontend.host}:{config.frontend.port}/api/hardpy_config"
|
|
261
|
+
error_msg = f"HardPy in directory {config.tests_dir} does not run."
|
|
262
|
+
try:
|
|
263
|
+
response = requests.get(url, timeout=2)
|
|
264
|
+
except Exception:
|
|
265
|
+
print(error_msg)
|
|
266
|
+
sys.exit()
|
|
267
|
+
|
|
268
|
+
running_config: dict = response.json()
|
|
269
|
+
if config.model_dump() != running_config:
|
|
270
|
+
print(error_msg)
|
|
271
|
+
sys.exit()
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _request_hardpy(url: str) -> None:
|
|
275
|
+
try:
|
|
276
|
+
response = requests.get(url, timeout=2)
|
|
277
|
+
except Exception:
|
|
278
|
+
print("HardPy operator panel is not running.")
|
|
279
|
+
sys.exit()
|
|
280
|
+
try:
|
|
281
|
+
status: dict = response.json().get("status", "ERROR")
|
|
282
|
+
except ValueError:
|
|
283
|
+
print(f"Hardpy internal error: {response}.")
|
|
284
|
+
sys.exit()
|
|
285
|
+
print(f"HardPy status: {status}.")
|
|
286
|
+
|
|
287
|
+
|
|
210
288
|
if __name__ == "__main__":
|
|
211
289
|
cli()
|
|
@@ -138,8 +138,6 @@ log_cli_format = %%(asctime)s [%%(levelname)s] %%(message)s
|
|
|
138
138
|
log_cli_date_format = %H:%M:%S
|
|
139
139
|
addopts = --hardpy-pt
|
|
140
140
|
--hardpy-db-url http://{}:{}@{}:{}/
|
|
141
|
-
--hardpy-sh {}
|
|
142
|
-
--hardpy-sp {}
|
|
143
141
|
"""
|
|
144
142
|
|
|
145
143
|
test_1_py = """import pytest
|
|
@@ -207,8 +205,6 @@ class TemplateGenerator:
|
|
|
207
205
|
self._config.database.password,
|
|
208
206
|
self._config.database.host,
|
|
209
207
|
self._config.database.port,
|
|
210
|
-
self._config.socket.host,
|
|
211
|
-
self._config.socket.port,
|
|
212
208
|
)
|
|
213
209
|
|
|
214
210
|
@property
|
|
@@ -42,14 +42,6 @@ class FrontendConfig(BaseModel):
|
|
|
42
42
|
port: int = 8000
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
class SocketConfig(BaseModel):
|
|
46
|
-
"""Socket configuration."""
|
|
47
|
-
|
|
48
|
-
model_config = ConfigDict(extra="forbid")
|
|
49
|
-
|
|
50
|
-
host: str = "localhost"
|
|
51
|
-
port: int = 6525
|
|
52
|
-
|
|
53
45
|
class StandCloudConfig(BaseModel):
|
|
54
46
|
"""StandCloud configuration."""
|
|
55
47
|
|
|
@@ -58,16 +50,16 @@ class StandCloudConfig(BaseModel):
|
|
|
58
50
|
address: str = ""
|
|
59
51
|
connection_only: bool = False
|
|
60
52
|
|
|
61
|
-
class HardpyConfig(BaseModel):
|
|
53
|
+
class HardpyConfig(BaseModel, extra="allow"):
|
|
62
54
|
"""HardPy configuration."""
|
|
63
55
|
|
|
64
56
|
model_config = ConfigDict(extra="forbid")
|
|
65
57
|
|
|
66
58
|
title: str = "HardPy TOML config"
|
|
67
59
|
tests_dir: str = "tests"
|
|
60
|
+
tests_name: str = ""
|
|
68
61
|
database: DatabaseConfig = DatabaseConfig()
|
|
69
62
|
frontend: FrontendConfig = FrontendConfig()
|
|
70
|
-
socket: SocketConfig = SocketConfig()
|
|
71
63
|
stand_cloud: StandCloudConfig = StandCloudConfig()
|
|
72
64
|
|
|
73
65
|
|
|
@@ -81,14 +73,13 @@ class ConfigManager:
|
|
|
81
73
|
def init_config( # noqa: PLR0913
|
|
82
74
|
cls,
|
|
83
75
|
tests_dir: str,
|
|
76
|
+
tests_name: str,
|
|
84
77
|
database_user: str,
|
|
85
78
|
database_password: str,
|
|
86
79
|
database_host: str,
|
|
87
80
|
database_port: int,
|
|
88
81
|
frontend_host: str,
|
|
89
82
|
frontend_port: int,
|
|
90
|
-
socket_host: str,
|
|
91
|
-
socket_port: int,
|
|
92
83
|
sc_address: str = "",
|
|
93
84
|
sc_connection_only: bool = False,
|
|
94
85
|
) -> None:
|
|
@@ -96,26 +87,24 @@ class ConfigManager:
|
|
|
96
87
|
|
|
97
88
|
Args:
|
|
98
89
|
tests_dir (str): Tests directory.
|
|
90
|
+
tests_name (str): Tests suite name.
|
|
99
91
|
database_user (str): Database user name.
|
|
100
92
|
database_password (str): Database password.
|
|
101
93
|
database_host (str): Database host.
|
|
102
94
|
database_port (int): Database port.
|
|
103
95
|
frontend_host (str): Operator panel host.
|
|
104
96
|
frontend_port (int): Operator panel port.
|
|
105
|
-
socket_host (str): Socket host.
|
|
106
|
-
socket_port (int): Socket port.
|
|
107
97
|
sc_address (str): StandCloud address.
|
|
108
98
|
sc_connection_only (bool): StandCloud check availability.
|
|
109
99
|
"""
|
|
110
100
|
cls.obj.tests_dir = str(tests_dir)
|
|
101
|
+
cls.obj.tests_name = tests_name
|
|
111
102
|
cls.obj.database.user = database_user
|
|
112
103
|
cls.obj.database.password = database_password
|
|
113
104
|
cls.obj.database.host = database_host
|
|
114
105
|
cls.obj.database.port = database_port
|
|
115
106
|
cls.obj.frontend.host = frontend_host
|
|
116
107
|
cls.obj.frontend.port = frontend_port
|
|
117
|
-
cls.obj.socket.host = socket_host
|
|
118
|
-
cls.obj.socket.port = socket_port
|
|
119
108
|
cls.obj.stand_cloud.address = sc_address
|
|
120
109
|
cls.obj.stand_cloud.connection_only = sc_connection_only
|
|
121
110
|
|
|
@@ -128,6 +117,8 @@ class ConfigManager:
|
|
|
128
117
|
"""
|
|
129
118
|
if not cls.obj.stand_cloud.address:
|
|
130
119
|
del cls.obj.stand_cloud
|
|
120
|
+
if not cls.obj.tests_name:
|
|
121
|
+
del cls.obj.tests_name
|
|
131
122
|
config_str = tomli_w.dumps(cls.obj.model_dump())
|
|
132
123
|
with Path.open(parent_dir / "hardpy.toml", "w") as file:
|
|
133
124
|
file.write(config_str)
|
|
@@ -149,13 +140,18 @@ class ConfigManager:
|
|
|
149
140
|
return None
|
|
150
141
|
try:
|
|
151
142
|
with Path.open(toml_path / "hardpy.toml", "rb") as f:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
logger.exception(
|
|
143
|
+
toml_data = tomli.load(f)
|
|
144
|
+
except tomli.TOMLDecodeError as exc:
|
|
145
|
+
msg = f"Error parsing TOML: {exc}"
|
|
146
|
+
logger.exception(msg)
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
cls.obj = HardpyConfig(**toml_data)
|
|
156
151
|
except ValidationError:
|
|
157
152
|
logger.exception("Error parsing TOML")
|
|
158
|
-
|
|
153
|
+
return None
|
|
154
|
+
return cls.obj
|
|
159
155
|
|
|
160
156
|
@classmethod
|
|
161
157
|
def get_config(cls) -> HardpyConfig:
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# Copyright (c) 2024 Everypin
|
|
2
2
|
# GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
3
3
|
|
|
4
|
-
from hardpy.common.stand_cloud.connector import StandCloudConnector
|
|
4
|
+
from hardpy.common.stand_cloud.connector import StandCloudAPIMode, StandCloudConnector
|
|
5
5
|
from hardpy.common.stand_cloud.exception import StandCloudError
|
|
6
6
|
from hardpy.common.stand_cloud.registration import login, logout
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
|
+
"StandCloudAPIMode",
|
|
9
10
|
"StandCloudConnector",
|
|
10
11
|
"StandCloudError",
|
|
11
12
|
"login",
|
|
@@ -4,19 +4,12 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
from datetime import datetime, timedelta, timezone
|
|
7
|
+
from enum import Enum
|
|
7
8
|
from logging import getLogger
|
|
8
9
|
from typing import TYPE_CHECKING, NamedTuple
|
|
9
10
|
|
|
10
|
-
from oauthlib.oauth2.rfc6749.errors import
|
|
11
|
-
|
|
12
|
-
MissingTokenError,
|
|
13
|
-
TokenExpiredError,
|
|
14
|
-
)
|
|
15
|
-
from requests.exceptions import (
|
|
16
|
-
ConnectionError as RequestConnectionError,
|
|
17
|
-
HTTPError,
|
|
18
|
-
InvalidURL,
|
|
19
|
-
)
|
|
11
|
+
from oauthlib.oauth2.rfc6749.errors import OAuth2Error
|
|
12
|
+
from requests.exceptions import RequestException
|
|
20
13
|
from requests_oauth2client import ApiClient, BearerToken
|
|
21
14
|
from requests_oauth2client.tokens import ExpiredAccessToken
|
|
22
15
|
from requests_oauthlib import OAuth2Session
|
|
@@ -42,24 +35,41 @@ class StandCloudURL(NamedTuple):
|
|
|
42
35
|
par: str
|
|
43
36
|
auth: str
|
|
44
37
|
|
|
38
|
+
|
|
39
|
+
class StandCloudAPIMode(str, Enum):
|
|
40
|
+
"""StandCloud API mode.
|
|
41
|
+
|
|
42
|
+
HARDPY for test stand, integration for third-party service.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
HARDPY = "hardpy"
|
|
46
|
+
INTEGRATION = "integration"
|
|
47
|
+
|
|
48
|
+
|
|
45
49
|
class StandCloudConnector:
|
|
46
50
|
"""StandCloud API connector."""
|
|
47
51
|
|
|
48
52
|
def __init__(
|
|
49
53
|
self,
|
|
50
54
|
addr: str,
|
|
55
|
+
api_mode: StandCloudAPIMode = StandCloudAPIMode.HARDPY,
|
|
56
|
+
api_version: int = 1,
|
|
51
57
|
) -> None:
|
|
52
58
|
"""Create StandCLoud loader.
|
|
53
59
|
|
|
54
60
|
Args:
|
|
55
|
-
addr (str
|
|
56
|
-
|
|
61
|
+
addr (str): StandCloud service name.
|
|
62
|
+
api_mode (StandCloudAPIMode): StandCloud API mode,
|
|
63
|
+
hardpy for test stand, integration for third-party service.
|
|
64
|
+
Default: StandCloudAPIMode.HARDPY.
|
|
65
|
+
api_version (int): StandCloud API version.
|
|
66
|
+
Default: 1.
|
|
57
67
|
"""
|
|
58
68
|
https_prefix = "https://"
|
|
59
69
|
auth_addr = addr + "/auth"
|
|
60
70
|
|
|
61
71
|
self._url: StandCloudURL = StandCloudURL(
|
|
62
|
-
api=https_prefix + addr +
|
|
72
|
+
api=https_prefix + addr + f"/{api_mode}/api/v{api_version}",
|
|
63
73
|
token=https_prefix + auth_addr + "/api/oidc/token",
|
|
64
74
|
par=https_prefix + auth_addr + "/api/oidc/pushed-authorization-request",
|
|
65
75
|
auth=https_prefix + auth_addr + "/api/oidc/authorization",
|
|
@@ -98,13 +108,11 @@ class StandCloudConnector:
|
|
|
98
108
|
try:
|
|
99
109
|
resp = api.get(verify=self._verify_ssl)
|
|
100
110
|
except ExpiredAccessToken as exc:
|
|
101
|
-
raise StandCloudError(str(exc))
|
|
102
|
-
except
|
|
103
|
-
raise StandCloudError(exc.description)
|
|
104
|
-
except
|
|
105
|
-
raise StandCloudError(exc.
|
|
106
|
-
except HTTPError as exc:
|
|
107
|
-
raise StandCloudError(exc.strerror) # type: ignore
|
|
111
|
+
raise StandCloudError(str(exc)) from exc
|
|
112
|
+
except OAuth2Error as exc:
|
|
113
|
+
raise StandCloudError(exc.description) from exc
|
|
114
|
+
except RequestException as exc:
|
|
115
|
+
raise StandCloudError(exc.strerror) from exc # type: ignore
|
|
108
116
|
|
|
109
117
|
return resp
|
|
110
118
|
|
|
@@ -192,15 +200,10 @@ class StandCloudConnector:
|
|
|
192
200
|
verify=False,
|
|
193
201
|
**extra,
|
|
194
202
|
)
|
|
195
|
-
except
|
|
196
|
-
raise StandCloudError(exc.description)
|
|
197
|
-
except
|
|
198
|
-
raise StandCloudError(exc.strerror) # type: ignore
|
|
199
|
-
except MissingTokenError as exc:
|
|
200
|
-
raise StandCloudError(exc.description)
|
|
201
|
-
except InvalidURL:
|
|
202
|
-
msg = "Authentication URL is not available"
|
|
203
|
-
raise StandCloudError(msg)
|
|
203
|
+
except OAuth2Error as exc:
|
|
204
|
+
raise StandCloudError(exc.description) from exc
|
|
205
|
+
except RequestException as exc:
|
|
206
|
+
raise StandCloudError(exc.strerror) from exc # type: ignore
|
|
204
207
|
self._token_update(ret) # type: ignore
|
|
205
208
|
|
|
206
209
|
return ApiClient(self._url.api + "/" + endpoint, session=session, timeout=10)
|
|
@@ -216,12 +219,3 @@ class StandCloudConnector:
|
|
|
216
219
|
expires_at=expires_at,
|
|
217
220
|
expires_in=expires_in,
|
|
218
221
|
)
|
|
219
|
-
|
|
220
|
-
def _get_service_name(self, addr: str) -> str:
|
|
221
|
-
addr_parts = addr.split(".")
|
|
222
|
-
number_of_parts = 3
|
|
223
|
-
service_position_in_address = 1
|
|
224
|
-
if isinstance(addr_parts, list) and len(addr_parts) >= number_of_parts:
|
|
225
|
-
return "/" + addr_parts[service_position_in_address]
|
|
226
|
-
msg = f"Invalid StandCloud address: {addr}"
|
|
227
|
-
raise StandCloudError(msg)
|
|
@@ -18,9 +18,9 @@ app.state.pytest_wrp = PyTestWrapper()
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class Status(str, Enum):
|
|
21
|
-
"""
|
|
21
|
+
"""HardPy status.
|
|
22
22
|
|
|
23
|
-
Statuses, that can be returned by HardPy
|
|
23
|
+
Statuses, that can be returned by HardPy API.
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
STOPPED = "stopped"
|
|
@@ -31,6 +31,16 @@ class Status(str, Enum):
|
|
|
31
31
|
ERROR = "error"
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
@app.get("/api/hardpy_config")
|
|
35
|
+
def hardpy_config() -> dict:
|
|
36
|
+
"""Get config of HardPy.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
dict: HardPy config
|
|
40
|
+
"""
|
|
41
|
+
return app.state.pytest_wrp.get_config()
|
|
42
|
+
|
|
43
|
+
|
|
34
44
|
@app.get("/api/start")
|
|
35
45
|
def start_pytest() -> dict:
|
|
36
46
|
"""Start pytest subprocess.
|
|
@@ -68,6 +78,18 @@ def collect_pytest() -> dict:
|
|
|
68
78
|
return {"status": Status.BUSY}
|
|
69
79
|
|
|
70
80
|
|
|
81
|
+
@app.get("/api/status")
|
|
82
|
+
def status() -> dict:
|
|
83
|
+
"""Get pytest subprocess status.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
dict[str, RunStatus]: run status
|
|
87
|
+
"""
|
|
88
|
+
is_running = app.state.pytest_wrp.is_running()
|
|
89
|
+
status = Status.BUSY if is_running else Status.READY
|
|
90
|
+
return {"status": status}
|
|
91
|
+
|
|
92
|
+
|
|
71
93
|
@app.get("/api/couch")
|
|
72
94
|
def couch_connection() -> dict:
|
|
73
95
|
"""Get couchdb connection string.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": {
|
|
3
3
|
"main.css": "/static/css/main.e8a862f1.css",
|
|
4
|
-
"main.js": "/static/js/main.
|
|
4
|
+
"main.js": "/static/js/main.fb8b84a3.js",
|
|
5
5
|
"blueprint-icons-all-paths-loader.js": "/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js",
|
|
6
6
|
"blueprint-icons-split-paths-by-size-loader.js": "/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js",
|
|
7
7
|
"static/js/808.ce070002.chunk.js": "/static/js/808.ce070002.chunk.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"static/media/logo_smol.png": "/static/media/logo_smol.5b16f92447a4a9e80331.png",
|
|
22
22
|
"index.html": "/index.html",
|
|
23
23
|
"main.e8a862f1.css.map": "/static/css/main.e8a862f1.css.map",
|
|
24
|
-
"main.
|
|
24
|
+
"main.fb8b84a3.js.map": "/static/js/main.fb8b84a3.js.map",
|
|
25
25
|
"blueprint-icons-all-paths-loader.0aa89747.chunk.js.map": "/static/js/blueprint-icons-all-paths-loader.0aa89747.chunk.js.map",
|
|
26
26
|
"blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js.map": "/static/js/blueprint-icons-split-paths-by-size-loader.52a072d3.chunk.js.map",
|
|
27
27
|
"808.ce070002.chunk.js.map": "/static/js/808.ce070002.chunk.js.map",
|
|
@@ -31,6 +31,6 @@
|
|
|
31
31
|
},
|
|
32
32
|
"entrypoints": [
|
|
33
33
|
"static/css/main.e8a862f1.css",
|
|
34
|
-
"static/js/main.
|
|
34
|
+
"static/js/main.fb8b84a3.js"
|
|
35
35
|
]
|
|
36
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>HardPy Operator Panel</title><script defer="defer" src="/static/js/main.
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>HardPy Operator Panel</title><script defer="defer" src="/static/js/main.fb8b84a3.js"></script><link href="/static/css/main.e8a862f1.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|