hardpy 0.8.0__py3-none-any.whl → 0.10.0__py3-none-any.whl

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.
Files changed (33) hide show
  1. hardpy/__init__.py +6 -1
  2. hardpy/cli/cli.py +78 -11
  3. hardpy/common/config.py +16 -0
  4. hardpy/common/stand_cloud/__init__.py +13 -0
  5. hardpy/common/stand_cloud/connector.py +227 -0
  6. hardpy/common/stand_cloud/exception.py +9 -0
  7. hardpy/common/stand_cloud/oauth_callback.py +95 -0
  8. hardpy/common/stand_cloud/registration.py +209 -0
  9. hardpy/common/stand_cloud/token_storage.py +23 -0
  10. hardpy/hardpy_panel/api.py +10 -8
  11. hardpy/hardpy_panel/frontend/dist/asset-manifest.json +3 -3
  12. hardpy/hardpy_panel/frontend/dist/index.html +1 -1
  13. hardpy/hardpy_panel/frontend/dist/static/js/main.8a7d8f7d.js +3 -0
  14. hardpy/hardpy_panel/frontend/dist/static/js/main.8a7d8f7d.js.map +1 -0
  15. hardpy/pytest_hardpy/db/const.py +6 -0
  16. hardpy/pytest_hardpy/db/schema/v1.py +12 -3
  17. hardpy/pytest_hardpy/plugin.py +56 -4
  18. hardpy/pytest_hardpy/pytest_call.py +79 -42
  19. hardpy/pytest_hardpy/pytest_wrapper.py +14 -4
  20. hardpy/pytest_hardpy/reporter/hook_reporter.py +10 -0
  21. hardpy/pytest_hardpy/result/__init__.py +4 -0
  22. hardpy/pytest_hardpy/result/report_loader/__init__.py +4 -0
  23. hardpy/pytest_hardpy/result/report_loader/stand_cloud_loader.py +72 -0
  24. hardpy/pytest_hardpy/utils/connection_data.py +2 -0
  25. hardpy/pytest_hardpy/utils/dialog_box.py +10 -0
  26. {hardpy-0.8.0.dist-info → hardpy-0.10.0.dist-info}/METADATA +9 -2
  27. {hardpy-0.8.0.dist-info → hardpy-0.10.0.dist-info}/RECORD +31 -24
  28. {hardpy-0.8.0.dist-info → hardpy-0.10.0.dist-info}/WHEEL +1 -1
  29. hardpy/hardpy_panel/frontend/dist/static/js/main.6f09d61a.js +0 -3
  30. hardpy/hardpy_panel/frontend/dist/static/js/main.6f09d61a.js.map +0 -1
  31. /hardpy/hardpy_panel/frontend/dist/static/js/{main.6f09d61a.js.LICENSE.txt → main.8a7d8f7d.js.LICENSE.txt} +0 -0
  32. {hardpy-0.8.0.dist-info → hardpy-0.10.0.dist-info}/entry_points.txt +0 -0
  33. {hardpy-0.8.0.dist-info → hardpy-0.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -29,3 +29,9 @@ class DatabaseField(str, Enum):
29
29
  ATTEMPT = "attempt"
30
30
  LOCATION = "location"
31
31
  HW_ID = "hw_id"
32
+ TITLE = "title"
33
+ VISIBLE = "visible"
34
+ IMAGE = "image"
35
+ ID = "id"
36
+ FONT_SIZE = "font_size"
37
+ ALERT = "alert"
@@ -42,7 +42,10 @@ class CaseStateStore(IBaseResult):
42
42
  "text": "some text"
43
43
  },
44
44
  "type": "textinput"
45
- }
45
+ },
46
+ visible: true,
47
+ id: "af6ac3e7-7ce8-4a6b-bb9d-88c3e10b5c7a",
48
+ font_size: 14
46
49
  }
47
50
  }
48
51
  }
@@ -214,6 +217,7 @@ class ResultStateStore(IBaseResult):
214
217
  "start_time": 1695817263,
