lr-qrm 0.1.1__py3-none-any.whl → 0.1.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lr-qrm
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: CLI client for interacting with Qestit QRM data from LumenRadio tooling.
5
5
  Author-email: Jonas Estberger <jonas.estberger@lumenradio.com>
6
6
  License: MIT License
@@ -66,10 +66,18 @@ qrm status
66
66
  #### Inspect UUT history
67
67
 
68
68
  ```bash
69
- qrm uut status 326115020010F2F1 --start "2026-02-01 00:00:00" --stop "2026-02-06 23:59:00"
69
+ qrm uut status 326115020010F2F1 --start "2026-02-01T00:00:00Z" --stop "2026-02-06T23:59:00Z"
70
70
  ```
71
71
 
72
- By default the command queries the last 24 hours ending now. Pass `--base-url` and `--insecure` to override the stored settings when needed.
72
+ By default the command uses `1970-01-01T00:00:00Z` as the start timestamp and lets QRM treat the stop as "now" (omit `--stop`). Pass `--base-url` and `--insecure` to override the stored settings when needed.
73
+
74
+ #### List production boxes
75
+
76
+ ```bash
77
+ qrm box list --stop "2026-02-06T23:59:00Z"
78
+ ```
79
+
80
+ If you omit `--start`, the command defaults to the Unix epoch (`1970-01-01T00:00:00Z`). Leave `--stop` out to let QRM use its current time.
73
81
 
74
82
  #### JSON output
75
83
 
@@ -87,12 +95,19 @@ from qrm.client import QrmClient
87
95
 
88
96
  state = load_login_state()
89
97
  client = QrmClient(base_url=str(state.base_url), verify_tls=state.verify_tls)
98
+
90
99
  uut_runs = client.uut_status(
91
- token=state.token,
92
- serial_number="326115020010F2F1",
93
- start_datetime="2026-02-01 00:00:00",
94
- stop_datetime="2026-02-06 23:59:00",
95
- max_results=1000,
100
+ token=state.token,
101
+ serial_number="326115020010F2F1",
102
+ start_datetime="2026-02-01T00:00:00Z",
103
+ stop_datetime="2026-02-06T23:59:00Z",
104
+ max_results=1000,
105
+ )
106
+
107
+ boxes = client.box_list(
108
+ token=state.token,
109
+ start_datetime="1970-01-01T00:00:00Z",
110
+ stop_datetime="2026-02-06T23:59:00Z",
96
111
  )
