From 9496eae2d510a8d6cb5208f10f65e2b72d6290c1 Mon Sep 17 00:00:00 2001 From: Bo Lopker Date: Sun, 15 Dec 2024 21:14:07 +0000 Subject: [PATCH] Collect Email Activity from Mailersend --- Makefile | 18 +-- config/settings/base.py | 3 + config/settings/local.py | 2 +- config/settings/production.py | 6 +- totem/circles/shotify.py | 94 ++++++------- totem/email/admin.py | 9 +- totem/email/migrations/0007_emailactivity.py | 25 ++++ ...e_id_emailactivity_activity_id_and_more.py | 52 ++++++++ ...009_emailactivity_date_created_and_more.py | 25 ++++ totem/email/models.py | 90 ++++++++++++- totem/email/tasks.py | 11 +- uv.lock | 125 +++++++++--------- 12 files changed, 332 insertions(+), 128 deletions(-) create mode 100644 totem/email/migrations/0007_emailactivity.py create mode 100644 totem/email/migrations/0008_rename_message_id_emailactivity_activity_id_and_more.py create mode 100644 totem/email/migrations/0009_emailactivity_date_created_and_more.py diff --git a/Makefile b/Makefile index ce6ca642..521f1f75 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ MAKEFLAGS += -j4 .PHONY: * +RUN_DJANGO = docker compose -f local.yml run --rm --remove-orphans django + run: up assets-watch up: @@ -18,13 +20,13 @@ test-js: npm run test:ci test-python: - docker compose -f local.yml run --rm django coverage run -m pytest -n auto + @${RUN_DJANGO} coverage run -m pytest -n auto tasks: - docker compose -f local.yml run --rm django python manage.py totem_tasks + @${RUN_DJANGO} python manage.py totem_tasks shell: - docker compose -f local.yml run --rm django bash + @${RUN_DJANGO} bash dbshell: docker compose -f local.yml exec postgres bash @@ -33,7 +35,7 @@ sqlshell: docker compose -f local.yml exec postgres psql -U debug -d totem pyshell: - docker compose -f local.yml run --rm django ./manage.py shell_plus + @${RUN_DJANGO} ./manage.py shell_plus deploy: git push dokku @@ -55,16 +57,16 @@ install_local: .venv npm install fixtures: - docker compose -f local.yml run --rm django python manage.py load_dev_data + @${RUN_DJANGO} python manage.py load_dev_data migrations: ## Create DB migrations in the container - @docker compose -f local.yml run django python manage.py makemigrations + @${RUN_DJANGO} python manage.py makemigrations migrate: ## Run DB migrations in the container - @docker compose -f local.yml run django python manage.py migrate + @${RUN_DJANGO} python manage.py migrate generate_api_models: - @docker compose -f local.yml run --rm --remove-orphans django python manage.py export_openapi_schema --api totem.api.api.api > openapi.json + @${RUN_DJANGO} python manage.py export_openapi_schema --api totem.api.api.api > openapi.json @npm run openapi-ts updatedep: diff --git a/config/settings/base.py b/config/settings/base.py index 275cdd7b..9de5acab 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -299,6 +299,9 @@ def b64_json_env(key: str): EMAIL_SUPPORT_ADDRESS = "help@totem.org" EMAIL_SHOW_ENV_BANNER = env.bool("EMAIL_SHOW_ENV_BANNER", default=False) # type: ignore MAILERLITE_API_KEY = env("MAILERLITE_API_KEY", default="") # type: ignore +MAILERSEND_API_TOKEN = env("MAILERSEND_API_TOKEN", default="") # type: ignore +MAILERSEND_COLLECT_ACTIVITY = env.bool("MAILERSEND_COLLECT_ACTIVITY", default=False) # type: ignore +MAILERSEND_DOMAIN_ID = env("MAILERSEND_DOMAIN_ID", default="") # type: ignore BREVO_API_KEY = env("BREVO_API_KEY", default="") # type: ignore SEND_BREVO_EMAILS = env.bool("SEND_BREVO_EMAILS", default=False) # type: ignore diff --git a/config/settings/local.py b/config/settings/local.py index 79ba00dc..1e32d1e8 100644 --- a/config/settings/local.py +++ b/config/settings/local.py @@ -47,7 +47,7 @@ # INSTALLED_APPS += ["anymail"] # noqa: F405 # EMAIL_BACKEND = "anymail.backends.mailersend.EmailBackend" -# ANYMAIL = {"MAILERSEND_API_TOKEN": env("MAILERSEND_API_TOKEN"), "MAILERSEND_BATCH_SEND_MODE": "use-bulk-email"} +# ANYMAIL = {"MAILERSEND_API_TOKEN": MAILERSEND_API_TOKEN, "MAILERSEND_BATCH_SEND_MODE": "use-bulk-email"} # WhiteNoise # ------------------------------------------------------------------------------ diff --git a/config/settings/production.py b/config/settings/production.py index aaa4077b..ca52b28e 100644 --- a/config/settings/production.py +++ b/config/settings/production.py @@ -1,7 +1,7 @@ import socket from .base import * # noqa -from .base import env +from .base import MAILERSEND_API_TOKEN, env STATIC_HOST = STATIC_HOST or None # noqa: F405 @@ -28,7 +28,7 @@ # "LOCATION": env("REDIS_URL"), # "OPTIONS": { # "CLIENT_CLASS": "django_redis.client.DefaultClient", -# # Mimicing memcache behavior. +# # Mimicking memcache behavior. # # https://github.com/jazzband/django-redis#memcached-exceptions-behavior # "IGNORE_EXCEPTIONS": True, # }, @@ -79,7 +79,7 @@ # # https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference # # https://anymail.readthedocs.io/en/stable/esps/mailgun/ EMAIL_BACKEND = "anymail.backends.mailersend.EmailBackend" -ANYMAIL = {"MAILERSEND_API_TOKEN": env("MAILERSEND_API_TOKEN"), "MAILERSEND_BATCH_SEND_MODE": "use-bulk-email"} +ANYMAIL = {"MAILERSEND_API_TOKEN": MAILERSEND_API_TOKEN, "MAILERSEND_BATCH_SEND_MODE": "use-bulk-email"} # LOGGING diff --git a/totem/circles/shotify.py b/totem/circles/shotify.py index f7ccb6ef..a0e5d4c7 100644 --- a/totem/circles/shotify.py +++ b/totem/circles/shotify.py @@ -1,63 +1,63 @@ -import asyncio -from io import BytesIO +# import asyncio +# from io import BytesIO -from PIL import Image -from playwright.async_api import async_playwright +# from PIL import Image +# from playwright.async_api import async_playwright -class ScreenshotRequestBody: - def __init__(self, url: str, selector: str): - self.url = url - self.selector = selector +# class ScreenshotRequestBody: +# def __init__(self, url: str, selector: str): +# self.url = url +# self.selector = selector -async def take_screenshot(request_body: ScreenshotRequestBody): - if not request_body.url or not request_body.selector: - raise Exception("Missing required fields: url or selector") +# async def take_screenshot(request_body: ScreenshotRequestBody): +# if not request_body.url or not request_body.selector: +# raise Exception("Missing required fields: url or selector") - browser = None - page = None +# browser = None +# page = None - try: - async with async_playwright() as p: - browser = await p.chromium.launch() - page = await browser.new_page(device_scale_factor=2) +# try: +# async with async_playwright() as p: +# browser = await p.chromium.launch() +# page = await browser.new_page(device_scale_factor=2) - await page.set_viewport_size({"width": 1280, "height": 800}) - await page.goto(request_body.url, wait_until="networkidle") +# await page.set_viewport_size({"width": 1280, "height": 800}) +# await page.goto(request_body.url, wait_until="networkidle") - element = await page.query_selector(request_body.selector) - if not element: - raise Exception("Element not found") +# element = await page.query_selector(request_body.selector) +# if not element: +# raise Exception("Element not found") - screenshot_buffer = await element.screenshot(type="png") - image = Image.open(BytesIO(screenshot_buffer)) - with BytesIO() as output_buffer: - image.save(output_buffer, format="WEBP", quality=80) - optimized_image_buffer = output_buffer.getvalue() +# screenshot_buffer = await element.screenshot(type="png") +# image = Image.open(BytesIO(screenshot_buffer)) +# with BytesIO() as output_buffer: +# image.save(output_buffer, format="WEBP", quality=80) +# optimized_image_buffer = output_buffer.getvalue() - return optimized_image_buffer +# return optimized_image_buffer - finally: - if page: - await page.close() - if browser: - await browser.close() +# finally: +# if page: +# await page.close() +# if browser: +# await browser.close() -async def main(): - try: - request_body = ScreenshotRequestBody( - url="https://www.totem.org/circles/event/zdx624ofr/social", - selector="[data-img]", - ) - screenshot = await take_screenshot(request_body) - with open("screenshot.webp", "wb") as f: - f.write(screenshot) - print("Screenshot saved as screenshot.webp") - except Exception as e: - print("Error:", str(e)) +# async def main(): +# try: +# request_body = ScreenshotRequestBody( +# url="https://www.totem.org/circles/event/zdx624ofr/social", +# selector="[data-img]", +# ) +# screenshot = await take_screenshot(request_body) +# with open("screenshot.webp", "wb") as f: +# f.write(screenshot) +# print("Screenshot saved as screenshot.webp") +# except Exception as e: +# print("Error:", str(e)) -if __name__ == "__main__": - asyncio.run(main()) +# if __name__ == "__main__": +# asyncio.run(main()) diff --git a/totem/email/admin.py b/totem/email/admin.py index 213dc3be..8f55fa04 100644 --- a/totem/email/admin.py +++ b/totem/email/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import EmailLog, SubscribedModel +from .models import EmailActivity, EmailLog, SubscribedModel @admin.register(SubscribedModel) @@ -19,3 +19,10 @@ class EmailLogAdmin(admin.ModelAdmin): list_filter = ["template"] readonly_fields = [field.name for field in EmailLog._meta.get_fields()] actions = [clear_logs] + + +@admin.register(EmailActivity) +class EmailActivityAdmin(admin.ModelAdmin): + list_display = ("email", "event_type", "timestamp", "subject", "status") + search_fields = ("email", "subject", "event_type", "status") + list_filter = ("event_type", "status", "timestamp") diff --git a/totem/email/migrations/0007_emailactivity.py b/totem/email/migrations/0007_emailactivity.py new file mode 100644 index 00000000..9bcad75a --- /dev/null +++ b/totem/email/migrations/0007_emailactivity.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1.4 on 2024-12-15 17:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('email', '0006_remove_emaillog_body_remove_emaillog_subscribed_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='EmailActivity', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('event', models.CharField(max_length=255)), + ('timestamp', models.DateTimeField()), + ('email', models.EmailField(max_length=254)), + ('status', models.CharField(blank=True, max_length=255, null=True)), + ('message_id', models.CharField(max_length=255, unique=True)), + ('metadata', models.JSONField(blank=True, null=True)), + ], + ), + ] diff --git a/totem/email/migrations/0008_rename_message_id_emailactivity_activity_id_and_more.py b/totem/email/migrations/0008_rename_message_id_emailactivity_activity_id_and_more.py new file mode 100644 index 00000000..d501821c --- /dev/null +++ b/totem/email/migrations/0008_rename_message_id_emailactivity_activity_id_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 5.1.4 on 2024-12-15 20:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('email', '0007_emailactivity'), + ] + + operations = [ + migrations.RenameField( + model_name='emailactivity', + old_name='message_id', + new_name='activity_id', + ), + migrations.RenameField( + model_name='emailactivity', + old_name='metadata', + new_name='data', + ), + migrations.RemoveField( + model_name='emailactivity', + name='event', + ), + migrations.AddField( + model_name='emailactivity', + name='event_type', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='emailactivity', + name='subject', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AlterField( + model_name='emailactivity', + name='email', + field=models.EmailField(blank=True, max_length=254, null=True), + ), + migrations.AlterField( + model_name='emailactivity', + name='id', + field=models.AutoField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='emailactivity', + name='status', + field=models.CharField(blank=True, max_length=50, null=True), + ), + ] diff --git a/totem/email/migrations/0009_emailactivity_date_created_and_more.py b/totem/email/migrations/0009_emailactivity_date_created_and_more.py new file mode 100644 index 00000000..9b4e0934 --- /dev/null +++ b/totem/email/migrations/0009_emailactivity_date_created_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1.4 on 2024-12-15 21:04 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('email', '0008_rename_message_id_emailactivity_activity_id_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='emailactivity', + name='date_created', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='emailactivity', + name='date_modified', + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/totem/email/models.py b/totem/email/models.py index bca2ab6f..80621be3 100644 --- a/totem/email/models.py +++ b/totem/email/models.py @@ -1,10 +1,12 @@ import uuid -from datetime import timedelta +from datetime import datetime, timedelta +import requests from django.conf import settings from django.db import models from django.urls import reverse from django.utils import timezone +from utils.models import BaseModel # subscribe: form, welcome email, subscribed page # unsubscribe: unsubscribe link, unsubscribe page, unsubscribe email @@ -56,8 +58,92 @@ def __str__(self): @classmethod def clear_old(cls): - cls.objects.filter(created__lte=timezone.now() - timedelta(days=30)).delete() + cls.objects.filter(created__lte=timezone.now() - timedelta(days=365)).delete() class Meta: verbose_name_plural = "Email Logs" ordering = ("-created",) + + +class EmailActivity(BaseModel): + id = models.AutoField(primary_key=True) # Internal primary key + activity_id = models.CharField(max_length=255, unique=True) # MailerSend's activity ID + event_type = models.CharField(max_length=50, null=True) + email = models.EmailField(blank=True, null=True) + timestamp = models.DateTimeField() + subject = models.CharField(max_length=255, blank=True, null=True) + status = models.CharField(max_length=50, blank=True, null=True) + data = models.JSONField(blank=True, null=True) + + def __str__(self): + return f"{self.event_type} - {self.timestamp}" + + @classmethod + def fetch_email_activity(cls): + # If the newest log is less than 12 hours old, don't fetch new logs + if cls.objects.latest("timestamp").timestamp > timezone.now() - timedelta(hours=12): + print("Skipping fetch_email_activity because the newest log is less than 12 hours old.") + return + _fetch_email_activity() + + @classmethod + def clear_old(cls): + cls.objects.filter(created__lte=timezone.now() - timedelta(days=365)).delete() + + +def _fetch_email_activity(): + if not settings.MAILERSEND_COLLECT_ACTIVITY: + return + if not settings.MAILERSEND_API_TOKEN: + raise Exception("MAILERSEND_API_TOKEN not set") + if not settings.MAILERSEND_DOMAIN_ID: + raise Exception("MAILERSEND_DOMAIN_ID not set") + page = 1 + now = datetime.now() + while True: + next = _get_activity_page(page, now) + if not next: + break + page += 1 + + +def _get_activity_page(page: int, now: datetime): + DOMAIN_ID = settings.MAILERSEND_DOMAIN_ID + MAILERSEND_API_URL = f"https://api.mailersend.com/v1/activity/{DOMAIN_ID}" + + # Convert datetime to Unix timestamps + date_from = int((now - timedelta(days=1)).timestamp()) + date_to = int(now.timestamp()) + + params = { + "date_from": date_from, # Unix timestamp + "date_to": date_to, # Unix timestamp + "limit": 100, + "page": page, + } + + headers = {"Authorization": f"Bearer {settings.MAILERSEND_API_TOKEN}"} + response = requests.get(MAILERSEND_API_URL, headers=headers, params=params) + + if response.status_code != 200: + raise Exception(f"MailerSend API error: {response.status_code} - {response.text}") + payload = response.json() + next = payload.get("links", {}).get("next") + activities = payload.get("data", []) + for activity in activities: + _save_activity(activity) + return next is not None + + +def _save_activity(activity: dict): + EmailActivity.objects.update_or_create( + activity_id=activity["id"], + defaults={ + "event_type": activity["type"], + "timestamp": datetime.fromisoformat(activity["updated_at"]), + "email": activity.get("email", {}).get("recipient", {}).get("email"), + "status": activity.get("email", {}).get("status"), + "subject": activity.get("email", {}).get("subject"), + "data": activity, + }, + ) diff --git a/totem/email/tasks.py b/totem/email/tasks.py index 386f1f29..8d4df8dc 100644 --- a/totem/email/tasks.py +++ b/totem/email/tasks.py @@ -1,8 +1,13 @@ -from .models import EmailLog +from .models import EmailActivity, EmailLog -def clear_old_email_logs(): +def clear_old_logs(): EmailLog.clear_old() + EmailActivity.clear_old() -tasks = [clear_old_email_logs] +def backup_email_activity(): + EmailActivity.fetch_email_activity() + + +tasks = [clear_old_logs, backup_email_activity] diff --git a/uv.lock b/uv.lock index ed810346..74ab3285 100644 --- a/uv.lock +++ b/uv.lock @@ -100,30 +100,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.35.76" +version = "1.35.81" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/24/31f56d43419dadf71d9d30192ebc1577fdef2e703622e1fe4cf370cce98f/boto3-1.35.76.tar.gz", hash = "sha256:31ddcdb6f15dace2b68f6a0f11bdb58dd3ae79b8a3ccb174ff811ef0bbf938e0", size = 111023 } +sdist = { url = "https://files.pythonhosted.org/packages/d9/a5/8e610a7c230326b6a766758ce290233a8d0ec88bef4f5afe09e2313d2def/boto3-1.35.81.tar.gz", hash = "sha256:d2e95fa06f095b8e0c545dd678c6269d253809b2997c30f5ce8a956c410b4e86", size = 111013 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/d6/36ed30de3cf85d2431c3ef9739e731ad8f8bfabeb8f556e35992a55d5834/boto3-1.35.76-py3-none-any.whl", hash = "sha256:69458399f41f57a50770c8974796d96978bcca44915c260319696bb43e47dffd", size = 139178 }, + { url = "https://files.pythonhosted.org/packages/b4/db/e6bf2a34d7e8440800fcd11f2b42efd4ba18cce56d5a213bb93bd62aaa0e/boto3-1.35.81-py3-none-any.whl", hash = "sha256:742941b2424c0223d2d94a08c3485462fa7c58d816b62ca80f08e555243acee1", size = 139178 }, ] [[package]] name = "botocore" -version = "1.35.76" +version = "1.35.81" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/73/c3f127c48869a3555e59c5cd381de119d96a02912dc789443fdaefa44807/botocore-1.35.76.tar.gz", hash = "sha256:a75a42ae53395796b8300c5fefb2d65a8696dc40dc85e49cf3a769e0c0202b13", size = 13439455 } +sdist = { url = "https://files.pythonhosted.org/packages/3d/a8/b44d94c14ee4eb13db6dc549269c79199b43bddd70982e192aefd6ca6279/botocore-1.35.81.tar.gz", hash = "sha256:564c2478e50179e0b766e6a87e5e0cdd35e1bc37eb375c1cf15511f5dd13600d", size = 13460205 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/c1/b6dc5e5d11efd493892daf4466ab720e4eb9c294aa05d7e571b27edc7842/botocore-1.35.76-py3-none-any.whl", hash = "sha256:b4729d12d00267b3185628f83543917b6caae292385230ab464067621aa086af", size = 13243531 }, + { url = "https://files.pythonhosted.org/packages/1a/ad/00dfec368dd4e957063ed1126b5511238b0900c1014dfe539af93fc0ac29/botocore-1.35.81-py3-none-any.whl", hash = "sha256:a7b13bbd959bf2d6f38f681676aab408be01974c46802ab997617b51399239f7", size = 13265330 }, ] [[package]] @@ -151,11 +151,11 @@ wheels = [ [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, + { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, ] [[package]] @@ -671,7 +671,7 @@ wheels = [ [[package]] name = "google-api-core" -version = "2.23.0" +version = "2.24.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, @@ -680,14 +680,14 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/6b/b98553c2061c4e2186f5bbfb1aa1a6ef13fc0775c096d18595d3c99ba023/google_api_core-2.23.0.tar.gz", hash = "sha256:2ceb087315e6af43f256704b871d99326b1f12a9d6ce99beaedec99ba26a0ace", size = 160094 } +sdist = { url = "https://files.pythonhosted.org/packages/81/56/d70d66ed1b5ab5f6c27bf80ec889585ad8f865ff32acbafd3b2ef0bfb5d0/google_api_core-2.24.0.tar.gz", hash = "sha256:e255640547a597a4da010876d333208ddac417d60add22b6851a0c66a831fcaf", size = 162647 } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/a4/c26886d57d90032c5f74c2e80aefdc38ec58551fc46bd4ce79fb2c9389fa/google_api_core-2.23.0-py3-none-any.whl", hash = "sha256:c20100d4c4c41070cf365f1d8ddf5365915291b5eb11b83829fbd1c999b5122f", size = 156554 }, + { url = "https://files.pythonhosted.org/packages/a1/76/65b8b94e74bf1b6d1cc38d916089670c4da5029d25762441d8c5c19e51dd/google_api_core-2.24.0-py3-none-any.whl", hash = "sha256:10d82ac0fca69c82a25b3efdeefccf6f28e02ebb97925a8cce8edbfe379929d9", size = 158576 }, ] [[package]] name = "google-api-python-client" -version = "2.154.0" +version = "2.155.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core" }, @@ -696,23 +696,23 @@ dependencies = [ { name = "httplib2" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b8/fa/861931cf33f3d91d0d17001af3249cfc2af2b5a1b8472604420c025f3339/google_api_python_client-2.154.0.tar.gz", hash = "sha256:1b420062e03bfcaa1c79e2e00a612d29a6a934151ceb3d272fe150a656dc8f17", size = 12070143 } +sdist = { url = "https://files.pythonhosted.org/packages/c8/7c/95defad65947571c152de64d67689e34185bfcafeb1e9c09d3a58f463027/google_api_python_client-2.155.0.tar.gz", hash = "sha256:25529f89f0d13abcf3c05c089c423fb2858ac16e0b3727543393468d0d7af67c", size = 12195015 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/3f/adf5f6b963307765209fc27943516c7c605d250234b01899d524b8f01fe3/google_api_python_client-2.154.0-py2.py3-none-any.whl", hash = "sha256:a521bbbb2ec0ba9d6f307cdd64ed6e21eeac372d1bd7493a4ab5022941f784ad", size = 12573774 }, + { url = "https://files.pythonhosted.org/packages/4d/6f/d8d446026a6b5a5e1f25604d6b7ddff06dcffcfaea2e95d61a3d5086cbe4/google_api_python_client-2.155.0-py2.py3-none-any.whl", hash = "sha256:83fe9b5aa4160899079d7c93a37be306546a17e6686e2549bcc9584f1a229747", size = 12703592 }, ] [[package]] name = "google-auth" -version = "2.36.0" +version = "2.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, { name = "pyasn1-modules" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/71/4c5387d8a3e46e3526a8190ae396659484377a73b33030614dd3b28e7ded/google_auth-2.36.0.tar.gz", hash = "sha256:545e9618f2df0bcbb7dcbc45a546485b1212624716975a1ea5ae8149ce769ab1", size = 268336 } +sdist = { url = "https://files.pythonhosted.org/packages/46/af/b25763b9d35dfc2c6f9c3ec34d8d3f1ba760af3a7b7e8d5c5f0579522c45/google_auth-2.37.0.tar.gz", hash = "sha256:0054623abf1f9c83492c63d3f47e77f0a544caa3d40b2d98e099a611c2dd5d00", size = 268878 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/9a/3d5087d27865c2f0431b942b5c4500b7d1b744dd3262fdc973a4c39d099e/google_auth-2.36.0-py2.py3-none-any.whl", hash = "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb", size = 209519 }, + { url = "https://files.pythonhosted.org/packages/8d/8d/4d5d5f9f500499f7bd4c93903b43e8d6976f3fc6f064637ded1a85d09b07/google_auth-2.37.0-py2.py3-none-any.whl", hash = "sha256:42664f18290a6be591be5329a96fe30184be1a1badb7292a7f686a9659de9ca0", size = 209829 }, ] [[package]] @@ -1726,27 +1726,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.8.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/2b/01245f4f3a727d60bebeacd7ee6d22586c7f62380a2597ddb22c2f45d018/ruff-0.8.2.tar.gz", hash = "sha256:b84f4f414dda8ac7f75075c1fa0b905ac0ff25361f42e6d5da681a465e0f78e5", size = 3349020 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/29/366be70216dba1731a00a41f2f030822b0c96c7c4f3b2c0cdce15cbace74/ruff-0.8.2-py3-none-linux_armv6l.whl", hash = "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d", size = 10530649 }, - { url = "https://files.pythonhosted.org/packages/63/82/a733956540bb388f00df5a3e6a02467b16c0e529132625fe44ce4c5fb9c7/ruff-0.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5", size = 10274069 }, - { url = "https://files.pythonhosted.org/packages/3d/12/0b3aa14d1d71546c988a28e1b412981c1b80c8a1072e977a2f30c595cc4a/ruff-0.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f05cdf8d050b30e2ba55c9b09330b51f9f97d36d4673213679b965d25a785f3c", size = 9909400 }, - { url = "https://files.pythonhosted.org/packages/23/08/f9f08cefb7921784c891c4151cce6ed357ff49e84b84978440cffbc87408/ruff-0.8.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60f578c11feb1d3d257b2fb043ddb47501ab4816e7e221fbb0077f0d5d4e7b6f", size = 10766782 }, - { url = "https://files.pythonhosted.org/packages/e4/71/bf50c321ec179aa420c8ec40adac5ae9cc408d4d37283a485b19a2331ceb/ruff-0.8.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbd5cf9b0ae8f30eebc7b360171bd50f59ab29d39f06a670b3e4501a36ba5897", size = 10286316 }, - { url = "https://files.pythonhosted.org/packages/f2/83/c82688a2a6117539aea0ce63fdf6c08e60fe0202779361223bcd7f40bd74/ruff-0.8.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b402ddee3d777683de60ff76da801fa7e5e8a71038f57ee53e903afbcefdaa58", size = 11338270 }, - { url = "https://files.pythonhosted.org/packages/7f/d7/bc6a45e5a22e627640388e703160afb1d77c572b1d0fda8b4349f334fc66/ruff-0.8.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:705832cd7d85605cb7858d8a13d75993c8f3ef1397b0831289109e953d833d29", size = 12058579 }, - { url = "https://files.pythonhosted.org/packages/da/3b/64150c93946ec851e6f1707ff586bb460ca671581380c919698d6a9267dc/ruff-0.8.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32096b41aaf7a5cc095fa45b4167b890e4c8d3fd217603f3634c92a541de7248", size = 11615172 }, - { url = "https://files.pythonhosted.org/packages/e4/9e/cf12b697ea83cfe92ec4509ae414dc4c9b38179cc681a497031f0d0d9a8e/ruff-0.8.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e769083da9439508833cfc7c23e351e1809e67f47c50248250ce1ac52c21fb93", size = 12882398 }, - { url = "https://files.pythonhosted.org/packages/a9/27/96d10863accf76a9c97baceac30b0a52d917eb985a8ac058bd4636aeede0/ruff-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe716592ae8a376c2673fdfc1f5c0c193a6d0411f90a496863c99cd9e2ae25d", size = 11176094 }, - { url = "https://files.pythonhosted.org/packages/eb/10/cd2fd77d4a4e7f03c29351be0f53278a393186b540b99df68beb5304fddd/ruff-0.8.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:81c148825277e737493242b44c5388a300584d73d5774defa9245aaef55448b0", size = 10771884 }, - { url = "https://files.pythonhosted.org/packages/71/5d/beabb2ff18870fc4add05fa3a69a4cb1b1d2d6f83f3cf3ae5ab0d52f455d/ruff-0.8.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d261d7850c8367704874847d95febc698a950bf061c9475d4a8b7689adc4f7fa", size = 10382535 }, - { url = "https://files.pythonhosted.org/packages/ae/29/6b3fdf3ad3e35b28d87c25a9ff4c8222ad72485ab783936b2b267250d7a7/ruff-0.8.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1ca4e3a87496dc07d2427b7dd7ffa88a1e597c28dad65ae6433ecb9f2e4f022f", size = 10886995 }, - { url = "https://files.pythonhosted.org/packages/e9/dc/859d889b4d9356a1a2cdbc1e4a0dda94052bc5b5300098647e51a58c430b/ruff-0.8.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:729850feed82ef2440aa27946ab39c18cb4a8889c1128a6d589ffa028ddcfc22", size = 11220750 }, - { url = "https://files.pythonhosted.org/packages/0b/08/e8f519f61f1d624264bfd6b8829e4c5f31c3c61193bc3cff1f19dbe7626a/ruff-0.8.2-py3-none-win32.whl", hash = "sha256:ac42caaa0411d6a7d9594363294416e0e48fc1279e1b0e948391695db2b3d5b1", size = 8729396 }, - { url = "https://files.pythonhosted.org/packages/f8/d4/ba1c7ab72aba37a2b71fe48ab95b80546dbad7a7f35ea28cf66fc5cea5f6/ruff-0.8.2-py3-none-win_amd64.whl", hash = "sha256:2aae99ec70abf43372612a838d97bfe77d45146254568d94926e8ed5bbb409ea", size = 9594729 }, - { url = "https://files.pythonhosted.org/packages/23/34/db20e12d3db11b8a2a8874258f0f6d96a9a4d631659d54575840557164c8/ruff-0.8.2-py3-none-win_arm64.whl", hash = "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8", size = 9035131 }, +version = "0.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/5e/683c7ef7a696923223e7d95ca06755d6e2acbc5fd8382b2912a28008137c/ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3", size = 3378522 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/c4/bfdbb8b9c419ff3b52479af8581026eeaac3764946fdb463dec043441b7d/ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6", size = 10535860 }, + { url = "https://files.pythonhosted.org/packages/ef/c5/0aabdc9314b4b6f051168ac45227e2aa8e1c6d82718a547455e40c9c9faa/ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939", size = 10346327 }, + { url = "https://files.pythonhosted.org/packages/1a/78/4843a59e7e7b398d6019cf91ab06502fd95397b99b2b858798fbab9151f5/ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d", size = 9942585 }, + { url = "https://files.pythonhosted.org/packages/91/5a/642ed8f1ba23ffc2dd347697e01eef3c42fad6ac76603be4a8c3a9d6311e/ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13", size = 10797597 }, + { url = "https://files.pythonhosted.org/packages/30/25/2e654bc7226da09a49730a1a2ea6e89f843b362db80b4b2a7a4f948ac986/ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18", size = 10307244 }, + { url = "https://files.pythonhosted.org/packages/c0/2d/a224d56bcd4383583db53c2b8f410ebf1200866984aa6eb9b5a70f04e71f/ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502", size = 11362439 }, + { url = "https://files.pythonhosted.org/packages/82/01/03e2857f9c371b8767d3e909f06a33bbdac880df17f17f93d6f6951c3381/ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d", size = 12078538 }, + { url = "https://files.pythonhosted.org/packages/af/ae/ff7f97b355da16d748ceec50e1604a8215d3659b36b38025a922e0612e9b/ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82", size = 11616172 }, + { url = "https://files.pythonhosted.org/packages/6a/d0/6156d4d1e53ebd17747049afe801c5d7e3014d9b2f398b9236fe36ba4320/ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452", size = 12919886 }, + { url = "https://files.pythonhosted.org/packages/4e/84/affcb30bacb94f6036a128ad5de0e29f543d3f67ee42b490b17d68e44b8a/ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd", size = 11212599 }, + { url = "https://files.pythonhosted.org/packages/60/b9/5694716bdefd8f73df7c0104334156c38fb0f77673d2966a5a1345bab94d/ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20", size = 10784637 }, + { url = "https://files.pythonhosted.org/packages/24/7e/0e8f835103ac7da81c3663eedf79dec8359e9ae9a3b0d704bae50be59176/ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc", size = 10390591 }, + { url = "https://files.pythonhosted.org/packages/27/da/180ec771fc01c004045962ce017ca419a0281f4bfaf867ed0020f555b56e/ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060", size = 10894298 }, + { url = "https://files.pythonhosted.org/packages/6d/f8/29f241742ed3954eb2222314b02db29f531a15cab3238d1295e8657c5f18/ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea", size = 11275965 }, + { url = "https://files.pythonhosted.org/packages/79/e9/5b81dc9afc8a80884405b230b9429efeef76d04caead904bd213f453b973/ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964", size = 8807651 }, + { url = "https://files.pythonhosted.org/packages/ea/67/7291461066007617b59a707887b90e319b6a043c79b4d19979f86b7a20e7/ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9", size = 9625289 }, + { url = "https://files.pythonhosted.org/packages/03/8f/e4fa95288b81233356d9a9dcaed057e5b0adc6399aa8fd0f6d784041c9c3/ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936", size = 9078754 }, ] [[package]] @@ -1808,11 +1808,11 @@ wheels = [ [[package]] name = "sqlparse" -version = "0.5.2" +version = "0.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/61/5bc3aff85dc5bf98291b37cf469dab74b3d0aef2dd88eade9070a200af05/sqlparse-0.5.2.tar.gz", hash = "sha256:9e37b35e16d1cc652a2545f0997c1deb23ea28fa1f3eefe609eee3063c3b105f", size = 84951 } +sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/13/5f6654c9d915077fae255686ca6fa42095b62b7337e3e1aa9e82caa6f43a/sqlparse-0.5.2-py3-none-any.whl", hash = "sha256:e99bc85c78160918c3e1d9230834ab8d80fc06c59d03f8db2618f65f65dda55e", size = 44407 }, + { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 }, ] [[package]] @@ -2063,27 +2063,26 @@ wheels = [ [[package]] name = "uv" -version = "0.5.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/1c/8c40ec75c26656bec9ada97833a437b49fd443b5d6dfd61d6dda8ad90cbe/uv-0.5.7.tar.gz", hash = "sha256:4d22a5046a6246af85c92257d110ed8fbcd98b16824e4efa9d825d001222b2cb", size = 2356161 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/15/4d05061146ef1ff909458f75812633944a144ebadf73ccd38bef127adc6a/uv-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:fb4a3ccbe13072b98919413ac8378dd3e2b5480352f75c349a4f71f423801485", size = 14208956 }, - { url = "https://files.pythonhosted.org/packages/ba/8f/dc99e8f026da8b3c74661ca60d424472b8fc73854be8dd0375c9a487474b/uv-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a4fc62749bda8e7ae62212b1d85cdf6c7bad41918b3c8ac5a6d730dd093d793d", size = 14205195 }, - { url = "https://files.pythonhosted.org/packages/fe/67/fba55047c34ceae31cf92f6286a8517749d8c86a2151620fccb4dfb01cba/uv-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:78c3c040e52c09a410b9788656d6e760d557f223058537081cb03a3e25ce89de", size = 13178700 }, - { url = "https://files.pythonhosted.org/packages/5c/af/476c4d3486690e3cd6a9d1e040e350aefcd374b6adf919228594c9e0d9d2/uv-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:76b514c79136e779cccf90cce5d60f317a0d42074e9f4c059f198ef435f2f6ab", size = 13438725 }, - { url = "https://files.pythonhosted.org/packages/a0/18/ab89b12e695e069f6a181f66fd22dfa66b3bb5b7508938a4d4a3bff6d214/uv-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a45648db157d2aaff859fe71ec738efea09b972b8864feb2fd61ef856a15b24f", size = 13987146 }, - { url = "https://files.pythonhosted.org/packages/60/72/0eedd9b4d25657124ee5715ec08a0b278716905dd4c2a79b2af5e742c421/uv-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1e7b5bcc8b380e333e948c01f6f4c6203067b5de60a05f8ed786332af7a9132", size = 14513180 }, - { url = "https://files.pythonhosted.org/packages/9c/b3/feef463577bb31f692b2e52fdce76865d297fe1a4ae48d2bad855b255a67/uv-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:737a06b15c4e6b8ab7dd0a577ba766380bda4c18ba4ecfcfff37d336f1b03a00", size = 15216614 }, - { url = "https://files.pythonhosted.org/packages/99/dd/90e3360402610e1f687fc52c1c0b12906530986c7fe87d63414e0b8ac045/uv-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba25eb99891b95b5200d5e369b788d443fae370b097e7268a71e9ba753f2af3f", size = 15005351 }, - { url = "https://files.pythonhosted.org/packages/f2/c5/1fd7eafa61d2659ab4b27314e01eaa2cd62acb0f3a8bceb6420d38f3137f/uv-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:747c011da9f631354a1c89b62b19b8572e040d3fe01c6fb8d650facc7a09fdbb", size = 19537320 }, - { url = "https://files.pythonhosted.org/packages/12/77/36eb833476111af75ecc624d103662aba650b2b3c47abf4df5917697a5b1/uv-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a141b40444c4184efba9fdc10abb3c1cff32154c7f8b0ad46ddc180d65a82d90", size = 14678070 }, - { url = "https://files.pythonhosted.org/packages/a9/c6/7a70672f383ec639d178e0b1481048f181c05bbe372f23a66853a02e0346/uv-0.5.7-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:46b03a9a78438219fb3060c096773284e2f22417a9c1f8fdd602f0650b3355c2", size = 13637987 }, - { url = "https://files.pythonhosted.org/packages/98/d1/a7c80c0a582344cf63ad17c8c344c9194a2f4475f6b522adbdb3b8cb6ac6/uv-0.5.7-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:13961a8116515eb288c4f91849fba11ebda0dfeec44cc356e388b3b03b2dbbe1", size = 13974519 }, - { url = "https://files.pythonhosted.org/packages/84/23/55ef8f1fdd750aa1a123dac92bac249cbf8268bd9ab5b63b33580cd4dc23/uv-0.5.7-py3-none-musllinux_1_1_i686.whl", hash = "sha256:071b57c934bdee8d7502a70e9ea0739a10e9b2d1d0c67e923a09e7a23d9a181b", size = 14241488 }, - { url = "https://files.pythonhosted.org/packages/e8/42/0cb96aa85849e55f3dcf4080fec1c13e75eb6179cbff630e4ded22b455f6/uv-0.5.7-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:1c5b89c64fb627f52f1e9c9bbc4dcc7bae29c4c5ab8eff46da3c966bbd4caed2", size = 16082215 }, - { url = "https://files.pythonhosted.org/packages/c5/d0/51e588ef932160f113a379781b7edf781d2a7e4667ff4a26b1f3146df359/uv-0.5.7-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b79e32438390add793bebc41b0729054e375be30bc53f124ee212d9c97affc39", size = 14809685 }, - { url = "https://files.pythonhosted.org/packages/cc/2b/5cc8622473e61b252211811ee6cb0471ac060dc4a36391747217a717a19a/uv-0.5.7-py3-none-win32.whl", hash = "sha256:d0600d2b2fbd9a9446bfbb7f03d88bc3d0293b949ce40e326429dd4fe246c926", size = 14074020 }, - { url = "https://files.pythonhosted.org/packages/e1/e0/2ce3eb10fab05d900b3434dce09f59f5ac0689e52ca4979e3bfd32e71b61/uv-0.5.7-py3-none-win_amd64.whl", hash = "sha256:27c630780e1856a70fbeb267e1ed6835268a1b50963ab9a984fafa4184389def", size = 15842701 }, +version = "0.5.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/51/9e1ee77f4af6b855c23a15d5ef87b3770690163e55c7a4fcaf3566110b9b/uv-0.5.9.tar.gz", hash = "sha256:6ed5ef05fc98cf6129c1c674c2b5e8a99eff1ae1eebd47ab60dde010a307784a", size = 2509665 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/aaded081f751bbd015af31162ee3bb732495043ce73aff206fedc5b5e4c3/uv-0.5.9-py3-none-linux_armv6l.whl", hash = "sha256:db6bae19df1dff43b890032fd9025983c38ac418beda19b02e043630428a19d8", size = 14432886 }, + { url = "https://files.pythonhosted.org/packages/b2/f2/51213a873e55d11ae17789ee30613a78c0c1869111d4f7b898d655ad815a/uv-0.5.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d8e384e2961f4ed5b21928f3f8ed7d7f5c8eb4607cbab84610958500360e5723", size = 14401386 }, + { url = "https://files.pythonhosted.org/packages/3f/d4/01cb414332506f701df82743595118e0f7a2ca4745890cdb081120d86738/uv-0.5.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1e93705005f4f74a768085908acee92d4ef5d816ffb51e9a6596ace6b7d0a3ad", size = 13336504 }, + { url = "https://files.pythonhosted.org/packages/70/96/b171921013aedcc5d8bff064e4d71a50108d0529033cfe4114b70473f367/uv-0.5.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:405b8e0f26ad33eb35b138160e9d5752e39bf91e832df0235ce6992263a60572", size = 13654621 }, + { url = "https://files.pythonhosted.org/packages/13/54/6b8e0b08e5815c50d01041be336b52ee8c66ca3129d53139d9734d4085be/uv-0.5.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0834cdb59ca23d42296092c55c1dd1157b18300234f32a82320a78645766ad5", size = 14213755 }, + { url = "https://files.pythonhosted.org/packages/49/60/0ddf97ae75d867c635b9bc0ade7c6b74db7daf1cddb136f78fce3ad944b0/uv-0.5.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45e59c8895a3ae0d7b09c913288749c82892d04d9d67a2baa96c24eb6d13e6cb", size = 14890057 }, + { url = "https://files.pythonhosted.org/packages/da/ac/147237407343d2099cc28a9dc4497c984888fdea1b53465278708790236a/uv-0.5.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f8fd3faec0b0e7a18491bb87ccfce3389e1137ef54f356a4f7723d7f10f37dcf", size = 15460102 }, + { url = "https://files.pythonhosted.org/packages/a0/07/545509281a673b2b3edc1402f5ea14aac10712f5c6f471f9eed19cf2a6ea/uv-0.5.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4db031dba3b948a76a1ce2599bf64db8cb0c3a05de9c0c717fba0ce5c346997c", size = 15313565 }, + { url = "https://files.pythonhosted.org/packages/9e/c0/94cf569f7a266d838eb85147c02dcee2f8ad049fb129e2a9538fb09f4cee/uv-0.5.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3fbaff3161694b5b349fc7286ceac343121a755c62a32b871c3ad5f781dca23c", size = 19812476 }, + { url = "https://files.pythonhosted.org/packages/6b/6b/d1472c76c744e8c4884b194176344cbeb278021cda6a9b57dc47eed7c02a/uv-0.5.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4640eba930cd492f74b75ed6eee4754048ea01371621762e4b7318b9d33d2ab", size = 14964881 }, + { url = "https://files.pythonhosted.org/packages/b2/5c/93e03417f196005e659a9bf55bf46f201bd4bcda353ff5ec87c0c2e110fa/uv-0.5.9-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:338c517bb47cd686e104ac63d3ad617f37cf4309eb582b39e6826f071f5e7916", size = 13865832 }, + { url = "https://files.pythonhosted.org/packages/39/75/05c23f97346e06d1f7bf42560a41cb142ae800dba340afe15c555d84a213/uv-0.5.9-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:5925399f92df8b1d030aca5009f3e889446de5bd2477c869627572de3ed1a701", size = 14202990 }, + { url = "https://files.pythonhosted.org/packages/cb/1f/086a7fe808faff60395c51d2f9f83f91b652f2d3b9a998c9e50204cfa843/uv-0.5.9-py3-none-musllinux_1_1_i686.whl", hash = "sha256:429f4b27d35aa23b07b493f360e9bbaf281c8ef179ad3fb97c58ad118fbe29c5", size = 14533968 }, + { url = "https://files.pythonhosted.org/packages/88/e4/4aeb7c0e03b8315a918fb35ee701a8c21b8c42296772739ec149419c76cb/uv-0.5.9-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:ee37165a9b03d7cd07dab8272e5cd5c36da30a54b28f08f301a0ee67526e9006", size = 15096613 }, + { url = "https://files.pythonhosted.org/packages/3a/6a/5eb8ef69490c59bc3f62c4e4a1063c323794b440479d3501577dc949aea7/uv-0.5.9-py3-none-win32.whl", hash = "sha256:f7046bc8a7b4c2fba22908025c59121c0ce2ce5bd9a45f1ed76023ff3eba06e5", size = 14435392 }, + { url = "https://files.pythonhosted.org/packages/f4/30/e260f5bdeed81ff0e9ac5f3fa326234ac98eb455830504febd9d440f20b8/uv-0.5.9-py3-none-win_amd64.whl", hash = "sha256:18ec1248a6d027f89d9ffb51079227373f3af42ca0d3f684047aec81793657d6", size = 16212276 }, ] [[package]]