215
218
  "status": "failed",
216
219
  "name": "hardpy-stand",
220
+ "alert": "",
217
221
  "dut": {
218
222
  "serial_number": "92c5a4bb-ecb0-42c5-89ac-e0caca0919fd",
219
223
  "part_number": "part_1",
@@ -241,7 +245,8 @@ class ResultStateStore(IBaseResult):
241
245
  "operator_msg": {
242
246
  "msg": "Operator message",
243
247
  "title": "Message",
244
- "visible": "True"
248
+ "visible": true,
249
+ "id": "f45ac1e7-2ce8-4a6b-bb9d-8863e30bcc78"
245
250
  },
246
251
  "modules": {
247
252
  "test_1_a": {
@@ -266,7 +271,10 @@ class ResultStateStore(IBaseResult):
266
271
  "text": "some text"
267
272
  },
268
273
  "type": "textinput"
269
- }
274
+ },
275
+ visible: true,
276
+ id: "f45bc1e7-2c18-4a4b-2b9d-8863e30bcc78",
277
+ font_size: 14
270
278
  }
271
279
  },
272
280
  "test_minute_parity": {
@@ -296,6 +304,7 @@ class ResultStateStore(IBaseResult):
296
304
  dut: Dut
297
305
  modules: dict[str, ModuleStateStore] = {}
298
306
  operator_msg: dict = {}
307
+ alert: str
299
308
 
300
309
 
301
310
  class ResultRunStore(IBaseResult):
@@ -7,6 +7,7 @@ from logging import getLogger
7
7
  from pathlib import Path, PurePath
8
8
  from platform import system
9
9
  from re import compile as re_compile
10
+ from time import sleep
10
11
  from typing import Any, Callable
11
12
 
12
13
  from _pytest._code.code import (
@@ -30,6 +31,7 @@ from pytest import (
30
31
  skip,
31
32
  )
32
33
 
34
+ from hardpy.common.stand_cloud.connector import StandCloudConnector, StandCloudError
33
35
  from hardpy.pytest_hardpy.reporter import HookReporter
34
36
  from hardpy.pytest_hardpy.utils import (
35
37
  ConnectionData,
@@ -39,6 +41,11 @@ from hardpy.pytest_hardpy.utils import (
39
41
  )
40
42
  from hardpy.pytest_hardpy.utils.node_info import TestDependencyInfo
41
43
 
44
+ if __debug__:
45
+ from urllib3 import disable_warnings
46
+ from urllib3.exceptions import InsecureRequestWarning
47
+
48
+ disable_warnings(InsecureRequestWarning)
42
49
 
43
50
  def pytest_addoption(parser: Parser) -> None:
44
51
  """Register argparse-style options."""
@@ -73,6 +80,18 @@ def pytest_addoption(parser: Parser) -> None:
73
80
  default=False,
74
81
  help="enable pytest-hardpy plugin",
75
82
  )
83
+ parser.addoption(
84
+ "--sc-address",
85
+ action="store",
86
+ default=con_data.sc_address,
87
+ help="StandCloud address",
88
+ )
89
+ parser.addoption(
90
+ "--sc-connection-only",
91
+ action="store_true",
92
+ default=con_data.sc_connection_only,
93
+ help="check StandCloud availability",
94
+ )
76
95
 
77
96
 
78
97
  # Bootstrapping hooks
@@ -125,6 +144,14 @@ class HardpyPlugin:
125
144
  if socket_host:
126
145
  con_data.socket_host = str(socket_host) # type: ignore
127
146
 
147
+ sc_address = config.getoption("--sc-address")
148
+ if sc_address:
149
+ con_data.sc_address = str(sc_address) # type: ignore
150
+
151
+ sc_connection_only = config.getoption("--sc-connection-only")
152
+ if sc_connection_only:
153
+ con_data.sc_connection_only = bool(sc_connection_only) # type: ignore
154
+
128
155
  config.addinivalue_line("markers", "case_name")
129
156
  config.addinivalue_line("markers", "module_name")
130
157
  config.addinivalue_line("markers", "dependency")
@@ -201,6 +228,27 @@ class HardpyPlugin:
201
228
  # ignore collect only mode
202
229
  return True
203
230
 
231
+ con_data = ConnectionData()
232
+
233
+ if con_data.sc_connection_only: # check
234
+ try:
235
+ sc_connector = StandCloudConnector(addr=con_data.sc_address)
236
+ except StandCloudError as exc:
237
+ msg = str(exc)
238
+ self._reporter.set_alert(msg)
239
+ exit(msg)
240
+ try:
241
+ sc_connector.healthcheck()
242
+ except Exception: # noqa: BLE001
243
+ addr = con_data.sc_address
244
+ msg = (
245
+ f"StandCloud service at the address {addr} "
246
+ "not available or HardPy user is not authorized"
247
+ )
248
+ self._reporter.set_alert(msg)
249
+ self._reporter.update_db_by_doc()
250
+ exit(msg)
251
+
204
252
  # testrun entrypoint
205
253
  self._reporter.start()
206
254
  self._reporter.update_db_by_doc()
@@ -254,6 +302,9 @@ class HardpyPlugin:
254
302
 
255
303
  # first attempt was in pytest_runtest_call
256
304
  for current_attempt in range(2, attempt + 1):
305
+ # add pause between attempts to verify STOP condition
306
+ sleep(1)
307
+
257
308
  self._reporter.set_module_status(module_id, TestStatus.RUN)
258
309
  self._reporter.set_case_status(module_id, case_id, TestStatus.RUN)
259
310
  self._reporter.set_case_attempt(module_id, case_id, current_attempt)
@@ -330,9 +381,10 @@ class HardpyPlugin:
330
381
  self._results[module_id][case_id] = None
331
382
 
332
383
  def _collect_module_result(self, module_id: str) -> None:
333
- if TestStatus.ERROR in self._results[module_id].values():
334
- status = TestStatus.ERROR
335
- elif TestStatus.FAILED in self._results[module_id].values():
384
+ if (
385
+ TestStatus.FAILED in self._results[module_id].values()
386
+ or TestStatus.ERROR in self._results[module_id].values()
387
+ ):
336
388
  status = TestStatus.FAILED
337
389
  elif TestStatus.SKIPPED in self._results[module_id].values():
338
390
  status = TestStatus.SKIPPED
@@ -352,7 +404,7 @@ class HardpyPlugin:
352
404
  self._stop_tests()
353
405
  return TestStatus.STOPPED
354
406
  case _:
355
- return TestStatus.ERROR
407
+ return TestStatus.FAILED
356
408
 
357
409
  def _stop_tests(self) -> None:
358
410
  """Update module and case statuses from READY or RUN to STOPPED."""
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import socket
6
6
  from dataclasses import dataclass
7
7
  from os import environ
8
- from time import sleep
8
+ from select import select
9
9
  from typing import Any
10
10
  from uuid import uuid4
11
11
 
@@ -25,6 +25,7 @@ from hardpy.pytest_hardpy.utils import (
25
25
  DuplicateSerialNumberError,
26
26
  DuplicateTestStandLocationError,
27
27
  DuplicateTestStandNameError,
28
+ ImageComponent,
28
29
  )
29
30
 
30
31
 
@@ -306,52 +307,76 @@ def run_dialog_box(dialog_box_data: DialogBox) -> Any: # noqa: ANN401
306
307
  current_test.case_id,
307
308
  DF.DIALOG_BOX,
308
309
  )
309
-
310
- reporter.set_doc_value(key, {}, statestore_only=True)
311
- reporter.update_db_by_doc()
312
- debounce_time = 0.2
313
- sleep(debounce_time)
310
+ _cleanup_widget(reporter, key)
314
311
 
315
312
  reporter.set_doc_value(key, dialog_box_data.to_dict(), statestore_only=True)
316
313
  reporter.update_db_by_doc()
317
314
 
315
+ # get socket data
318
316
  input_dbx_data = _get_socket_raw_data()
319
317
 
320
- # cleanup widget
321
- reporter.set_doc_value(key, {}, statestore_only=True)
322
- reporter.update_db_by_doc()
318
+ _cleanup_widget(reporter, key)
323
319
  return dialog_box_data.widget.convert_data(input_dbx_data)
324
320
 
325
321
 
326
- def set_operator_message(msg: str, title: str | None = None) -> None:
322
+ def set_operator_message(
323
+ msg: str,
324
+ title: str | None = None,
325
+ block: bool = True,
326
+ image: ImageComponent | None = None,
327
+ font_size: int = 14,
328
+ ) -> None:
327
329
  """Set operator message.
328
330
 
329
331
  The function should be used to handle events outside of testing.
330
332
  For messages to the operator during testing, there is the function `run_dialog_box`.
331
333
 
332
334
  Args:
333
- msg (str): Message
334
- title (str | None): Title
335
+ msg (str): message
336
+ title (str | None): title
337
+ image (ImageComponent | None): operator message info
338
+ block (bool): if True, the function will block until the message is closed
339
+ font_size (int): font size
335
340
  """
336
341
  reporter = RunnerReporter()
337
342
  key = reporter.generate_key(DF.OPERATOR_MSG)
343
+ _cleanup_widget(reporter, key)
338
344
 
339
- reporter.set_doc_value(key, {}, statestore_only=True)
340
- reporter.update_db_by_doc()
341
- debounce_time = 0.2
342
- sleep(debounce_time)
345
+ if font_size < 1:
346
+ msg = "The 'font_size' argument cannot be less than 1"
347
+ raise ValueError(msg)
343
348
 
344
- msg_data = {"msg": msg, "title": title, "visible": True}
345
- reporter.set_doc_value(key, msg_data, statestore_only=True)
346
- reporter.update_db_by_doc()
347
- is_msg_visible = _get_socket_raw_data()
348
- msg_data["visible"] = is_msg_visible
349
+ msg_data = {
350
+ DF.MSG: msg,
351
+ DF.TITLE: title,
352
+ DF.VISIBLE: True,
353
+ DF.IMAGE: image.to_dict() if image else None,
354
+ DF.ID: str(uuid4()),
355
+ DF.FONT_SIZE: int(font_size),
356
+ }
349
357
  reporter.set_doc_value(key, msg_data, statestore_only=True)
350
358
  reporter.update_db_by_doc()
351
359
 
352
- # cleanup widget
353
- reporter.set_doc_value(key, {}, statestore_only=True)
354
- reporter.update_db_by_doc()
360
+ if block:
361
+ # get socket data
362
+ is_msg_visible = _get_socket_raw_data()
363
+
364
+ msg_data[DF.VISIBLE] = is_msg_visible
365
+ reporter.set_doc_value(key, msg_data, statestore_only=True)
366
+ reporter.update_db_by_doc()
367
+
368
+ _cleanup_widget(reporter, key)
369
+
370
+
371
+ def clear_operator_message() -> None:
372
+ """Clear operator message.
373
+
374
+ Clears the current message to the operator if it exists, otherwise does nothing.
375
+ """
376
+ reporter = RunnerReporter()
377
+ key = reporter.generate_key(DF.OPERATOR_MSG)
378
+
379
+ _cleanup_widget(reporter, key)
355
380
 
356
381
 
357
382
  def get_current_attempt() -> int:
@@ -391,25 +416,37 @@ def _get_current_test() -> CurrentTestInfo:
391
416
 
392
417
  def _get_socket_raw_data() -> str:
393
418
  # create socket connection
394
- server = socket.socket()
395
- server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
396
- con_data = ConnectionData()
397
-
398
- try:
399
- server.bind((con_data.socket_host, con_data.socket_port))
400
- except OSError as exc:
401
- msg = "Socket creating error"
419
+ with socket.socket() as server:
420
+ server.setblocking(False)
421
+ server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
422
+
423
+ con_data = ConnectionData()
424
+ try:
425
+ server.bind((con_data.socket_host, con_data.socket_port))
426
+ except OSError as exc:
427
+ msg = "Socket creating error"
428
+ server.close()
429
+ raise RuntimeError(msg) from exc
430
+ server.listen(1)
431
+
432
+ client = None
433
+ while True:
434
+ ready_to_read, _, _ = select([server], [], [], 0.5)
435
+ if ready_to_read:
436
+ client, _ = server.accept()
437
+ break
438
+
439
+ # receive data
440
+ max_input_data_len = 1024
441
+ socket_data = client.recv(max_input_data_len).decode("utf-8")
442
+
443
+ # close connection
444
+ client.close()
402
445
  server.close()
403
- raise RuntimeError(msg) from exc
404
- server.listen(1)
405
- client, _ = server.accept()
406
446
 
407
- # receive data
408
- max_input_data_len = 1024
409
- socket_data = client.recv(max_input_data_len).decode("utf-8")
447
+ return socket_data
410
448
 
411
- # close connection
412
- client.close()
413
- server.close()
414
449
 
415
- return socket_data
450
+ def _cleanup_widget(reporter: RunnerReporter, key: str) -> None:
451
+ reporter.set_doc_value(key, {}, statestore_only=True)
452
+ reporter.update_db_by_doc()
@@ -47,6 +47,11 @@ class PyTestWrapper:
47
47
  str(self.config.socket.port),
48
48
  "--hardpy-sh",
49
49
  self.config.socket.host,
50
+ "--sc-address",
51
+ self.config.stand_cloud.address,
52
+ "--sc-connection-only"
53
+ if self.config.stand_cloud.connection_only
54
+ else "",
50
55
  "--hardpy-pt",
51
56
  ],