97
112
  ```
98
113
 
@@ -0,0 +1,10 @@
1
+ lr_qrm-0.1.3.dist-info/licenses/LICENSE,sha256=cEnG3HeMPa3GKtuENkytOQaNJS9rwMMU_KWHObdWbOY,1070
2
+ qrm/__init__.py,sha256=vpsqqB9AKEhCcTdlSNeMF74vKSUG2NuUPEd5uE6u-ws,1069
3
+ qrm/cli.py,sha256=djqm4xNwPKk82mZPXHsuELe7gQDRzLvEePHh83L2GUs,14716
4
+ qrm/client.py,sha256=8i2IJx1D8S2g1BBReH_7rr0Lq4YKbpKtuVljm_4rqig,11431
5
+ qrm/config.py,sha256=TewatdLxqbCa7hBLbqj8hrKbposvbD-Wc1TSiUipzq4,623
6
+ lr_qrm-0.1.3.dist-info/METADATA,sha256=8jM8UVe4lYKuxsBV6Z1WHu-iNKRvoNOXk57gU2IALYk,2753
7
+ lr_qrm-0.1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
+ lr_qrm-0.1.3.dist-info/entry_points.txt,sha256=9CaqYoIqOC9O40WoJ6SM1qYk9gP5VVShmMc51zBqas4,36
9
+ lr_qrm-0.1.3.dist-info/top_level.txt,sha256=URCUvNbN-7KWypbbmrCaie8JjKDowz0p4GNhBqnN99U,4
10
+ lr_qrm-0.1.3.dist-info/RECORD,,
qrm/cli.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
- from datetime import datetime, timedelta, timezone
6
+ from datetime import datetime, timezone
7
7
  from enum import Enum
8
8
  from pathlib import Path
9
9
  from typing import Any
@@ -28,15 +28,18 @@ class OutputFormat(str, Enum):
28
28
  app = typer.Typer(help="Interact with the Qestit QRM service.")
29
29
  console = Console()
30
30
  uut_app = typer.Typer(help="Inspect Units Under Test (UUTs).")
31
+ box_app = typer.Typer(help="Inspect production boxes.")
31
32
  app.add_typer(uut_app, name="uut")
33
+ app.add_typer(box_app, name="box")
32
34
 
33
- UUT_API_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
34
- UUT_CLI_DATE_FORMATS = [
35
- UUT_API_DATE_FORMAT,
35
+ API_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
36
+ CLI_DATE_FORMATS = [
37
+ API_DATE_FORMAT,
38
+ "%Y-%m-%d %H:%M:%S",
36
39
  "%Y-%m-%dT%H:%M:%S",
37
40
  "%Y-%m-%dT%H:%M:%S%z",
38
41
  ]
39
- UUT_DEFAULT_WINDOW = timedelta(days=1)
42
+ EPOCH_START = datetime(1970, 1, 1, tzinfo=timezone.utc)
40
43
 
41
44
 
42
45
  @app.callback(invoke_without_command=True)
@@ -166,14 +169,20 @@ def uut_status(
166
169
  start: datetime | None = typer.Option(
167
170
  None,
168
171
  "--start",
169
- formats=UUT_CLI_DATE_FORMATS,
170
- help="Filter from this timestamp (UTC). Format: YYYY-MM-DD HH:MM:SS. Default: stop - 1 day.",
172
+ formats=CLI_DATE_FORMATS,
173
+ help=(
174
+ "Filter from this timestamp (UTC). Accepts ISO-8601 (e.g. 2020-01-01T00:00:00Z) "
175
+ "or YYYY-MM-DD HH:MM:SS. Default: 1970-01-01T00:00:00Z."
176
+ ),
171
177
  ),
172
178
  stop: datetime | None = typer.Option(
173
179
  None,
174
180
  "--stop",
175
- formats=UUT_CLI_DATE_FORMATS,
176
- help="Filter until this timestamp (UTC). Format: YYYY-MM-DD HH:MM:SS. Default: now.",
181
+ formats=CLI_DATE_FORMATS,
182
+ help=(
183
+ "Filter until this timestamp (UTC). Accepts ISO-8601 (e.g. 2026-02-06T23:59:00Z) "
184
+ "or YYYY-MM-DD HH:MM:SS. Default: QRM current time."
185
+ ),
177
186
  ),
178
187
  max_results: int = typer.Option(
179
188
  1000,
@@ -209,7 +218,7 @@ def uut_status(
209
218
  """Fetch UUT execution history using the `/Uut/Status` endpoint."""
210
219
 
211
220
  state = _load_state_or_exit(config_path)
212
- start, stop = _resolve_uut_window(start, stop)
221
+ resolved_start, resolved_stop = _resolve_uut_window(start, stop)
213
222
 
214
223
  client = QrmClient(
215
224
  base_url=base_url or str(state.base_url),
@@ -220,8 +229,8 @@ def uut_status(
220
229
  payload = client.uut_status(
221
230
  token=state.token,
222
231
  serial_number=serial_number,
223
- start_datetime=_format_uut_datetime(start),
224
- stop_datetime=_format_uut_datetime(stop),
232
+ start_datetime=_format_api_datetime(resolved_start),
233
+ stop_datetime=_format_api_datetime(resolved_stop) if resolved_stop is not None else None,
225
234
  max_results=max_results,
226
235
  )
227
236
  except QrmClientError as exc:
@@ -235,6 +244,76 @@ def uut_status(
235
244
  console.print(_render_uut_status(payload))
236
245
 
237
246
 
247
+ @box_app.command("list")
248
+ def box_list(
249
+ start: datetime | None = typer.Option(
250
+ None,
251
+ "--start",
252
+ formats=CLI_DATE_FORMATS,
253
+ help=(
254
+ "Filter from this timestamp (UTC). Accepts ISO-8601 (e.g. 2020-01-01T00:00:00Z) "
255
+ "or YYYY-MM-DD HH:MM:SS. Default: 1970-01-01T00:00:00Z."
256
+ ),
257
+ ),
258
+ stop: datetime | None = typer.Option(
259
+ None,
260
+ "--stop",
261
+ formats=CLI_DATE_FORMATS,
262
+ help=(
263
+ "Filter until this timestamp (UTC). Accepts ISO-8601 (e.g. 2026-02-06T23:59:00Z) "
264
+ "or YYYY-MM-DD HH:MM:SS. Default: QRM current time."
265
+ ),
266
+ ),
267
+ config_path: Path = typer.Option(
268
+ None,
269
+ "--config-path",
270
+ help="Override the location of the login.json file.",
271
+ ),
272
+ base_url: str = typer.Option(
273
+ None,
274
+ "--base-url",
275
+ help="Override the base URL stored in the config (useful for testing).",
276
+ ),
277
+ insecure: bool = typer.Option(
278
+ False,
279
+ "--insecure",
280
+ help="Disable TLS verification when contacting QRM.",
281
+ ),
282
+ output: OutputFormat = typer.Option(
283
+ OutputFormat.rich,
284
+ "--output",
285
+ "-o",
286
+ case_sensitive=False,
287
+ help="Choose output format: rich (default) or json.",
288
+ ),
289
+ ) -> None:
290
+ """List production boxes via the `/Box/List` endpoint."""
291
+
292
+ state = _load_state_or_exit(config_path)
293
+ resolved_start, resolved_stop = _resolve_box_window(start, stop)
294
+
295
+ client = QrmClient(
296
+ base_url=base_url or str(state.base_url),
297
+ verify_tls=not insecure and state.verify_tls,
298
+ )
299
+
300
+ try:
301
+ payload = client.box_list(
302
+ token=state.token,
303
+ start_datetime=_format_api_datetime(resolved_start),
304
+ stop_datetime=_format_api_datetime(resolved_stop) if resolved_stop is not None else None,
305
+ )
306
+ except QrmClientError as exc:
307
+ typer.secho(f"Box list failed: {exc}", fg=typer.colors.RED)
308
+ raise typer.Exit(code=1) from exc
309
+
310
+ if output is OutputFormat.json:
311
+ _emit(payload, output)
312
+ return
313
+
314
+ console.print(_render_box_list(payload))
315
+
316
+
238
317
  def _resolve_config_path(config_path: Path | None) -> Path:
239
318
  return config_path.expanduser() if config_path else DEFAULT_CONFIG_PATH
240
319
 
@@ -278,17 +357,35 @@ def _resolve_uut_window(
278
357
  start: datetime | None,
279
358
  stop: datetime | None,
280
359
  ) -> tuple[datetime, datetime]:
281
- stop_value = stop or datetime.now(timezone.utc)
282
- start_value = start or (stop_value - UUT_DEFAULT_WINDOW)
360
+ return _resolve_window(start, stop, default_start=EPOCH_START)
361
+
362
+
363
+ def _resolve_box_window(
364
+ start: datetime | None,
365
+ stop: datetime | None,
366
+ ) -> tuple[datetime, datetime]:
367
+ return _resolve_window(start, stop, default_start=EPOCH_START)
368
+
369
+
370
+ def _resolve_window(
371
+ start: datetime | None,
372
+ stop: datetime | None,
373
+ *,
374
+ default_start: datetime,
375
+ ) -> tuple[datetime, datetime | None]:
376
+ stop_value = stop
377
+ start_value = start or default_start
378
+ if stop_value and start_value > stop_value:
379
+ raise typer.BadParameter("--start must be earlier than --stop")
283
380
  return start_value, stop_value
284
381
 
285
382
 
286
- def _format_uut_datetime(value: datetime | None) -> str | None:
383
+ def _format_api_datetime(value: datetime | None) -> str | None:
287
384
  if value is None:
288
385
  return None
289
386
  if value.tzinfo is None:
290
- return value.strftime(UUT_API_DATE_FORMAT)
291
- return value.astimezone(timezone.utc).strftime(UUT_API_DATE_FORMAT)
387
+ value = value.replace(tzinfo=timezone.utc)
388
+ return value.astimezone(timezone.utc).strftime(API_DATE_FORMAT)
292
389
 
293
390
 
294
391
  def _render_uut_status(payload: Any) -> Table:
@@ -326,6 +423,38 @@ def _render_uut_status(payload: Any) -> Table:
326
423
  return table
327
424
 
328
425
 
426
+ def _render_box_list(payload: Any) -> Table:
427
+ table = Table(show_header=True, header_style="bold")
428
+ table.add_column("Box", style="cyan")
429
+ table.add_column("Name", style="cyan")
430
+ table.add_column("Status", style="green")
431
+ table.add_column("Start", style="magenta")
432
+ table.add_column("Stop", style="magenta")
433
+ table.add_column("Location", style="yellow")
434
+
435
+ rows = payload if isinstance(payload, list) else ([payload] if payload else [])
436
+
437
+ if not rows:
438
+ table.add_row("No results", "-", "-", "-", "-", "-")
439
+ return table
440
+
441
+ for entry in rows:
442
+ if not isinstance(entry, dict):
443
+ table.add_row(str(entry), "-", "-", "-", "-", "-")
444
+ continue
445
+
446
+ table.add_row(
447
+ str(entry.get("BoxId") or entry.get("Id", "-")),
448
+ str(entry.get("BoxName") or entry.get("Name", "-")),
449
+ str(entry.get("Status") or entry.get("Outcome", "-")),
450
+ _format_timestamp(entry.get("StartDateTime")),
451
+ _format_timestamp(entry.get("StopDateTime")),
452
+ str(entry.get("Location") or entry.get("StationName") or "-"),
453
+ )
454
+
455
+ return table
456
+
457
+
329
458
  def _resolve_serial(entry: dict[str, Any]) -> str:
330
459
  value = entry.get("serialNumber") or entry.get("SerialNumber")
331
460
  return str(value) if value else "-"
qrm/client.py CHANGED
@@ -3,6 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import os
7
+ import pprint
8
+ import sys
6
9
  from dataclasses import dataclass
7
10
  from datetime import datetime, timezone
8
11
  from typing import Any, Dict, Optional
@@ -42,8 +45,10 @@ class QrmClient:
42
45
  verify_tls: bool = True
43
46
  timeout: float = 10.0
44
47
  session: Optional[requests.Session] = None
48
+ debug: bool = False
45
49
 
46
50
  def __post_init__(self) -> None:
51
+ self.debug = self.debug or os.getenv("QRM_DEBUG") == "1"
47
52
  if not self.base_url:
48
53
  raise ValueError("base_url must be provided")
49
54
  self.base_url = self.base_url.rstrip("/")
@@ -56,16 +61,32 @@ class QrmClient:
56
61
  """Authenticate against `/User/Login` and return the bearer token."""
57
62
 
58
63
  credentials = LoginCredentials(username=username, password=password)
64
+ url = self._build_url("/User/Login")
65
+ body = credentials.model_dump()
66
+ self._debug_request(
67
+ method="POST",
68
+ url=url,
69
+ headers=None,
70
+ payload=body,
71
+ )
59
72
  try:
60
73
  response = self.session.post(
61
- self._build_url("/User/Login"),
62
- json=credentials.model_dump(),
74
+ url,
75
+ json=body,
63
76
  timeout=self.timeout,
64
77
  )
65
- response.raise_for_status()
78
+ self._debug_response(method="POST", url=url, response=response)
66
79
  except requests.RequestException as exc: # pragma: no cover - network failure
80
+ self._debug_error(method="POST", url=url, error=exc)
67
81
  raise QrmClientError("Failed to contact QRM login endpoint") from exc
68
82
 
83
+ self._ensure_success(
84
+ response=response,
85
+ method="POST",
86
+ url=url,
87
+ error_message="Failed to contact QRM login endpoint",
88
+ )
89
+
69
90
  token = self._extract_token(response)
70
91
  if not token:
71
92
  raise QrmClientError("QRM login response did not contain a token")
@@ -75,16 +96,31 @@ class QrmClient:
75
96
  """Query the `/Result/Status` endpoint to verify service health."""
76
97
 
77
98
  headers = {"Authorization": f"Bearer {token}"} if token else None
99
+ url = self._build_url("/Result/Status")
100
+ self._debug_request(
101
+ method="GET",
102
+ url=url,
103
+ headers=headers,
104
+ payload=None,
105
+ )
78
106
  try:
79
107
  response = self.session.get(
80
- self._build_url("/Result/Status"),
108
+ url,
81
109
  headers=headers,
82
110
  timeout=self.timeout,
83
111
  )
84
- response.raise_for_status()
112
+ self._debug_response(method="GET", url=url, response=response)
85
113
  except requests.RequestException as exc: # pragma: no cover - network failure
114
+ self._debug_error(method="GET", url=url, error=exc)
86
115
  raise QrmClientError("Failed to contact QRM status endpoint") from exc
87
116
 
117
+ self._ensure_success(
118
+ response=response,
119
+ method="GET",
120
+ url=url,
121
+ error_message="Failed to contact QRM status endpoint",
122
+ )
123
+
88
124
  try:
89
125
  return response.json()
90
126
  except ValueError:
@@ -114,22 +150,114 @@ class QrmClient:
114
150
  if max_results is not None:
115
151
  payload["maxNumberOfResults"] = max_results
116
152
 
153
+ url = self._build_url("/Uut/Status")
154
+ self._debug_request(
155
+ method="POST",
156
+ url=url,
157
+ headers=headers,
158
+ payload=payload,
159
+ )
117
160
  try:
118
161
  response = self.session.post(
119
- self._build_url("/Uut/Status"),
162
+ url,
120
163
  headers=headers,
121
164
  json=payload,
122
165
  timeout=self.timeout,
123
166
  )
124
- response.raise_for_status()
167
+ self._debug_response(method="POST", url=url, response=response)
125
168
  except requests.RequestException as exc: # pragma: no cover - network failure
169
+ self._debug_error(method="POST", url=url, error=exc)
126
170
  raise QrmClientError("Failed to contact QRM UUT status endpoint") from exc
127
171
 
172
+ self._ensure_success(
173
+ response=response,
174
+ method="POST",
175
+ url=url,
176
+ error_message="Failed to contact QRM UUT status endpoint",
177
+ )
178
+
179
+ try:
180
+ return response.json()
181
+ except ValueError:
182
+ return response.text.strip()
183
+
184
+ def box_list(
185
+ self,
186
+ *,
187
+ token: str,
188
+ start_datetime: Optional[str] = None,
189
+ stop_datetime: Optional[str] = None,
190
+ ) -> Any:
191
+ """Query the `/Box/List` endpoint for production boxes."""
192
+
193
+ if not token:
194
+ raise ValueError("token must be provided")
195
+
196
+ headers = {"Authorization": f"Bearer {token}"}
197
+ payload: Dict[str, Any] = {}
198
+ if start_datetime:
199
+ payload["startDateTime"] = start_datetime
200
+ if stop_datetime:
201
+ payload["stopDateTime"] = stop_datetime
202
+
203
+ body = payload or None
204
+ url = self._build_url("/Box/List")
205
+ self._debug_request(
206
+ method="POST",
207
+ url=url,
208
+ headers=headers,
209
+ payload=body,
210
+ )
211
+ try:
212
+ response = self.session.post(
213
+ url,
214
+ headers=headers,
215
+ json=body,
216
+ timeout=self.timeout,
217
+ )
218
+ self._debug_response(method="POST", url=url, response=response)
219
+ except requests.RequestException as exc: # pragma: no cover - network failure
220
+ self._debug_error(method="POST", url=url, error=exc)
221
+ raise QrmClientError("Failed to contact QRM box list endpoint") from exc
222
+
223
+ self._ensure_success(
224
+ response=response,
225
+ method="POST",
226
+ url=url,
227
+ error_message="Failed to contact QRM box list endpoint",
228
+ )
229
+
128
230
  try:
129
231
  return response.json()
130
232
  except ValueError:
131
233
  return response.text.strip()
132
234
 
235
+ def _ensure_success(
236
+ self,
237
+ *,
238
+ response: requests.Response,
239
+ method: str,
240
+ url: str,
241
+ error_message: str,
242
+ ) -> None:
243
+ if response.status_code == 401:
244
+ message = (
245
+ "Authentication failed (HTTP 401). Your credentials may be invalid or expired. "
246
+ "Please run `qrm login` and try again."
247
+ )
248
+ self._debug_error(
249
+ method=method,
250
+ url=url,
251
+ error=RuntimeError(message),
252
+ )
253
+ raise QrmClientError(message)
254
+
255
+ try:
256
+ response.raise_for_status()
257
+ except requests.HTTPError as exc:
258
+ self._debug_error(method=method, url=url, error=exc)
259
+ raise QrmClientError(error_message) from exc
260
+
133
261
  def _build_url(self, path: str) -> str:
134
262
  return urljoin(f"{self.base_url}/", path.lstrip("/"))
135
263
 
@@ -179,5 +307,60 @@ class QrmClient:
179
307
  return trimmed
180
308
  return None
181
309
 
310
+ def _debug_request(
311
+ self,
312
+ *,
313
+ method: str,
314
+ url: str,
315
+ headers: Optional[Dict[str, Any]],
316
+ payload: Any,
317
+ ) -> None:
318
+ if not self.debug:
319
+ return
320
+ data: Dict[str, Any] = {"method": method, "url": url}
321
+ if headers is not None:
322
+ data["headers"] = headers
323
+ if payload is not None:
324
+ data["body"] = payload
325
+ self._debug_http("REQUEST", data)
326
+
327
+ def _debug_response(
328
+ self,
329
+ *,
330
+ method: str,
331
+ url: str,
332
+ response: requests.Response,
333
+ ) -> None:
334
+ if not self.debug:
335
+ return
336
+ headers = getattr(response, "headers", None)
337
+ data: Dict[str, Any] = {
338
+ "method": method,
339
+ "url": url,
340
+ "status_code": response.status_code,
341
+ "body": response.text,
342
+ }
343
+ if headers is not None:
344
+ data["headers"] = dict(headers)
345
+ self._debug_http("RESPONSE", data)
346
+
347
+ def _debug_error(self, *, method: str, url: str, error: Exception) -> None:
348
+ if not self.debug:
349
+ return
350
+ self._debug_http(
351
+ "ERROR",
352
+ {
353
+ "method": method,
354
+ "url": url,
355
+ "error": f"{error.__class__.__name__}: {error}",
356
+ },
357
+ )
358
+
359
+ def _debug_http(self, stage: str, payload: Dict[str, Any]) -> None:
360
+ if not self.debug:
361
+ return
362
+ formatted = pprint.pformat(payload)
363
+ print(f"[QRM DEBUG] {stage}: {formatted}", file=sys.stderr)
364
+
182
365
 
183
366
  __all__ = ["LoginCredentials", "LoginState", "QrmClient", "QrmClientError"]
@@ -1,10 +0,0 @@
1
- lr_qrm-0.1.1.dist-info/licenses/LICENSE,sha256=cEnG3HeMPa3GKtuENkytOQaNJS9rwMMU_KWHObdWbOY,1070
2
- qrm/__init__.py,sha256=vpsqqB9AKEhCcTdlSNeMF74vKSUG2NuUPEd5uE6u-ws,1069
3
- qrm/cli.py,sha256=M3f6D5_ROssysCWsWhbdMgsNJOaDWQZux5u5D0LE6II,10602
4
- qrm/client.py,sha256=-wA06kxdOx1y4xlPASfwFzltUZaFt4KeDqbgMBY8QCY,5979
5
- qrm/config.py,sha256=TewatdLxqbCa7hBLbqj8hrKbposvbD-Wc1TSiUipzq4,623
6
- lr_qrm-0.1.1.dist-info/METADATA,sha256=DaUZm9XUDnxxWReoj9GzpoUcxxYDY-1Zrbln0bGc1xQ,2310
7
- lr_qrm-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
- lr_qrm-0.1.1.dist-info/entry_points.txt,sha256=9CaqYoIqOC9O40WoJ6SM1qYk9gP5VVShmMc51zBqas4,36
9
- lr_qrm-0.1.1.dist-info/top_level.txt,sha256=URCUvNbN-7KWypbbmrCaie8JjKDowz0p4GNhBqnN99U,4
10
- lr_qrm-0.1.1.dist-info/RECORD,,
File without changes