python-daynest 0.1.8__tar.gz → 0.1.9__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.
@@ -8,6 +8,7 @@ node_modules/
8
8
  dist/
9
9
  *.tsbuildinfo
10
10
  .vite/
11
+ frontend/src/paraglide/
11
12
  custom_components/daynest/frontend/daynest-card.js
12
13
 
13
14
  # Local dev/runtime artifacts
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-daynest
3
- Version: 0.1.8
3
+ Version: 0.1.9
4
4
  Summary: Python client library for the Daynest API
5
5
  License: MIT
6
6
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-daynest"
3
- version = "0.1.8"
3
+ version = "0.1.9"
4
4
  description = "Python client library for the Daynest API"
5
5
  requires-python = ">=3.12"
6
6
  license = { text = "MIT" }
@@ -10,10 +10,10 @@ dependencies = [
10
10
 
11
11
  [dependency-groups]
12
12
  dev = [
13
- "pytest>=8.0",
14
- "pytest-asyncio>=0.25",
15
- "ruff>=0.8",
16
- "pyright>=1.1",
13
+ "pytest==9.0.3",
14
+ "pytest-asyncio==1.4.0",
15
+ "ruff==0.15.15",
16
+ "pyright==1.1.409",
17
17
  ]
18
18
 
19
19
  [build-system]
@@ -203,7 +203,7 @@ class DaynestClient:
203
203
  Returns (authorization_url, token_url) or None if the backend is
204
204
  unreachable or the response is malformed. Does not require authentication.
205
205
  """
206
- url = urljoin(f"{base_url.strip().rstrip('/')}/", "api/v1/auth/oidc-config")
206
+ url = urljoin(f"{base_url.strip().rstrip('/')}/", "api/auth/oidc-config")
207
207
  owned = session is None
208
208
  if owned:
209
209
  session = aiohttp.ClientSession()
@@ -233,7 +233,7 @@ class DaynestClient:
233
233
  return await self._cached_call(
234
234
  "async_get_summary",
235
235
  lambda: self._request_model(
236
- path="/api/v1/integrations/home-assistant/summary",
236
+ path="/api/integrations/home-assistant/summary",
237
237
  parser=DaynestSummary.from_dict,
238
238
  ),
239
239
  )
@@ -243,58 +243,58 @@ class DaynestClient:
243
243
  return await self._cached_call(
244
244
  "async_get_dashboard",
245
245
  lambda: self._request_model(
246
- path="/api/v1/integrations/home-assistant/dashboard",
246
+ path="/api/integrations/home-assistant/dashboard",
247
247
  parser=DaynestDashboard.from_dict,
248
248
  ),
249
249
  )
250
250
 
251
251
  async def async_get_user_settings(self) -> dict[str, Any]:
252
252
  """Fetch user settings for the authenticated integration user."""
253
- return await self._cached_call("async_get_user_settings", lambda: self._request_dict("/api/v1/users/me/settings"))
253
+ return await self._cached_call("async_get_user_settings", lambda: self._request_dict("/api/users/me/settings"))
254
254
 
255
255
  async def async_update_user_settings(self, payload: dict[str, Any]) -> dict[str, Any]:
256
256
  """Patch user settings for the authenticated integration user."""
257
- return await self._send_action("patch", path="/api/v1/users/me/settings", payload=payload)
257
+ return await self._send_action("patch", path="/api/users/me/settings", payload=payload)
258
258
 
259
259
  async def async_complete_task(self, chore_instance_id: int) -> dict[str, Any]:
260
260
  """Complete a chore instance by ID."""
261
261
  return await self._post_action(
262
- path="/api/v1/integrations/home-assistant/actions/complete-task",
262
+ path="/api/integrations/home-assistant/actions/complete-task",
263
263
  payload={"chore_instance_id": chore_instance_id},
264
264
  )
265
265
 
266
266
  async def async_snooze_task(self, chore_instance_id: int, days: int = 1) -> dict[str, Any]:
267
267
  """Reschedule a chore instance N days into the future."""
268
268
  return await self._post_action(
269
- path="/api/v1/integrations/home-assistant/actions/snooze-task",
269
+ path="/api/integrations/home-assistant/actions/snooze-task",
270
270
  payload={"chore_instance_id": chore_instance_id, "days": days},
271
271
  )
272
272
 
273
273
  async def async_mark_medication_taken(self, medication_dose_id: int) -> dict[str, Any]:
274
274
  """Mark a medication dose as taken."""
275
275
  return await self._post_action(
276
- path="/api/v1/integrations/home-assistant/actions/mark-medication-taken",
276
+ path="/api/integrations/home-assistant/actions/mark-medication-taken",
277
277
  payload={"medication_dose_id": medication_dose_id},
278
278
  )
279
279
 
280
280
  async def async_skip_task(self, chore_instance_id: int) -> dict[str, Any]:
281
281
  """Skip a chore instance."""
282
282
  return await self._post_action(
283
- path="/api/v1/integrations/home-assistant/actions/skip-task",
283
+ path="/api/integrations/home-assistant/actions/skip-task",
284
284
  payload={"chore_instance_id": chore_instance_id},
285
285
  )
286
286
 
287
287
  async def async_skip_medication(self, medication_dose_id: int) -> dict[str, Any]:
288
288
  """Skip a medication dose."""
289
289
  return await self._post_action(
290
- path="/api/v1/integrations/home-assistant/actions/skip-medication",
290
+ path="/api/integrations/home-assistant/actions/skip-medication",
291
291
  payload={"medication_dose_id": medication_dose_id},
292
292
  )
293
293
 
294
294
  async def async_mark_planned_done(self, planned_item_id: int) -> dict[str, Any]:
295
295
  """Mark a planned item as done."""
296
296
  return await self._post_action(
297
- path="/api/v1/integrations/home-assistant/actions/mark-planned-done",
297
+ path="/api/integrations/home-assistant/actions/mark-planned-done",
298
298
  payload={"planned_item_id": planned_item_id},
299
299
  )
300
300
 
@@ -307,7 +307,7 @@ class DaynestClient:
307
307
  query = urlencode({"start_date": date_from.isoformat(), "end_date": date_to.isoformat()})
308
308
  payload = await self._cached_call(
309
309
  "async_list_planned_items",
310
- lambda: self._request_list(f"/api/v1/planned-items?{query}"),
310
+ lambda: self._request_list(f"/api/planned-items?{query}"),
311
311
  date_from.isoformat(),
312
312
  date_to.isoformat(),
313
313
  )
@@ -332,15 +332,21 @@ class DaynestClient:
332
332
  "tags": tags or [],
333
333
  "rrule": rrule,
334
334
  }
335
- result = await self._send_action("post", path="/api/v1/planned-items", payload=payload)
335
+ result = await self._send_action("post", path="/api/planned-items", payload=payload)
336
336
  return PlannedItem.from_dict(result)
337
337
 
338
338
  async def async_update_planned_item(
339
339
  self,
340
340
  item_id: int | None = None,
341
+ *,
342
+ scope: str = "this",
341
343
  **fields: Any,
342
344
  ) -> PlannedItem:
343
- """Update a planned item."""
345
+ """Update a planned item.
346
+
347
+ scope: "this" (default), "future", or "all" — controls which items in a
348
+ recurring series are updated. Non-recurring items ignore this parameter.
349
+ """
344
350
  payload_fields = dict(fields)
345
351
  resolved_item_id = item_id
346
352
  if resolved_item_id is None:
@@ -352,20 +358,32 @@ class DaynestClient:
352
358
  raise ValueError(msg)
353
359
  if "planned_for" in payload_fields and isinstance(payload_fields["planned_for"], date):
354
360
  payload_fields["planned_for"] = payload_fields["planned_for"].isoformat()
355
- result = await self._send_action(
356
- "put",
357
- path=f"/api/v1/planned-items/{resolved_item_id}",
358
- payload=payload_fields,
359
- )
361
+ path = f"/api/planned-items/{resolved_item_id}"
362
+ if scope != "this":
363
+ path += f"?scope={scope}"
364
+ result = await self._send_action("put", path=path, payload=payload_fields)
360
365
  return PlannedItem.from_dict(result)
361
366
 
362
- async def async_delete_planned_item(self, item_id: int | None = None, *, planned_item_id: int | None = None) -> None:
363
- """Delete a planned item."""
367
+ async def async_delete_planned_item(
368
+ self,
369
+ item_id: int | None = None,
370
+ *,
371
+ planned_item_id: int | None = None,
372
+ scope: str = "this",
373
+ ) -> None:
374
+ """Delete a planned item.
375
+
376
+ scope: "this" (default) or "future" — controls which items in a recurring
377
+ series are deleted. Non-recurring items ignore this parameter.
378
+ """
364
379
  resolved_item_id = item_id if item_id is not None else planned_item_id
365
380
  if resolved_item_id is None:
366
381
  msg = "item_id is required"
367
382
  raise ValueError(msg)
368
- await self._send_no_content_action("delete", path=f"/api/v1/planned-items/{resolved_item_id}")
383
+ path = f"/api/planned-items/{resolved_item_id}"
384
+ if scope != "this":
385
+ path += f"?scope={scope}"
386
+ await self._send_no_content_action("delete", path=path)
369
387
 
370
388
  async def async_get_calendar(
371
389
  self,
@@ -383,7 +401,7 @@ class DaynestClient:
383
401
  encoded_params = urlencode(params)
384
402
  return await self._cached_call(
385
403
  "async_get_calendar",
386
- lambda: self._request_list(f"/api/v1/integrations/home-assistant/calendar?{encoded_params}"),
404
+ lambda: self._request_list(f"/api/integrations/home-assistant/calendar?{encoded_params}"),
387
405
  start.isoformat(),
388
406
  end.isoformat(),
389
407
  event_type or "",
@@ -391,7 +409,7 @@ class DaynestClient:
391
409
 
392
410
  async def async_list_routine_templates(self) -> list[RoutineTemplate]:
393
411
  """List routine templates."""
394
- payload = await self._cached_call("async_list_routine_templates", lambda: self._request_list("/api/v1/templates/routines"))
412
+ payload = await self._cached_call("async_list_routine_templates", lambda: self._request_list("/api/templates/routines"))
395
413
  return [RoutineTemplate.from_dict(item) for item in payload]
396
414
 
397
415
  async def async_create_routine_template(
@@ -408,7 +426,7 @@ class DaynestClient:
408
426
  """Create a routine template."""
409
427
  result = await self._send_action(
410
428
  "post",
411
- path="/api/v1/templates/routines",
429
+ path="/api/templates/routines",
412
430
  payload={
413
431
  "name": name,
414
432
  "every_n_days": every_n_days,
@@ -426,19 +444,19 @@ class DaynestClient:
426
444
  payload_fields = dict(fields)
427
445
  if "start_date" in payload_fields and isinstance(payload_fields["start_date"], date):
428
446
  payload_fields["start_date"] = payload_fields["start_date"].isoformat()
429
- result = await self._send_action("put", path=f"/api/v1/templates/routines/{template_id}", payload=payload_fields)
447
+ result = await self._send_action("put", path=f"/api/templates/routines/{template_id}", payload=payload_fields)
430
448
  return RoutineTemplate.from_dict(result)
431
449
 
432
450
  async def async_delete_routine_template(self, template_id: int) -> None:
433
451
  """Delete a routine template."""
434
- await self._send_no_content_action("delete", path=f"/api/v1/templates/routines/{template_id}")
452
+ await self._send_no_content_action("delete", path=f"/api/templates/routines/{template_id}")
435
453
 
436
454
  async def async_list_chore_templates(self, *, tags: list[str] | None = None) -> list[ChoreTemplate]:
437
455
  """List chore templates."""
438
456
  query = f"?{urlencode({'tags': ','.join(tags)})}" if tags else ""
439
457
  payload = await self._cached_call(
440
458
  "async_list_chore_templates",
441
- lambda: self._request_list(f"/api/v1/templates/chores{query}"),
459
+ lambda: self._request_list(f"/api/templates/chores{query}"),
442
460
  ",".join(tags or []),
443
461
  )
444
462
  return [ChoreTemplate.from_dict(item) for item in payload]
@@ -458,7 +476,7 @@ class DaynestClient:
458
476
  """Create a chore template."""
459
477
  result = await self._send_action(
460
478
  "post",
461
- path="/api/v1/templates/chores",
479
+ path="/api/templates/chores",
462
480
  payload={
463
481
  "name": name,
464
482
  "every_n_days": every_n_days,
@@ -477,18 +495,18 @@ class DaynestClient:
477
495
  payload_fields = dict(fields)
478
496
  if "start_date" in payload_fields and isinstance(payload_fields["start_date"], date):
479
497
  payload_fields["start_date"] = payload_fields["start_date"].isoformat()
480
- result = await self._send_action("put", path=f"/api/v1/templates/chores/{template_id}", payload=payload_fields)
498
+ result = await self._send_action("put", path=f"/api/templates/chores/{template_id}", payload=payload_fields)
481
499
  return ChoreTemplate.from_dict(result)
482
500
 
483
501
  async def async_delete_chore_template(self, template_id: int) -> None:
484
502
  """Delete a chore template."""
485
- await self._send_no_content_action("delete", path=f"/api/v1/templates/chores/{template_id}")
503
+ await self._send_no_content_action("delete", path=f"/api/templates/chores/{template_id}")
486
504
 
487
505
  async def async_get_calendar_month(self, year: int, month: int) -> list[CalendarDay]:
488
506
  """Fetch calendar month summaries."""
489
507
  payload = await self._cached_call(
490
508
  "async_get_calendar_month",
491
- lambda: self._request_dict(f"/api/v1/calendar/month?{urlencode({'year': year, 'month': month})}"),
509
+ lambda: self._request_dict(f"/api/calendar/month?{urlencode({'year': year, 'month': month})}"),
492
510
  year,
493
511
  month,
494
512
  )
@@ -502,7 +520,7 @@ class DaynestClient:
502
520
  """Fetch calendar day details."""
503
521
  payload = await self._cached_call(
504
522
  "async_get_calendar_day",
505
- lambda: self._request_dict(f"/api/v1/calendar/day?{urlencode({'date': target_date.isoformat()})}"),
523
+ lambda: self._request_dict(f"/api/calendar/day?{urlencode({'date': target_date.isoformat()})}"),
506
524
  target_date.isoformat(),
507
525
  )
508
526
  return CalendarDay.from_day_dict(payload)
@@ -519,7 +537,7 @@ class DaynestClient:
519
537
 
520
538
  async def async_export_calendar_ics(self) -> bytes:
521
539
  """Export calendar iCalendar bytes."""
522
- return await self._cached_call("async_export_calendar_ics", lambda: self._request_bytes("/api/v1/calendar/export.ics"))
540
+ return await self._cached_call("async_export_calendar_ics", lambda: self._request_bytes("/api/calendar/export.ics"))
523
541
 
524
542
  async def async_listen(
525
543
  self,
@@ -541,7 +559,7 @@ class DaynestClient:
541
559
  return
542
560
  url = urljoin(
543
561
  f"{self._base_url}/",
544
- f"/api/v1/today/stream?{urlencode({'token': token})}",
562
+ f"/api/today/stream?{urlencode({'token': token})}",
545
563
  )
546
564
  try:
547
565
  async with session.get(
@@ -549,7 +567,7 @@ class DaynestClient:
549
567
  headers={"Accept": "text/event-stream"},
550
568
  timeout=None,
551
569
  ) as response:
552
- self._check_response_status(response, "/api/v1/today/stream")
570
+ self._check_response_status(response, "/api/today/stream")
553
571
  backoff_seconds = 1.0
554
572
  event_name = "message"
555
573
  data_lines: list[str] = []
@@ -564,7 +564,7 @@ class TestDaynestClientWriteMethods:
564
564
  }
565
565
  assert isinstance(result, PlannedItem)
566
566
  assert result.id == 10
567
- assert session.post.call_args[0][0].endswith("/api/v1/planned-items")
567
+ assert session.post.call_args[0][0].endswith("/api/planned-items")
568
568
 
569
569
  async def test_update_planned_item_uses_expected_payload(self) -> None:
570
570
  response = _make_mock_response(200, {**VALID_PLANNED_ITEM_PAYLOAD, "is_done": True})
@@ -585,7 +585,7 @@ class TestDaynestClientWriteMethods:
585
585
  assert call_kwargs["json"]["planned_for"] == "2026-01-16"
586
586
  assert call_kwargs["json"]["is_done"] is True
587
587
  assert isinstance(result, PlannedItem)
588
- assert session.put.call_args[0][0].endswith("/api/v1/planned-items/10")
588
+ assert session.put.call_args[0][0].endswith("/api/planned-items/10")
589
589
 
590
590
  async def test_delete_planned_item_uses_expected_endpoint(self) -> None:
591
591
  response = _make_mock_response(204, {})
@@ -596,7 +596,7 @@ class TestDaynestClientWriteMethods:
596
596
  await client.async_delete_planned_item(planned_item_id=10)
597
597
 
598
598
  call_args = session.delete.call_args[0]
599
- assert call_args[0].endswith("/api/v1/planned-items/10")
599
+ assert call_args[0].endswith("/api/planned-items/10")
600
600
 
601
601
 
602
602
  VALID_CALENDAR_PAYLOAD = [
@@ -494,20 +494,20 @@ wheels = [
494
494
 
495
495
  [[package]]
496
496
  name = "pytest-asyncio"
497
- version = "1.3.0"
497
+ version = "1.4.0"
498
498
  source = { registry = "https://pypi.org/simple" }
499
499
  dependencies = [
500
500
  { name = "pytest" },
501
501
  { name = "typing-extensions", marker = "python_full_version < '3.13'" },
502
502
  ]
503
- sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" }
503
+ sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" }
504
504
  wheels = [
505
- { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" },
505
+ { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" },
506
506
  ]
507
507
 
508
508
  [[package]]
509
509
  name = "python-daynest"
510
- version = "0.1.0"
510
+ version = "0.1.9"
511
511
  source = { editable = "." }
512
512
  dependencies = [
513
513
  { name = "aiohttp" },
@@ -526,35 +526,35 @@ requires-dist = [{ name = "aiohttp", specifier = ">=3.9" }]
526
526
 
527
527
  [package.metadata.requires-dev]
528
528
  dev = [
529
- { name = "pyright", specifier = ">=1.1" },
530
- { name = "pytest", specifier = ">=8.0" },
531
- { name = "pytest-asyncio", specifier = ">=0.25" },
532
- { name = "ruff", specifier = ">=0.8" },
529
+ { name = "pyright", specifier = "==1.1.409" },
530
+ { name = "pytest", specifier = "==9.0.3" },
531
+ { name = "pytest-asyncio", specifier = "==1.4.0" },
532
+ { name = "ruff", specifier = "==0.15.15" },
533
533
  ]
534
534
 
535
535
  [[package]]
536
536
  name = "ruff"
537
- version = "0.15.14"
537
+ version = "0.15.15"
538
538
  source = { registry = "https://pypi.org/simple" }
539
- sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" }
539
+ sdist = { url = "https://files.pythonhosted.org/packages/84/6f/a76f7d96e5c962f5b69cee865e49c15c1116897c01990faa8a57edb62e7f/ruff-0.15.15.tar.gz", hash = "sha256:b8dff018130b46d8e5bf0f926ef6b60cf871d6d5ae45fc9334e09632daa741d6", size = 4706985, upload-time = "2026-05-28T14:16:57.784Z" }
540
540
  wheels = [
541
- { url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" },
542
- { url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" },
543
- { url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" },
544
- { url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" },
545
- { url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" },
546
- { url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" },
547
- { url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" },
548
- { url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" },
549
- { url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" },
550
- { url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" },
551
- { url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" },
552
- { url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" },
553
- { url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" },
554
- { url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" },
555
- { url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" },
556
- { url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" },
557
- { url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" },
541
+ { url = "https://files.pythonhosted.org/packages/fa/9d/3a45c05b8ab04b4705989de70a79008e27c8003296a0feaee9edc18dd7e9/ruff-0.15.15-py3-none-linux_armv6l.whl", hash = "sha256:cf93e5388f412e1b108b1f8b34a6e036b70fe8aff89393befad96fe48670311b", size = 10710652, upload-time = "2026-05-28T14:16:06.701Z" },
542
+ { url = "https://files.pythonhosted.org/packages/05/66/da974431624bf3b49f6ee1f9543c02d929ff1cba78b0d5a79c38cf21f744/ruff-0.15.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac5a646d1f6a7dadd5d50842dae2c1f9862ac887ef5d1b1375e02def791fde6e", size = 11096615, upload-time = "2026-05-28T14:16:23.313Z" },
543
+ { url = "https://files.pythonhosted.org/packages/8c/09/7443452e5d290230a712103f2fdceeef7184f3ec99a2bd01c8be78aaceb5/ruff-0.15.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:77d955a431430c66f72dd94e379ad38a16daea3d25094872ac4edf9e797be530", size = 10436683, upload-time = "2026-05-28T14:16:40.974Z" },
544
+ { url = "https://files.pythonhosted.org/packages/53/01/d330c26a57fa4f3943a14424904027428315b700fe4d14a84bb123a649e5/ruff-0.15.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7614ee79c69788cf6cedd568069ade9cecc22a1ad20494efe8d0c9ebb4b622d4", size = 10769064, upload-time = "2026-05-28T14:16:28.905Z" },
545
+ { url = "https://files.pythonhosted.org/packages/1d/85/cc8770f8bdff541b1da8392d1634141fe4a0e3f4ee596605959b7906c27f/ruff-0.15.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3cdb1679e06a1f6b47bc384714ae96f6e2fb65ca441eb78c43d2ca554176ce1f", size = 10511987, upload-time = "2026-05-28T14:16:43.732Z" },
546
+ { url = "https://files.pythonhosted.org/packages/7c/29/8c190c1472b63013583ba391f3342036e02010544c1270455ed8e519bdf3/ruff-0.15.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2728b93d7b23a603ea2c0ac6eb73d760bd38ec9de35f35fb41e18f7a3fee7622", size = 11275100, upload-time = "2026-05-28T14:16:55.244Z" },
547
+ { url = "https://files.pythonhosted.org/packages/9f/6b/7e145ce2cc8e63d6834eca03d83a0e18d121def5c69f91b4cf4011ed4879/ruff-0.15.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be582fcc0db438902c7792b08d6ddf6c9b9e21addaa10092c2c741cfb09e5a45", size = 12176903, upload-time = "2026-05-28T14:16:14.368Z" },
548
+ { url = "https://files.pythonhosted.org/packages/80/a3/d5974637f68e451f7fadf015cf3101d1cd7d8ba5027cffe0b9e3826ebe6b/ruff-0.15.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7aa77465b8ecaf1a27bea098d696f7fed5e1eccbd10b321b682d6de586ae5627", size = 11404550, upload-time = "2026-05-28T14:16:20.138Z" },
549
+ { url = "https://files.pythonhosted.org/packages/fe/1c/e6e5e568f22be4fb05d6244234aba384c06b451252453b821e1a529263cf/ruff-0.15.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48decfa11d740de4889de623be1463308346312f2409a56e24aa280c86162dc4", size = 11382027, upload-time = "2026-05-28T14:16:46.615Z" },
550
+ { url = "https://files.pythonhosted.org/packages/1d/01/170921b49fcd2e8858825593f91cf7146c3e40a5c3e6df763e4bb0484dde/ruff-0.15.15-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a5015088452ca0081387063649ec67f06d3d1d6b8b936a1f836b5e9657ecd48c", size = 11366041, upload-time = "2026-05-28T14:16:26.247Z" },
551
+ { url = "https://files.pythonhosted.org/packages/87/54/a7bad711d7de93254e15e06a4c375b89a03d18de45d3e5dcc86a4472fb1a/ruff-0.15.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5294aab6356c81600fcdea3a62bb1b924dfd5e91767c12318d3f68f86af57cd", size = 10741795, upload-time = "2026-05-28T14:16:17.11Z" },
552
+ { url = "https://files.pythonhosted.org/packages/c9/31/38c075963668f8b41c6914ee0f6f318727fbe30ab9145cb29e6df464c5fa/ruff-0.15.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db5bd4d802415cca656dc1616070b725952d6ae95eb5d4831e49fbd94a38f75f", size = 10511117, upload-time = "2026-05-28T14:16:31.767Z" },
553
+ { url = "https://files.pythonhosted.org/packages/9d/96/6ff689e1f7e375d1d97075eca022f74c2bab59554a432fe4d2e6f091986a/ruff-0.15.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:587a6278ed42059191c1a466e490bd7930fb50bd2e255398bc29616c895a61cb", size = 10994867, upload-time = "2026-05-28T14:16:35.149Z" },
554
+ { url = "https://files.pythonhosted.org/packages/c3/c2/5dce0ab9f92a8d534fa62b9bf9caca3eddb8c1a81b616f5e195ada4f0d6e/ruff-0.15.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:df0c1c084f5f4be9812f61518a45c440d3c30d69ce4bf6c5270e66d38338f02a", size = 11482101, upload-time = "2026-05-28T14:16:49.598Z" },
555
+ { url = "https://files.pythonhosted.org/packages/b1/c0/1003b60edd697c649faf61f1a34094b1abb38fb3d1181e3f895781250a08/ruff-0.15.15-py3-none-win32.whl", hash = "sha256:29428ea79694afbe756d45fd59b36f22b6b020dc0443cf7de0173046236964b9", size = 10716774, upload-time = "2026-05-28T14:16:52.337Z" },
556
+ { url = "https://files.pythonhosted.org/packages/02/a8/1269eddd6945a06c23f055ef7848886e37cf9d6a8bebb386a3115f01470c/ruff-0.15.15-py3-none-win_amd64.whl", hash = "sha256:8df0323902e15e24bc4bf246da830573d3cf3352bd0b9a164eab335d111ff4a4", size = 11868463, upload-time = "2026-05-28T14:16:11.333Z" },
557
+ { url = "https://files.pythonhosted.org/packages/4e/b2/920464c907b191e37469d477a1aa8bc048b8f36c4c1610dfa4ab87b39e18/ruff-0.15.15-py3-none-win_arm64.whl", hash = "sha256:3c8ceca6792f38196b8f589bc92eccd03eef286602da92e5dc05cc42ef6441b7", size = 11138498, upload-time = "2026-05-28T14:16:38.425Z" },
558
558
  ]
559
559
 
560
560
  [[package]]