52
57
  cwd=ConfigManager().get_tests_path(),
@@ -63,6 +68,11 @@ class PyTestWrapper:
63
68
  str(self.config.socket.port),
64
69
  "--hardpy-sh",
65
70
  self.config.socket.host,
71
+ "--sc-address",
72
+ self.config.stand_cloud.address,
73
+ "--sc-connection-only"
74
+ if self.config.stand_cloud.connection_only
75
+ else "",
66
76
  "--hardpy-pt",
67
77
  ],
68
78
  cwd=ConfigManager().get_tests_path(),
@@ -134,10 +144,10 @@ class PyTestWrapper:
134
144
  bool: True if dialog box was confirmed/closed, else False
135
145
  """
136
146
  try:
137
- client = socket()
138
- client.connect((self.config.socket.host, self.config.socket.port))
139
- client.sendall(data.encode("utf-8"))
140
- client.close()
147
+ with socket() as client:
148
+ client.connect((self.config.socket.host, self.config.socket.port))
149
+ client.sendall(data.encode("utf-8"))
150
+ client.close()
141
151
  except Exception: # noqa: BLE001
142
152
  return False
143
153
  return True
@@ -37,6 +37,7 @@ class HookReporter(BaseReporter):
37
37
  self.set_doc_value(DF.PROGRESS, 0, statestore_only=True)
38
38
  self.set_doc_value(DF.ARTIFACT, {}, runstore_only=True)
39
39
  self.set_doc_value(DF.OPERATOR_MSG, {}, statestore_only=True)
40
+ self.set_doc_value(DF.ALERT, "", statestore_only=True)
40
41
 
41
42
  test_stand_tz = self.generate_key(DF.TEST_STAND, DF.TIMEZONE)
42
43
  self.set_doc_value(test_stand_tz, str(get_localzone().key))
@@ -51,6 +52,7 @@ class HookReporter(BaseReporter):
51
52
  self.set_doc_value(DF.START_TIME, start_time)
52
53
  self.set_doc_value(DF.STATUS, TestStatus.RUN)
53
54
  self.set_doc_value(DF.PROGRESS, 0, statestore_only=True)
55
+ self.set_doc_value(DF.ALERT, "", statestore_only=True)
54
56
 
55
57
  def finish(self, status: TestStatus) -> None:
56
58
  """Finish test.
