hardpy 0.17.1__py3-none-any.whl → 0.18.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 (23) hide show
  1. hardpy/cli/cli.py +50 -7
  2. hardpy/common/config.py +14 -11
  3. hardpy/common/stand_cloud/connector.py +20 -1
  4. hardpy/common/stand_cloud/token_manager.py +26 -1
  5. hardpy/hardpy_panel/api.py +93 -2
  6. hardpy/hardpy_panel/frontend/dist/assets/{allPaths-LQifhvrX.js → allPaths-31ulJ0tA.js} +1 -1
  7. hardpy/hardpy_panel/frontend/dist/assets/{allPathsLoader-C-JecT3u.js → allPathsLoader-HPn4WHWu.js} +2 -2
  8. hardpy/hardpy_panel/frontend/dist/assets/{browser-ponyfill-B-CPdmQc.js → browser-ponyfill-BQ1ipruI.js} +1 -1
  9. hardpy/hardpy_panel/frontend/dist/assets/{index-B2oxSaK6.js → index-BK2y65ib.js} +119 -119
  10. hardpy/hardpy_panel/frontend/dist/assets/{splitPathsBySizeLoader-CBTnepth.js → splitPathsBySizeLoader-ev1ZiRR9.js} +1 -1
  11. hardpy/hardpy_panel/frontend/dist/index.html +1 -1
  12. hardpy/pytest_hardpy/db/__init__.py +3 -1
  13. hardpy/pytest_hardpy/db/tempstore.py +54 -0
  14. hardpy/pytest_hardpy/plugin.py +34 -1
  15. hardpy/pytest_hardpy/pytest_wrapper.py +2 -0
  16. hardpy/pytest_hardpy/result/report_loader/stand_cloud_loader.py +8 -4
  17. hardpy/pytest_hardpy/result/report_synchronizer/__init__.py +10 -0
  18. hardpy/pytest_hardpy/result/report_synchronizer/synchronizer.py +121 -0
  19. {hardpy-0.17.1.dist-info → hardpy-0.18.0.dist-info}/METADATA +3 -2
  20. {hardpy-0.17.1.dist-info → hardpy-0.18.0.dist-info}/RECORD +23 -20
  21. {hardpy-0.17.1.dist-info → hardpy-0.18.0.dist-info}/WHEEL +0 -0
  22. {hardpy-0.17.1.dist-info → hardpy-0.18.0.dist-info}/entry_points.txt +0 -0
  23. {hardpy-0.17.1.dist-info → hardpy-0.18.0.dist-info}/licenses/LICENSE +0 -0
hardpy/cli/cli.py CHANGED
@@ -67,13 +67,21 @@ def init( # noqa: PLR0913
67
67
  help="Specify a frontend port.",
68
68
  ),
69
69
  sc_address: str = typer.Option(
70
- default="",
70
+ default=default_config.stand_cloud.address,
71
71
  help="Specify a StandCloud address.",
72
72
  ),
73
73
  sc_connection_only: bool = typer.Option(
74
- default=False,
74
+ default=default_config.stand_cloud.connection_only,
75
75
  help="Check StandCloud service availability before start.",
76
76
  ),
77
+ sc_autosync: bool = typer.Option(
78
+ default=default_config.stand_cloud.autosync,
79
+ help="Enable StandCloud auto syncronization.",
80
+ ),
81
+ sc_api_key: str | None = typer.Option(
82
+ default=default_config.stand_cloud.api_key,
83
+ help="Specify a StandCloud API key.",
84
+ ),
77
85
  ) -> None:
78
86
  """Initialize HardPy tests directory.
79
87
 
@@ -90,6 +98,8 @@ def init( # noqa: PLR0913
90
98
  frontend_language (str): Panel operator language
91
99
  sc_address (str): StandCloud address
92
100
  sc_connection_only (bool): Flag to check StandCloud service availability
101
+ sc_autosync (bool): Flag to enable StandCloud auto syncronization
102
+ sc_api_key (str | None): StandCloud API key
93
103
  """
94
104
  dir_path = Path(Path.cwd() / tests_dir if tests_dir else "tests")
95
105
  config_manager = ConfigManager()
@@ -104,6 +114,8 @@ def init( # noqa: PLR0913
104
114
  frontend_language=default_config.frontend.language,
105
115
  sc_address=sc_address,
106
116
  sc_connection_only=sc_connection_only,
117
+ sc_autosync=sc_autosync,
118
+ sc_api_key=sc_api_key,
107
119
  )
108
120
  # create tests directory
109
121
  Path.mkdir(dir_path, exist_ok=True, parents=True)
@@ -142,6 +154,7 @@ def run(tests_dir: Annotated[Optional[str], typer.Argument()] = None) -> None:
142
154
  tests_dir (Optional[str]): Test directory. Current directory by default