@@ -185,6 +187,14 @@ class HookReporter(BaseReporter):
185
187
  )
186
188
  self.set_doc_value(key, attempt, statestore_only=True)
187
189
 
190
+ def set_alert(self, alert: str) -> None:
191
+ """Set alert message.
192
+
193
+ Args:
194
+ alert (str): alert message
195
+ """
196
+ self.set_doc_value(DF.ALERT, alert, statestore_only=True)
197
+
188
198
  def update_node_order(self, nodes: dict) -> None:
189
199
  """Update node order.
190
200
 
@@ -2,9 +2,13 @@
2
2
  # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
 
4
4
  from hardpy.pytest_hardpy.result.report_loader.couchdb_loader import CouchdbLoader
5
+ from hardpy.pytest_hardpy.result.report_loader.stand_cloud_loader import (
6
+ StandCloudLoader,
7
+ )
5
8
  from hardpy.pytest_hardpy.result.report_reader.couchdb_reader import CouchdbReader
6
9
 
7
10
  __all__ = [
8
11
  "CouchdbLoader",
9
12
  "CouchdbReader",
13
+ "StandCloudLoader",
10
14
  ]
@@ -4,7 +4,11 @@
4
4
  from hardpy.pytest_hardpy.result.report_loader.couchdb_loader import (
5
5
  CouchdbLoader,
6
6
  )
7
+ from hardpy.pytest_hardpy.result.report_loader.stand_cloud_loader import (
8
+ StandCloudLoader,
9
+ )
7
10
 
8
11
  __all__ = [
9
12
  "CouchdbLoader",
13
+ "StandCloudLoader",
10
14
  ]
@@ -0,0 +1,72 @@
1
+ # Copyright (c) 2024 Everypin
2
+ # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from oauthlib.oauth2.rfc6749.errors import InvalidGrantError, TokenExpiredError
8
+ from requests.exceptions import HTTPError
9
+ from requests_oauth2client.tokens import ExpiredAccessToken
10
+
11
+ from hardpy.common.stand_cloud.connector import StandCloudConnector, StandCloudError
12
+ from hardpy.pytest_hardpy.utils import ConnectionData
13
+
14
+ if TYPE_CHECKING:
15
+ from requests import Response
16
+
17
+ from hardpy.pytest_hardpy.db.schema import ResultRunStore
18
+
19
+
20
+ class StandCloudLoader:
21
+ """StandCloud report generator."""
22
+
23
+ def __init__(self, address: str | None = None) -> None:
24
+ """Create StandCLoud loader.
25
+
26
+ Args:
27
+ address (str | None, optional): StandCloud address.
28
+ Defaults to None (the value is taken from the.toml).
29
+ Can be used outside of HardPy applications.
30
+ """
31
+ self._verify_ssl = not __debug__
32
+ connection_data = ConnectionData()
33
+ sc_addr = address if address else connection_data.sc_address
34
+ self._sc_connector = StandCloudConnector(sc_addr)
35
+
36
+ def load(self, report: ResultRunStore) -> Response:
37
+ """Load report to the StandCloud.
38
+
39
+ Args:
40
+ report (ResultRunStore): report
41
+
42
+ Returns:
43
+ Response: StandCloud load response, must be 201
44
+
45
+ Raises:
46
+ StandCloudError: if report not uploaded to StandCloud
47
+ """
48
+ api = self._sc_connector.get_api("api/test_run")
49
+
50
+ try:
51
+ resp = api.post(verify=self._verify_ssl, json=report.model_dump())
52
+ except ExpiredAccessToken as exc:
53
+ raise StandCloudError(str(exc)) # type: ignore
54
+ except TokenExpiredError as exc:
55
+ raise StandCloudError(exc.description)
56
+ except InvalidGrantError as exc:
57
+ raise StandCloudError(exc.description)
58
+ except HTTPError as exc:
59
+ return exc.response # type: ignore
60
+
61
+ return resp
62
+
63
+ def healthcheck(self) -> Response:
64
+ """Healthcheck of StandCloud API.
65
+
66
+ Returns:
67
+ Response: StandCloud healthcheck response, must be 200
68
+
69
+ Raises:
70
+ StandCloudError: if StandCloud is unavailable
71
+ """
72
+ return self._sc_connector.healthcheck()
@@ -13,3 +13,5 @@ class ConnectionData(metaclass=SingletonMeta):
13
13
  self.database_url: str = "http://dev:dev@localhost:5984/"
14
14
  self.socket_host: str = gethostname()
15
15
  self.socket_port: int = 6525
16
+ self.sc_address: str = ""
17
+ self.sc_connection_only: bool = False
@@ -9,6 +9,7 @@ from copy import deepcopy
9
9
  from dataclasses import dataclass
10
10
  from enum import Enum
11
11
  from typing import Any, Final
12
+ from uuid import uuid4
12
13
 
13
14
  from hardpy.pytest_hardpy.utils.exception import ImageError, WidgetInfoError
14
15
 
@@ -315,6 +316,7 @@ class DialogBox:
315
316
  title_bar (str | None): title bar
316
317
  widget (IWidget | None): widget info
317
318
  image (ImageComponent | None): image
319
+ font_size (int): font size
318
320
  """