143
155
  """
144
156
  config = _get_config(tests_dir)
157
+ _validate_config(config)
145
158
 
146
159
  print("\nLaunch the HardPy operator panel...")
147
160
 
@@ -263,6 +276,30 @@ def sc_logout(address: Annotated[str, typer.Argument()]) -> None:
263
276
  print(f"HardPy logout failed from {address}")
264
277
 
265
278
 
279
+ @cli.command()
280
+ def sc_sync(
281
+ tests_dir: Annotated[Optional[str], typer.Argument()] = None,
282
+ timeout: int = typer.Option(
283
+ default="60",
284
+ help="Specify a synchronization timeout.",
285
+ ),
286
+ ) -> str:
287
+ """Synchronize HardPy tests with StandCloud.
288
+
289
+ Args:
290
+ tests_dir (Optional[str]): Test directory. Current directory by default
291
+ timeout (int): Synchronization timeout
292
+ """
293
+ try:
294
+ _timeout = int(timeout)
295
+ except ValueError:
296
+ print("Timeout must be a number.")
297
+ sys.exit()
298
+ config = _get_config(tests_dir, validate=True)
299
+ url = f"http://{config.frontend.host}:{config.frontend.port}/api/stand_cloud_sync"
300
+ return _request_hardpy(url, timeout=_timeout)
301
+
302
+
266
303
  def _get_config(tests_dir: str | None = None, validate: bool = False) -> HardpyConfig:
267
304
  dir_path = Path.cwd() / tests_dir if tests_dir else Path.cwd()
268
305
  config_manager = ConfigManager()
@@ -273,12 +310,12 @@ def _get_config(tests_dir: str | None = None, validate: bool = False) -> HardpyC
273
310
  sys.exit()
274
311
 
275
312
  if validate:
276
- _validate_config(config, dir_path)
313
+ _validate_running_config(config, dir_path)
277
314
 
278
315
  return config
279
316
 
280
317
 
281
- def _validate_config(config: HardpyConfig, tests_dir: str) -> None:
318
+ def _validate_running_config(config: HardpyConfig, tests_dir: str) -> None:
282
319
  url = f"http://{config.frontend.host}:{config.frontend.port}/api/hardpy_config"
283
320
  error_msg = f"HardPy in directory {tests_dir} does not run."
284
321
  try:
@@ -292,19 +329,25 @@ def _validate_config(config: HardpyConfig, tests_dir: str) -> None:
292
329
  print(error_msg)
293
330
  sys.exit()
294
331
 
332
+ def _validate_config(config: HardpyConfig) -> None:
333
+ if config.stand_cloud.autosync_timeout < 1:
334
+ print("StandCloud autosync timeout must be greater than 0.")
335
+ sys.exit()
336
+
295
337
 
296
- def _request_hardpy(url: str) -> None:
338
+ def _request_hardpy(url: str, timeout: int = 5) -> str:
297
339
  try:
298
- response = requests.get(url, timeout=2)
340
+ response = requests.get(url, timeout=timeout)
299
341
  except Exception:
300
342
  print("HardPy operator panel is not running.")
301
343
  sys.exit()
302
344
  try:
303
345
  status: dict = response.json().get("status", "ERROR")
304
346
  except ValueError:
305
- print(f"Hardpy internal error: {response}.")
347
+ print(f"HardPy internal error: {response}.")
306
348
  sys.exit()
307
349
  print(f"HardPy status: {status}.")
350
+ return status
308
351
 
309
352
 
310
353
  if __name__ == "__main__":
hardpy/common/config.py CHANGED
@@ -51,6 +51,7 @@ class FrontendConfig(BaseModel):
51
51
  language: str = "en"
52
52
  full_size_button: bool = False
53
53
  sound_on: bool = False
54
+ measurement_display: bool = True
54
55
  modal_result: ModalResultConfig = Field(default_factory=lambda: ModalResultConfig())
55
56
 
56
57
 
@@ -69,8 +70,11 @@ class StandCloudConfig(BaseModel):
69
70
 
70
71
  model_config = ConfigDict(extra="forbid")
71
72
 
72
- address: str = ""
73
+ address: str = "standcloud.io"
73
74
  connection_only: bool = False
75
+ autosync: bool = False
76
+ autosync_timeout: int = 30 # 30 minutes
77
+ api_key: str = ""
74
78
 
75
79
 
76
80
  class HardpyConfig(BaseModel, extra="allow"):
@@ -128,8 +132,10 @@ class ConfigManager(metaclass=SingletonMeta):
128
132
  frontend_host: str,
129
133
  frontend_port: int,
130
134
  frontend_language: str,
131
- sc_address: str = "",
132
- sc_connection_only: bool = False,
135
+ sc_address: str,
136
+ sc_connection_only: bool,
137
+ sc_autosync: bool,
138
+ sc_api_key: str,
133
139
  ) -> None:
134
140
  """Initialize the HardPy configuration.
135
141
 
@@ -146,6 +152,8 @@ class ConfigManager(metaclass=SingletonMeta):
146
152
  frontend_language (str): Operator panel language.
147
153
  sc_address (str): StandCloud address.
148
154
  sc_connection_only (bool): StandCloud check availability.
155
+ sc_autosync (bool): StandCloud auto syncronization.
156
+ sc_api_key (str): StandCloud API key.
149
157
  """
150
158
  self._config.tests_name = tests_name
151
159
  self._config.frontend.host = frontend_host
@@ -159,6 +167,8 @@ class ConfigManager(metaclass=SingletonMeta):
159
167
  self._config.database.url = self._config.database.get_url()
160
168
  self._config.stand_cloud.address = sc_address
161
169
  self._config.stand_cloud.connection_only = sc_connection_only
170
+ self._config.stand_cloud.autosync = sc_autosync
171
+ self._config.stand_cloud.api_key = sc_api_key
162
172
 
163
173
  def create_config(self, parent_dir: Path) -> None:
164
174
  """Create HardPy configuration.
@@ -166,14 +176,7 @@ class ConfigManager(metaclass=SingletonMeta):
166
176
  Args:
167
177
  parent_dir (Path): Configuration file parent directory.
168
178
  """
169
- config = self._config
170
- if not self._config.stand_cloud.address:
171
- del config.stand_cloud
172
- if not self._config.tests_name:
173
- del config.tests_name
174
- if not self._config.database.doc_id:
175
- del config.database.doc_id
176
- config_str = tomli_w.dumps(config.model_dump())
179
+ config_str = tomli_w.dumps(self._config.model_dump())
177
180
  with Path.open(parent_dir / "hardpy.toml", "w") as file:
178
181
  file.write(config_str)
179
182
 
@@ -29,9 +29,10 @@ class StandCloudConnector:
29
29
 
30
30
  def __init__(
31
31
  self,
32
- addr: str,
32
+ addr: str = "standcloud.io",
33
33
  api_mode: StandCloudAPIMode = StandCloudAPIMode.HARDPY,
34
34
  api_version: int = 1,
35
+ api_key: str | None = None,
35
36
  ) -> None:
36
37
  """Create StandCloud API connector.
37
38
 
@@ -42,6 +43,8 @@ class StandCloudConnector:
42
43
  Default: StandCloudAPIMode.HARDPY.
43
44
  api_version (int): StandCloud API version.
44
45
  Default: 1.
46
+ api_key (str | None): StandCloud API key.
47
+ Default: None.
45
48
  """
46
49
  https_prefix = "https://"
47
50
  auth_addr = addr + "/auth"
@@ -57,6 +60,10 @@ class StandCloudConnector:
57
60
  self._client_id = "hardpy-report-uploader"
58
61
  self._verify_ssl = not __debug__
59
62
  self._token_manager = TokenManager(self._addr.domain)
63
+ self._token_manager.save_token(api_key)
64
+ # TODO(xorialexandrov): Change the self._token logic for storing the
65
+ # API key in the file system to secure storage.
66
+ # Remove device flow process.
60
67
  self._token: BearerToken = self.get_access_token()
61
68
  self._log = getLogger(__name__)
62
69
 
@@ -84,6 +91,8 @@ class StandCloudConnector:
84
91
  Returns:
85
92
  bool: True if token is valid, False otherwise.
86
93
  """
94
+ if self._token_manager.api_key:
95
+ return False
87
96
  try:
88
97
  OAuth2(
89
98
  sc_addr=self._addr,
@@ -208,12 +217,22 @@ class StandCloudConnector:
208
217
  return new_token
209
218
 
210
219
  def _get_api(self, endpoint: str) -> ApiClient:
220
+ if self._token_manager.api_key:
221
+ session = requests.Session()
222
+ session.headers["Authorization"] = f"Bearer {self._token_manager.api_key}"
223
+ return ApiClient(
224
+ f"{self._addr.api}/{endpoint}",
225
+ session=session,
226
+ timeout=10,
227
+ )
228
+
211
229
  if self._token is None:
212
230
  msg = (
213
231
  f"Access token to {self._addr.domain} is not set."
214
232
  f"Login to {self._addr.domain} first"
215
233
  )
216
234
  raise StandCloudError(msg)
235
+
217
236
  try:
218
237
  auth = OAuth2(
219
238
  sc_addr=self._addr,
@@ -27,6 +27,16 @@ class TokenManager:
27
27
 
28
28
  def __init__(self, service_name: str) -> None:
29
29
  self._service_name = f"HardPy_{service_name}"
30
+ self._api_key = None
31
+
32
+ @property
33
+ def api_key(self) -> str | None:
34
+ """Get personal access token.
35
+
36
+ Returns:
37
+ str | None: personal access token
38
+ """
39
+ return self._api_key
30
40
 
31
41
  def remove_token(self) -> bool:
32
42
  """Remove token from keyring storage.
@@ -47,6 +57,14 @@ class TokenManager:
47
57
  storage_keyring.delete_password(self._service_name, "refresh_token")
48
58
  return True
49
59
 
60
+ def save_token(self, token: str) -> None:
61
+ """Save personal access token.
62
+
63
+ Args:
64
+ token (str): personal access token
65
+ """
66
+ self._api_key = token
67
+
50
68
  def save_token_info(self, token: BearerToken | dict) -> None:
51
69
  """Save token to keyring storage.
52
70
 
@@ -74,8 +92,15 @@ class TokenManager:
74
92
  Returns:
75
93
  BearerToken: access token
76
94
  """
77
- _, mem_keyring = self._get_store()
95
+ storage_keyring, mem_keyring = self._get_store()
96
+ pat = storage_keyring.get_password(self._service_name, "pat")
97
+ if pat:
98
+ return BearerToken(access_token=pat)
99
+
78
100
  token_info = mem_keyring.get_password(self._service_name, "access_token")
101
+ if not token_info:
102
+ msg = "Access token not found"
103
+ raise FileNotFoundError(msg)
79
104
  secret = self._add_expires_in(json.loads(token_info)) # type: ignore
80
105
  return BearerToken(**secret)
81
106
 
@@ -2,12 +2,16 @@
2
2
  # GNU General Public License v3.0 (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
6
+ import contextlib
5
7
  import json
8
+ import logging
6
9
  import os
7
10
  import re
11
+ from concurrent.futures import ThreadPoolExecutor
8
12
  from enum import Enum
9
13
  from pathlib import Path
10
- from typing import Annotated
14
+ from typing import TYPE_CHECKING, Annotated, Any, Final
11
15
  from urllib.parse import unquote
12
16
 
13
17
  from fastapi import FastAPI, Query
@@ -15,9 +19,46 @@ from fastapi.staticfiles import StaticFiles
15
19
 
16
20
  from hardpy.common.config import ConfigManager
17
21
  from hardpy.pytest_hardpy.pytest_wrapper import PyTestWrapper
22
+ from hardpy.pytest_hardpy.result.report_synchronizer import StandCloudSynchronizer
18
23
 
19
- app = FastAPI()
24
+ if TYPE_CHECKING:
25
+ from collections.abc import AsyncGenerator
26
+
27
+ # TODO (xorialexandrov): Move logging to own module
28
+ logging.basicConfig(
29
+ level=logging.INFO,
30
+ format="%(asctime)s %(levelname)s:\t %(message)s",
31
+ )
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ @contextlib.asynccontextmanager
36
+ async def lifespan_sync_scheduler(app: FastAPI) -> AsyncGenerator[Any, Any]:
37
+ """Manages the lifecycle events (startup and shutdown) for background tasks."""
38
+ config_manager = ConfigManager()
39
+ sc_autosync = config_manager.config.stand_cloud.autosync
40
+ if sc_autosync:
41
+ autosync_timeout = config_manager.config.stand_cloud.autosync_timeout
42
+ app.state.sync_task = asyncio.create_task(sync_stand_cloud(autosync_timeout))
43
+
44
+ yield
45
+
46
+ if sc_autosync:
47
+ if hasattr(app.state, "sync_task"):
48
+ app.state.sync_task.cancel()
49
+ # Wait for the task to be cancelled/finish its current cycle
50
+ await asyncio.gather(app.state.sync_task, return_exceptions=True)
51
+ logger.info("Cancelled StandCloud synchronization task.")
52
+
53
+ if hasattr(app.state, "executor"):
54
+ app.state.executor.shutdown(wait=False)
55
+ logger.info("Shut down ThreadPoolExecutor.")
56
+
57
+
58
+ app = FastAPI(lifespan=lifespan_sync_scheduler)
20
59
  app.state.pytest_wrp = PyTestWrapper()
60
+ app.state.sc_synchronizer = StandCloudSynchronizer()
61
+ app.state.executor = ThreadPoolExecutor(max_workers=1)
21
62
 
22
63
 
23
64
  class Status(str, Enum):
@@ -34,6 +75,30 @@ class Status(str, Enum):
34
75
  ERROR = "error"
35
76
 
36
77
 
78
+ async def sync_stand_cloud(sc_sync_interval_minutes: int) -> None:
79
+ """Periodically calls the blocking stand_cloud_sync logic in a separate thread."""
80
+ sc_sync_interval: Final[int] = sc_sync_interval_minutes * 60
81
+ initial_pause: Final[int] = 30
82
+
83
+ loop = asyncio.get_event_loop()
84
+ await asyncio.sleep(initial_pause)
85
+
86
+ while True:
87
+ try:
88
+ sync_result = await loop.run_in_executor(
89
+ app.state.executor,
90
+ app.state.sc_synchronizer.sync,
91
+ )
92
+ logger.info(f"StandCloud synchronization status: {sync_result}")
93
+ except asyncio.CancelledError:
94
+ logger.info("StandCloud synchronization task cancelled.")
95
+ break
96
+ except Exception: # noqa: BLE001
97
+ # TODO (xorialexandrov): add more information to logging
98
+ logger.info("Error during StandCloud synchronization")
99
+ await asyncio.sleep(sc_sync_interval)
100
+
101
+
37
102
  @app.get("/api/hardpy_config")
38
103
  def hardpy_config() -> dict:
39
104
  """Get config of HardPy.
@@ -60,7 +125,9 @@ def start_pytest(args: Annotated[list[str] | None, Query()] = None) -> dict:
60
125
  args_dict = dict(arg.split("=", 1) for arg in args if "=" in arg)
61
126
 
62
127
  if app.state.pytest_wrp.start(start_args=args_dict):
128
+ logger.info("Start testing process.")
63
129
  return {"status": Status.STARTED}
130
+ logger.info("Testing process is already running.")
64
131
  return {"status": Status.BUSY}
65
132
 
66
133
 
@@ -72,7 +139,9 @@ def stop_pytest() -> dict:
72
139
  dict[str, RunStatus]: run status
73
140
  """
74
141
  if app.state.pytest_wrp.stop():
142
+ logger.info("Stop testing process.")
75
143
  return {"status": Status.STOPPED}
144
+ logger.info("Testing process is not running.")
76
145
  return {"status": Status.READY}
77
146
 
78
147
 
@@ -126,6 +195,28 @@ def database_document_id() -> dict:
126
195
  return {"document_id": config_manager.config.database.doc_id}
127
196
 
128
197
 
198
+ @app.get("/api/stand_cloud_sync")
199
+ async def stand_cloud_sync() -> dict:
200
+ """Stop pytest subprocess.
201
+
202
+ Returns:
203
+ dict[status, str]: synchronization status
204
+ """
205
+ loop = asyncio.get_event_loop()
206
+ try:
207
+ sync_result = await loop.run_in_executor(
208
+ app.state.executor,
209
+ app.state.sc_synchronizer.sync,
210
+ )
211
+ except Exception: # noqa: BLE001
212
+ msg = "Error during StandCloud synchronization"
213
+ # TODO (xorialexandrov): add more information to logging
214
+ logger.info("Error during StandCloud synchronization")
215
+ return {"status": msg}
216
+ logger.info(f"StandCloud sucnronization status: {sync_result}")
217
+ return {"status": sync_result}
218
+
219
+
129
220
  @app.post("/api/confirm_dialog_box")
130
221
  def confirm_dialog_box(dbx_data: dict) -> dict:
131
222
  """Confirm dialog box with unified JSON structure.
@@ -1 +1 @@
1
- import{I as n}from"./index-DLOviMB1.js";import{I as e}from"./index-B-fsa5Ru.js";import{p as r,I as s}from"./index-B2oxSaK6.js";function I(o,t){var a=r(o);return t===s.STANDARD?n[a]:e[a]}function p(o){return r(o)}export{n as IconSvgPaths16,e as IconSvgPaths20,I as getIconPaths,p as iconNameToPathsRecordKey};
1
+ import{I as n}from"./index-DLOviMB1.js";import{I as e}from"./index-B-fsa5Ru.js";import{p as r,I as s}from"./index-BK2y65ib.js";function I(o,t){var a=r(o);return t===s.STANDARD?n[a]:e[a]}function p(o){return r(o)}export{n as IconSvgPaths16,e as IconSvgPaths20,I as getIconPaths,p as iconNameToPathsRecordKey};
@@ -1,2 +1,2 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/allPaths-LQifhvrX.js","assets/index-DLOviMB1.js","assets/index-B-fsa5Ru.js","assets/index-B2oxSaK6.js","assets/index-B7T9xvaW.css"])))=>i.map(i=>d[i]);
2
- import{_ as o,a as n,b as i}from"./index-B2oxSaK6.js";var _=function(e,a){return o(void 0,void 0,void 0,function(){var t;return n(this,function(r){switch(r.label){case 0:return[4,i(()=>import("./allPaths-LQifhvrX.js"),__vite__mapDeps([0,1,2,3,4]))];case 1:return t=r.sent().getIconPaths,[2,t(e,a)]}})})};export{_ as allPathsLoader};
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/allPaths-31ulJ0tA.js","assets/index-DLOviMB1.js","assets/index-B-fsa5Ru.js","assets/index-BK2y65ib.js","assets/index-B7T9xvaW.css"])))=>i.map(i=>d[i]);
2
+ import{_ as o,a as n,b as i}from"./index-BK2y65ib.js";var _=function(e,a){return o(void 0,void 0,void 0,function(){var t;return n(this,function(r){switch(r.label){case 0:return[4,i(()=>import("./allPaths-31ulJ0tA.js"),__vite__mapDeps([0,1,2,3,4]))];case 1:return t=r.sent().getIconPaths,[2,t(e,a)]}})})};export{_ as allPathsLoader};
@@ -1,2 +1,2 @@
1
- import{g as G}from"./index-B2oxSaK6.js";function $(w,c){for(var m=0;m<c.length;m++){const d=c[m];if(typeof d!="string"&&!Array.isArray(d)){for(const y in d)if(y!=="default"&&!(y in w)){const p=Object.getOwnPropertyDescriptor(d,y);p&&Object.defineProperty(w,y,p.get?p:{enumerable:!0,get:()=>d[y]})}}}return Object.freeze(Object.defineProperty(w,Symbol.toStringTag,{value:"Module"}))}var E={exports:{}},U;function X(){return U||(U=1,(function(w,c){var m={},d=typeof globalThis<"u"&&globalThis||typeof self<"u"&&self||typeof m<"u"&&m,y=(function(){function v(){this.fetch=!1,this.DOMException=d.DOMException}return v.prototype=d,new v})();(function(v){(function(u){var a=typeof v<"u"&&v||typeof self<"u"&&self||typeof a<"u"&&a,f={searchParams:"URLSearchParams"in a,iterable:"Symbol"in a&&"iterator"in Symbol,blob:"FileReader"in a&&"Blob"in a&&(function(){try{return new Blob,!0}catch{return!1}})(),formData:"FormData"in a,arrayBuffer:"ArrayBuffer"in a};function S(e){return e&&DataView.prototype.isPrototypeOf(e)}if(f.arrayBuffer)var F=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],I=ArrayBuffer.isView||function(e){return e&&F.indexOf(Object.prototype.toString.call(e))>-1};function _(e){if(typeof e!="string"&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(e)||e==="")throw new TypeError('Invalid character in header field name: "'+e+'"');return e.toLowerCase()}function T(e){return typeof e!="string"&&(e=String(e)),e}function B(e){var t={next:function(){var r=e.shift();return{done:r===void 0,value:r}}};return f.iterable&&(t[Symbol.iterator]=function(){return t}),t}function s(e){this.map={},e instanceof s?e.forEach(function(t,r){this.append(r,t)},this):Array.isArray(e)?e.forEach(function(t){this.append(t[0],t[1])},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){this.append(t,e[t])},this)}s.prototype.append=function(e,t){e=_(e),t=T(t);var r=this.map[e];this.map[e]=r?r+", "+t:t},s.prototype.delete=function(e){delete this.map[_(e)]},s.prototype.get=function(e){return e=_(e),this.has(e)?this.map[e]:null},s.prototype.has=function(e){return this.map.hasOwnProperty(_(e))},s.prototype.set=function(e,t){this.map[_(e)]=T(t)},s.prototype.forEach=function(e,t){for(var r in this.map)this.map.hasOwnProperty(r)&&e.call(t,this.map[r],r,this)},s.prototype.keys=function(){var e=[];return this.forEach(function(t,r){e.push(r)}),B(e)},s.prototype.values=function(){var e=[];return this.forEach(function(t){e.push(t)}),B(e)},s.prototype.entries=function(){var e=[];return this.forEach(function(t,r){e.push([r,t])}),B(e)},f.iterable&&(s.prototype[Symbol.iterator]=s.prototype.entries);function O(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function D(e){return new Promise(function(t,r){e.onload=function(){t(e.result)},e.onerror=function(){r(e.error)}})}function M(e){var t=new FileReader,r=D(t);return t.readAsArrayBuffer(e),r}function q(e){var t=new FileReader,r=D(t);return t.readAsText(e),r}function H(e){for(var t=new Uint8Array(e),r=new Array(t.length),n=0;n<t.length;n++)r[n]=String.fromCharCode(t[n]);return r.join("")}function x(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLength);return t.set(new Uint8Array(e)),t.buffer}function R(){return this.bodyUsed=!1,this._initBody=function(e){this.bodyUsed=this.bodyUsed,this._bodyInit=e,e?typeof e=="string"?this._bodyText=e:f.blob&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:f.formData&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:f.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():f.arrayBuffer&&f.blob&&S(e)?(this._bodyArrayBuffer=x(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):f.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(e)||I(e))?this._bodyArrayBuffer=x(e):this._bodyText=e=Object.prototype.toString.call(e):this._bodyText="",this.headers.get("content-type")||(typeof e=="string"?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):f.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},f.blob&&(this.blob=function(){var e=O(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){if(this._bodyArrayBuffer){var e=O(this);return e||(ArrayBuffer.isView(this._bodyArrayBuffer)?Promise.resolve(this._bodyArrayBuffer.buffer.slice(this._bodyArrayBuffer.byteOffset,this._bodyArrayBuffer.byteOffset+this._bodyArrayBuffer.byteLength)):Promise.resolve(this._bodyArrayBuffer))}else return this.blob().then(M)}),this.text=function(){var e=O(this);if(e)return e;if(this._bodyBlob)return q(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(H(this._bodyArrayBuffer));if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)},f.formData&&(this.formData=function(){return this.text().then(k)}),this.json=function(){return this.text().then(JSON.parse)},this}var L=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];function C(e){var t=e.toUpperCase();return L.indexOf(t)>-1?t:e}function b(e,t){if(!(this instanceof b))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');t=t||{};var r=t.body;if(e instanceof b){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new s(e.headers)),this.method=e.method,this.mode=e.mode,this.signal=e.signal,!r&&e._bodyInit!=null&&(r=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=t.credentials||this.credentials||"same-origin",(t.headers||!this.headers)&&(this.headers=new s(t.headers)),this.method=C(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.signal=t.signal||this.signal,this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&r)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(r),(this.method==="GET"||this.method==="HEAD")&&(t.cache==="no-store"||t.cache==="no-cache")){var n=/([?&])_=[^&]*/;if(n.test(this.url))this.url=this.url.replace(n,"$1_="+new Date().getTime());else{var i=/\?/;this.url+=(i.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}b.prototype.clone=function(){return new b(this,{body:this._bodyInit})};function k(e){var t=new FormData;return e.trim().split("&").forEach(function(r){if(r){var n=r.split("="),i=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(i),decodeURIComponent(o))}}),t}function N(e){var t=new s,r=e.replace(/\r?\n[\t ]+/g," ");return r.split("\r").map(function(n){return n.indexOf(`
1
+ import{g as G}from"./index-BK2y65ib.js";function $(w,c){for(var m=0;m<c.length;m++){const d=c[m];if(typeof d!="string"&&!Array.isArray(d)){for(const y in d)if(y!=="default"&&!(y in w)){const p=Object.getOwnPropertyDescriptor(d,y);p&&Object.defineProperty(w,y,p.get?p:{enumerable:!0,get:()=>d[y]})}}}return Object.freeze(Object.defineProperty(w,Symbol.toStringTag,{value:"Module"}))}var E={exports:{}},U;function X(){return U||(U=1,(function(w,c){var m={},d=typeof globalThis<"u"&&globalThis||typeof self<"u"&&self||typeof m<"u"&&m,y=(function(){function v(){this.fetch=!1,this.DOMException=d.DOMException}return v.prototype=d,new v})();(function(v){(function(u){var a=typeof v<"u"&&v||typeof self<"u"&&self||typeof a<"u"&&a,f={searchParams:"URLSearchParams"in a,iterable:"Symbol"in a&&"iterator"in Symbol,blob:"FileReader"in a&&"Blob"in a&&(function(){try{return new Blob,!0}catch{return!1}})(),formData:"FormData"in a,arrayBuffer:"ArrayBuffer"in a};function S(e){return e&&DataView.prototype.isPrototypeOf(e)}if(f.arrayBuffer)var F=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],I=ArrayBuffer.isView||function(e){return e&&F.indexOf(Object.prototype.toString.call(e))>-1};function _(e){if(typeof e!="string"&&(e=String(e)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(e)||e==="")throw new TypeError('Invalid character in header field name: "'+e+'"');return e.toLowerCase()}function T(e){return typeof e!="string"&&(e=String(e)),e}function B(e){var t={next:function(){var r=e.shift();return{done:r===void 0,value:r}}};return f.iterable&&(t[Symbol.iterator]=function(){return t}),t}function s(e){this.map={},e instanceof s?e.forEach(function(t,r){this.append(r,t)},this):Array.isArray(e)?e.forEach(function(t){this.append(t[0],t[1])},this):e&&Object.getOwnPropertyNames(e).forEach(function(t){this.append(t,e[t])},this)}s.prototype.append=function(e,t){e=_(e),t=T(t);var r=this.map[e];this.map[e]=r?r+", "+t:t},s.prototype.delete=function(e){delete this.map[_(e)]},s.prototype.get=function(e){return e=_(e),this.has(e)?this.map[e]:null},s.prototype.has=function(e){return this.map.hasOwnProperty(_(e))},s.prototype.set=function(e,t){this.map[_(e)]=T(t)},s.prototype.forEach=function(e,t){for(var r in this.map)this.map.hasOwnProperty(r)&&e.call(t,this.map[r],r,this)},s.prototype.keys=function(){var e=[];return this.forEach(function(t,r){e.push(r)}),B(e)},s.prototype.values=function(){var e=[];return this.forEach(function(t){e.push(t)}),B(e)},s.prototype.entries=function(){var e=[];return this.forEach(function(t,r){e.push([r,t])}),B(e)},f.iterable&&(s.prototype[Symbol.iterator]=s.prototype.entries);function O(e){if(e.bodyUsed)return Promise.reject(new TypeError("Already read"));e.bodyUsed=!0}function D(e){return new Promise(function(t,r){e.onload=function(){t(e.result)},e.onerror=function(){r(e.error)}})}function M(e){var t=new FileReader,r=D(t);return t.readAsArrayBuffer(e),r}function q(e){var t=new FileReader,r=D(t);return t.readAsText(e),r}function H(e){for(var t=new Uint8Array(e),r=new Array(t.length),n=0;n<t.length;n++)r[n]=String.fromCharCode(t[n]);return r.join("")}function x(e){if(e.slice)return e.slice(0);var t=new Uint8Array(e.byteLength);return t.set(new Uint8Array(e)),t.buffer}function R(){return this.bodyUsed=!1,this._initBody=function(e){this.bodyUsed=this.bodyUsed,this._bodyInit=e,e?typeof e=="string"?this._bodyText=e:f.blob&&Blob.prototype.isPrototypeOf(e)?this._bodyBlob=e:f.formData&&FormData.prototype.isPrototypeOf(e)?this._bodyFormData=e:f.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)?this._bodyText=e.toString():f.arrayBuffer&&f.blob&&S(e)?(this._bodyArrayBuffer=x(e.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):f.arrayBuffer&&(ArrayBuffer.prototype.isPrototypeOf(e)||I(e))?this._bodyArrayBuffer=x(e):this._bodyText=e=Object.prototype.toString.call(e):this._bodyText="",this.headers.get("content-type")||(typeof e=="string"?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):f.searchParams&&URLSearchParams.prototype.isPrototypeOf(e)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},f.blob&&(this.blob=function(){var e=O(this);if(e)return e;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){if(this._bodyArrayBuffer){var e=O(this);return e||(ArrayBuffer.isView(this._bodyArrayBuffer)?Promise.resolve(this._bodyArrayBuffer.buffer.slice(this._bodyArrayBuffer.byteOffset,this._bodyArrayBuffer.byteOffset+this._bodyArrayBuffer.byteLength)):Promise.resolve(this._bodyArrayBuffer))}else return this.blob().then(M)}),this.text=function(){var e=O(this);if(e)return e;if(this._bodyBlob)return q(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(H(this._bodyArrayBuffer));if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)},f.formData&&(this.formData=function(){return this.text().then(k)}),this.json=function(){return this.text().then(JSON.parse)},this}var L=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];function C(e){var t=e.toUpperCase();return L.indexOf(t)>-1?t:e}function b(e,t){if(!(this instanceof b))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');t=t||{};var r=t.body;if(e instanceof b){if(e.bodyUsed)throw new TypeError("Already read");this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new s(e.headers)),this.method=e.method,this.mode=e.mode,this.signal=e.signal,!r&&e._bodyInit!=null&&(r=e._bodyInit,e.bodyUsed=!0)}else this.url=String(e);if(this.credentials=t.credentials||this.credentials||"same-origin",(t.headers||!this.headers)&&(this.headers=new s(t.headers)),this.method=C(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.signal=t.signal||this.signal,this.referrer=null,(this.method==="GET"||this.method==="HEAD")&&r)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(r),(this.method==="GET"||this.method==="HEAD")&&(t.cache==="no-store"||t.cache==="no-cache")){var n=/([?&])_=[^&]*/;if(n.test(this.url))this.url=this.url.replace(n,"$1_="+new Date().getTime());else{var i=/\?/;this.url+=(i.test(this.url)?"&":"?")+"_="+new Date().getTime()}}}b.prototype.clone=function(){return new b(this,{body:this._bodyInit})};function k(e){var t=new FormData;return e.trim().split("&").forEach(function(r){if(r){var n=r.split("="),i=n.shift().replace(/\+/g," "),o=n.join("=").replace(/\+/g," ");t.append(decodeURIComponent(i),decodeURIComponent(o))}}),t}function N(e){var t=new s,r=e.replace(/\r?\n[\t ]+/g," ");return r.split("\r").map(function(n){return n.indexOf(`
2
2
  `)===0?n.substr(1,n.length):n}).forEach(function(n){var i=n.split(":"),o=i.shift().trim();if(o){var g=i.join(":").trim();t.append(o,g)}}),t}R.call(b.prototype);function l(e,t){if(!(this instanceof l))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');t||(t={}),this.type="default",this.status=t.status===void 0?200:t.status,this.ok=this.status>=200&&this.status<300,this.statusText=t.statusText===void 0?"":""+t.statusText,this.headers=new s(t.headers),this.url=t.url||"",this._initBody(e)}R.call(l.prototype),l.prototype.clone=function(){return new l(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new s(this.headers),url:this.url})},l.error=function(){var e=new l(null,{status:0,statusText:""});return e.type="error",e};var V=[301,302,303,307,308];l.redirect=function(e,t){if(V.indexOf(t)===-1)throw new RangeError("Invalid status code");return new l(null,{status:t,headers:{location:e}})},u.DOMException=a.DOMException;try{new u.DOMException}catch{u.DOMException=function(t,r){this.message=t,this.name=r;var n=Error(t);this.stack=n.stack},u.DOMException.prototype=Object.create(Error.prototype),u.DOMException.prototype.constructor=u.DOMException}function P(e,t){return new Promise(function(r,n){var i=new b(e,t);if(i.signal&&i.signal.aborted)return n(new u.DOMException("Aborted","AbortError"));var o=new XMLHttpRequest;function g(){o.abort()}o.onload=function(){var h={status:o.status,statusText:o.statusText,headers:N(o.getAllResponseHeaders()||"")};h.url="responseURL"in o?o.responseURL:h.headers.get("X-Request-URL");var A="response"in o?o.response:o.responseText;setTimeout(function(){r(new l(A,h))},0)},o.onerror=function(){setTimeout(function(){n(new TypeError("Network request failed"))},0)},o.ontimeout=function(){setTimeout(function(){n(new TypeError("Network request failed"))},0)},o.onabort=function(){setTimeout(function(){n(new u.DOMException("Aborted","AbortError"))},0)};function z(h){try{return h===""&&a.location.href?a.location.href:h}catch{return h}}o.open(i.method,z(i.url),!0),i.credentials==="include"?o.withCredentials=!0:i.credentials==="omit"&&(o.withCredentials=!1),"responseType"in o&&(f.blob?o.responseType="blob":f.arrayBuffer&&i.headers.get("Content-Type")&&i.headers.get("Content-Type").indexOf("application/octet-stream")!==-1&&(o.responseType="arraybuffer")),t&&typeof t.headers=="object"&&!(t.headers instanceof s)?Object.getOwnPropertyNames(t.headers).forEach(function(h){o.setRequestHeader(h,T(t.headers[h]))}):i.headers.forEach(function(h,A){o.setRequestHeader(A,h)}),i.signal&&(i.signal.addEventListener("abort",g),o.onreadystatechange=function(){o.readyState===4&&i.signal.removeEventListener("abort",g)}),o.send(typeof i._bodyInit>"u"?null:i._bodyInit)})}return P.polyfill=!0,a.fetch||(a.fetch=P,a.Headers=s,a.Request=b,a.Response=l),u.Headers=s,u.Request=b,u.Response=l,u.fetch=P,u})({})})(y),y.fetch.ponyfill=!0,delete y.fetch.polyfill;var p=d.fetch?d:y;c=p.fetch,c.default=p.fetch,c.fetch=p.fetch,c.Headers=p.Headers,c.Request=p.Request,c.Response=p.Response,w.exports=c})(E,E.exports)),E.exports}var j=X();const J=G(j),Q=$({__proto__:null,default:J},[j]);export{Q as b};