319
321
 
320
322
  def __init__(
@@ -323,11 +325,19 @@ class DialogBox:
323
325
  title_bar: str | None = None,
324
326
  widget: IWidget | None = None,
325
327
  image: ImageComponent | None = None,
328
+ font_size: int = 14,
326
329
  ) -> None:
327
330
  self.widget: IWidget = BaseWidget() if widget is None else widget
328
331
  self.image: ImageComponent | None = image
329
332
  self.dialog_text: str = dialog_text
330
333
  self.title_bar: str | None = title_bar
334
+ self.visible: bool = True
335
+ self.id = str(uuid4())
336
+ self.font_size = font_size
337
+
338
+ if font_size < 1:
339
+ msg = "The 'font_size' argument cannot be less than 1"
340
+ raise ValueError(msg)
331
341
 
332
342
  def to_dict(self) -> dict:
333
343
  """Convert DialogBox to dictionary.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: hardpy
3
- Version: 0.8.0
3
+ Version: 0.10.0
4
4
  Summary: HardPy library for device testing
5
5
  Project-URL: Homepage, https://github.com/everypinio/hardpy/
6
6
  Project-URL: Documentation, https://everypinio.github.io/hardpy/
@@ -18,6 +18,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
21
22
  Classifier: Topic :: Software Development :: Libraries
22
23
  Classifier: Topic :: Software Development :: Testing
23
24
  Classifier: Topic :: Software Development :: Testing :: Acceptance
@@ -25,11 +26,17 @@ Classifier: Topic :: Utilities
25
26
  Requires-Python: >=3.10
26
27
  Requires-Dist: fastapi>=0.100.1
27
28
  Requires-Dist: glom>=23.3.0
29
+ Requires-Dist: jinja2<4,>=3
30
+ Requires-Dist: keyring<26,>=25.0.0
28
31
  Requires-Dist: natsort>=8.4.0
32
+ Requires-Dist: oauthlib<4,>=3.1.0
29
33
  Requires-Dist: py-machineid~=0.6.0
30
34
  Requires-Dist: pycouchdb<2,>=1.14.2
31
35
  Requires-Dist: pydantic<3,>=2.4.0
32
36
  Requires-Dist: pytest<9,>=7
37
+ Requires-Dist: requests-oauth2client<2,>=1.5.0
38
+ Requires-Dist: requests-oauthlib<3,>=2.0.0
39
+ Requires-Dist: requests<3,>=2.30.0
33
40
  Requires-Dist: tomli-w<2,>=1.1.0
34
41
  Requires-Dist: tomli<3,>=2.0.1
35
42
  Requires-Dist: typer<1,>=0.12