From 4b3cd8eb75990f00f6592c9c68e92972b88daf44 Mon Sep 17 00:00:00 2001 From: colaftc Date: Fri, 28 Nov 2025 18:25:06 +0800 Subject: [PATCH] feat: purchase_order approve and calcel --- api_v1/tests.py | 53 +++- api_v1/urls.py | 1 + api_v1/views/purchase_order.py | 64 ++++- business/admin.py | 16 +- .../0008_alter_purchaseorderitem_quantity.py | 18 ++ .../migrations/0009_purchaseorderitem_spec.py | 18 ++ business/models.py | 13 +- business/services.py | 149 ++++++++++- business/tests.py | 105 ++++++-- docs/business_purchase.md | 66 ++++- docs/purchase_order_approval_and_red_flush.md | 86 ++++++ flower/utils.py | 227 ++++++++++++++++ pyproject.toml | 1 + uv.lock | 245 ++++++++++++++++++ 14 files changed, 1019 insertions(+), 43 deletions(-) create mode 100644 business/migrations/0008_alter_purchaseorderitem_quantity.py create mode 100644 business/migrations/0009_purchaseorderitem_spec.py create mode 100644 docs/purchase_order_approval_and_red_flush.md create mode 100644 flower/utils.py diff --git a/api_v1/tests.py b/api_v1/tests.py index 23dbdc8..c16f052 100644 --- a/api_v1/tests.py +++ b/api_v1/tests.py @@ -1,3 +1,4 @@ +import copy import shutil import tempfile from pathlib import Path @@ -19,6 +20,8 @@ from basic_info.models import ( WareHouse, WareHouseModeEnum, ) +from business import models as business_models +from stock import models as stock_models from api_v1 import tasks @@ -197,13 +200,22 @@ class PurchaseOrderAPITestCase(TestCase): ], } + def _create_purchase_order(self, payload): + body = copy.deepcopy(payload) + with patch('business.services.create_purchase_order_stock_entries.delay'): + response = self.client.post('/api/v1/purchase-orders/', body, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + return response.data['id'] + def test_create_purchase_order_success_strict(self): with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay: response = self.client.post('/api/v1/purchase-orders/', self.strict_payload, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertIn('id', response.data) - mock_delay.assert_called_once() + self.assertEqual(response.data['status'], 1) + self.assertIn('等待审批', response.data['message']) + mock_delay.assert_not_called() def test_create_purchase_order_invalid_supplier(self): payload = {**self.strict_payload, 'supplier': 999} @@ -220,7 +232,8 @@ class PurchaseOrderAPITestCase(TestCase): with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay: response = self.client.post('/api/v1/purchase-orders/', self.relaxed_payload, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) - mock_delay.assert_called_once() + self.assertEqual(response.data['status'], 1) + mock_delay.assert_not_called() def test_mode_mismatch_raises(self): payload = {**self.relaxed_payload} @@ -229,6 +242,42 @@ class PurchaseOrderAPITestCase(TestCase): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn('严进模式', response.data['error']) + def test_review_purchase_order_requires_action(self): + order_id = self._create_purchase_order(self.strict_payload) + response = self.client.post(f'/api/v1/purchase-orders/{order_id}/review/', {}, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn('action', response.data) + + def test_review_purchase_order_approve_success(self): + order_id = self._create_purchase_order(self.strict_payload) + response = self.client.post( + f'/api/v1/purchase-orders/{order_id}/review/', + {'action': 'approve'}, + format='json', + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['status'], business_models.PurchaseOrderStatusEnum.APPROVED) + order = business_models.PurchaseOrder.objects.get(id=order_id) + self.assertEqual(order.status, business_models.PurchaseOrderStatusEnum.APPROVED) + + def test_review_purchase_order_cancel_blocked_after_stock_exists(self): + order_id = self._create_purchase_order(self.relaxed_payload) + stock_models.StockChangeRecord.objects.create( + merchant=self.merchant, + type=stock_models.StockChangeTypeEnum.ADD, + warehouse=self.warehouse_relaxed, + source_type=stock_models.StockChangeSourceEnum.PURCHASE, + source_id=order_id, + ) + response = self.client.post( + f'/api/v1/purchase-orders/{order_id}/review/', + {'action': 'cancel'}, + format='json', + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + order = business_models.PurchaseOrder.objects.get(id=order_id) + self.assertEqual(order.status, business_models.PurchaseOrderStatusEnum.PENDING) + @override_settings( CELERY_TASK_ALWAYS_EAGER=True, diff --git a/api_v1/urls.py b/api_v1/urls.py index 3810f90..b7b76f4 100644 --- a/api_v1/urls.py +++ b/api_v1/urls.py @@ -57,6 +57,7 @@ urlpatterns = [ # 库存查询 API path('inventory/', inventory.InventoryAPIView.as_view(), name='inventory'), path('purchase-orders/', purchase_order.PurchaseOrderView.as_view(), name='purchase_orders'), + path('purchase-orders//review/', purchase_order.PurchaseOrderReviewView.as_view(), name='purchase_order_review'), path('health/', healthy.HealthCheckView.as_view(), name='health_check'), path('print-count/delta/', print_count.adjust_print_count, name='print_count_delta'), diff --git a/api_v1/views/purchase_order.py b/api_v1/views/purchase_order.py index 2d8fefa..2add73b 100644 --- a/api_v1/views/purchase_order.py +++ b/api_v1/views/purchase_order.py @@ -1,7 +1,6 @@ from rest_framework import status, views, serializers, pagination from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response - from basic_info import models as basic_models from business import services as business_services from business import models as business_models @@ -14,12 +13,23 @@ class PurchaseOrderItemSerializer(serializers.ModelSerializer): fields = [ 'id', 'product', 'price', 'color', 'quantity', 'unit', 'empty_diff_percent', 'quantity_of_rolls', 'num_of_rolls', - 'batch_number', 'remarks', 'created_at', 'updated_at', + 'batch_number', 'remarks', 'created_at', 'updated_at', 'spec', ] - read_only_fields = ['id', 'created_at', 'updated_at'] + read_only_fields = ['id', 'created_at', 'updated_at', 'total_amount', 'diff_quantity', 'real_quantity'] class PurchaseOrderSerializer(serializers.ModelSerializer): + total_amount = serializers.SerializerMethodField(read_only=True) + diff_quantity = serializers.SerializerMethodField(read_only=True) + total_quantity = serializers.SerializerMethodField(read_only=True) + + def get_total_amount(self, obj: business_models.PurchaseOrder): + return obj.get_total_amount() + def get_diff_quantity(self, obj: business_models.PurchaseOrder): + return obj.get_total_diff_quantity() + def get_total_quantity(self, obj: business_models.PurchaseOrder): + return obj.get_total_quantity() + supplier_name = serializers.CharField(source='supplier.name', read_only=True) operator_name = serializers.CharField(source='operator.name', read_only=True) warehouse_name = serializers.CharField(source='warehouse.name', read_only=True) @@ -29,6 +39,7 @@ class PurchaseOrderSerializer(serializers.ModelSerializer): model = business_models.PurchaseOrder fields = [ 'id', 'supplier', 'supplier_name', 'purchase_date', 'kind', + 'total_amount', 'diff_quantity', 'total_quantity', 'operator', 'operator_name', 'warehouse', 'warehouse_name', 'status', 'remarks', 'created_at', 'updated_at', 'items', ] @@ -108,8 +119,53 @@ class PurchaseOrderView(StockChangeViewMixin, views.APIView): return Response( { 'id': purchase_order.id, - 'message': '采购单创建成功,入库任务已排队', + 'status': purchase_order.status, + 'message': '采购单创建成功,等待审批', }, status=status.HTTP_201_CREATED, ) + +class PurchaseOrderReviewSerializer(serializers.Serializer): + action = serializers.ChoiceField(choices=[('approve', '审批通过'), ('cancel', '作废')]) + + +class PurchaseOrderReviewView(StockChangeViewMixin, views.APIView): + """采购单审批 / 作废""" + + permission_classes = [IsAuthenticated] + ACTION_STATUS_MAP = { + 'approve': business_models.PurchaseOrderStatusEnum.APPROVED, + 'cancel': business_models.PurchaseOrderStatusEnum.CANCELLED, + } + + def post(self, request, pk: int): + if not self.check_employee_permission(request): + return self.permission_error_response('无权限访问') + + merchant = request.user.employee.merchant + try: + purchase_order = business_models.PurchaseOrder.objects.select_related( + 'supplier', 'operator', 'warehouse' + ).prefetch_related('items').get(id=pk, merchant=merchant) + except business_models.PurchaseOrder.DoesNotExist: + return self.not_found_response('采购单不存在') + + serializer = PurchaseOrderReviewSerializer(data=request.data or {}) + serializer.is_valid(raise_exception=True) + action = serializer.validated_data['action'] + target_status = self.ACTION_STATUS_MAP[action] + + try: + business_services.review_purchase_order( + purchase_order=purchase_order, + target_status=target_status, + reviewed_by=request.user, + ) + except ValueError as exc: + return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST) + + refreshed_order = business_models.PurchaseOrder.objects.select_related( + 'supplier', 'operator', 'warehouse' + ).prefetch_related('items').get(id=purchase_order.id) + return Response(PurchaseOrderSerializer(refreshed_order).data, status=status.HTTP_200_OK) diff --git a/business/admin.py b/business/admin.py index faeb743..2f4e0fa 100644 --- a/business/admin.py +++ b/business/admin.py @@ -5,11 +5,23 @@ from . import models @admin.register(models.PurchaseOrder) class PurchaseOrderAdmin(admin.ModelAdmin): - list_display = ('id', 'supplier', 'purchase_date', 'operator', 'created_at') + list_display = ('id', 'supplier', 'purchase_date', 'operator', 'status', '_total_amount', '_diff_quantity', '_total_quantity', 'created_at') search_fields = ('supplier__name',) - list_filter = ('operator', 'warehouse', 'created_at') + list_filter = ('operator', 'warehouse', 'status', 'created_at') ordering = ('-created_at',) + @admin.display(description='总价') + def _total_amount(self, obj: models.PurchaseOrder): + return obj.get_total_amount() + + @admin.display(description='差异数量') + def _diff_quantity(self, obj: models.PurchaseOrder): + return obj.get_total_diff_quantity() + + @admin.display(description='总数量') + def _total_quantity(self, obj: models.PurchaseOrder): + return obj.get_total_quantity() + @admin.register(models.PurchaseOrderItem) class PurchaseOrderItemAdmin(admin.ModelAdmin): diff --git a/business/migrations/0008_alter_purchaseorderitem_quantity.py b/business/migrations/0008_alter_purchaseorderitem_quantity.py new file mode 100644 index 0000000..48a6e5c --- /dev/null +++ b/business/migrations/0008_alter_purchaseorderitem_quantity.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-11-28 09:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('business', '0007_alter_purchaseorderitem_batch_number'), + ] + + operations = [ + migrations.AlterField( + model_name='purchaseorderitem', + name='quantity', + field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='数量'), + ), + ] diff --git a/business/migrations/0009_purchaseorderitem_spec.py b/business/migrations/0009_purchaseorderitem_spec.py new file mode 100644 index 0000000..9e80520 --- /dev/null +++ b/business/migrations/0009_purchaseorderitem_spec.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-11-28 09:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('business', '0008_alter_purchaseorderitem_quantity'), + ] + + operations = [ + migrations.AddField( + model_name='purchaseorderitem', + name='spec', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='规格'), + ), + ] diff --git a/business/models.py b/business/models.py index ce9fd89..d08896e 100644 --- a/business/models.py +++ b/business/models.py @@ -1,3 +1,4 @@ +from decimal import Decimal from django.db import models from flower.common import ModelBase from basic_info import models as basic_info_models @@ -64,6 +65,15 @@ class PurchaseOrder(ModelBase): verbose_name = '采购单' verbose_name_plural = '采购单' + def get_total_amount(self) -> Decimal: + return sum(item.total_amount() for item in self.items.all()) + + def get_total_quantity(self) -> Decimal: + return sum(item.quantity for item in self.items.all()) + + def get_total_diff_quantity(self) -> Decimal: + return sum(item.diff_quantity() for item in self.items.all()) + class PurchaseOrderItem(ModelBase): """采购单明细模型""" @@ -83,8 +93,9 @@ class PurchaseOrderItem(ModelBase): ) price = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='单价') color = models.CharField(max_length=50, null=True, blank=True, verbose_name='颜色') - quantity = models.PositiveIntegerField(verbose_name='数量') + quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='数量') unit = models.CharField(max_length=50, verbose_name='单位') + spec = models.CharField(max_length=100, null=True, blank=True, verbose_name='规格') empty_diff_percent = models.DecimalField( max_digits=15, decimal_places=2, diff --git a/business/services.py b/business/services.py index 993ebcb..428a4d7 100644 --- a/business/services.py +++ b/business/services.py @@ -84,6 +84,7 @@ def create_purchase_order( color=item_data.get('color'), quantity=item_data['quantity'], unit=item_data['unit'], + spec=item_data.get('spec'), empty_diff_percent=item_data['empty_diff_percent'], quantity_of_rolls=item_data.get('quantity_of_rolls'), num_of_rolls=item_data['num_of_rolls'], @@ -94,18 +95,42 @@ def create_purchase_order( ] models.PurchaseOrderItem.objects.bulk_create(bulk_objects) - created_by_id = getattr(created_by, 'id', None) - if MerchantSettingService.get_setting(merchant, basic_info_models.MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS).value is True: - logger.info('自动创建出入库记录任务已开启,创建入库记录任务') - create_purchase_order_stock_entries.delay( - purchase_order_id=purchase_order.id, - warehouse_id=warehouse.id, - items=stock_flow_items, - created_by_id=created_by_id, - ) + purchase_order.refresh_from_db() return purchase_order +def review_purchase_order( + *, + purchase_order: models.PurchaseOrder | None = None, + purchase_order_id: int | None = None, + target_status: models.PurchaseOrderStatusEnum, + reviewed_by=None, +) -> models.PurchaseOrder: + """ + 审批或作废采购单。 + + 当目标状态为 APPROVED 且开启自动入库任务时,将触发入库 Celery 任务; + 当目标状态为 CANCELLED 时,会在更新状态前确认未生成任何出入库单。 + """ + order = _resolve_purchase_order_instance(purchase_order, purchase_order_id) + + if target_status not in { + models.PurchaseOrderStatusEnum.APPROVED, + models.PurchaseOrderStatusEnum.CANCELLED, + }: + raise ValueError('target_status 只能是 APPROVED 或 CANCELLED') + + if order.status == target_status: + return order + + if target_status == models.PurchaseOrderStatusEnum.APPROVED: + if order.status == models.PurchaseOrderStatusEnum.CANCELLED: + raise ValueError('作废状态的采购单无法再次审批') + return _approve_purchase_order(order, reviewed_by) + + return _cancel_purchase_order(order) + + def _normalize_purchase_items( *, merchant: basic_info_models.Merchant, @@ -136,6 +161,7 @@ def _normalize_purchase_items( color = raw_item.get('color') batch_number = raw_item.get('batch_number') remarks = raw_item.get('remarks') + spec = raw_item.get('spec') unit = raw_item.get('unit') or product.get_unit_display() or '米' if warehouse_mode == basic_info_models.WareHouseModeEnum.UNRESTRICTED: @@ -176,11 +202,48 @@ def _normalize_purchase_items( 'num_of_rolls': num_of_rolls, 'batch_number': batch_number, 'remarks': remarks, + 'spec': spec, }) return purchase_items, stock_flow_items +def _approve_purchase_order( + purchase_order: models.PurchaseOrder, + reviewed_by, +) -> models.PurchaseOrder: + stock_flow_items = _build_stock_flow_items_from_order(purchase_order) + + with transaction.atomic(): + purchase_order.status = models.PurchaseOrderStatusEnum.APPROVED + purchase_order.save(update_fields=['status', 'updated_at']) + + created_by_id = getattr(reviewed_by, 'id', None) + if _auto_stock_task_enabled(purchase_order.merchant): + logger.info('审批通过采购单 %s,触发入库任务', purchase_order.id) + create_purchase_order_stock_entries.delay( + purchase_order_id=purchase_order.id, + warehouse_id=purchase_order.warehouse_id, + items=stock_flow_items, + created_by_id=created_by_id, + ) + + purchase_order.refresh_from_db(fields=['status', 'updated_at']) + return purchase_order + + +def _cancel_purchase_order(purchase_order: models.PurchaseOrder) -> models.PurchaseOrder: + if _purchase_order_has_stock_records(purchase_order): + raise ValueError('采购单已生成出入库记录,无法作废') + + with transaction.atomic(): + purchase_order.status = models.PurchaseOrderStatusEnum.CANCELLED + purchase_order.save(update_fields=['status', 'updated_at']) + + purchase_order.refresh_from_db(fields=['status', 'updated_at']) + return purchase_order + + def create_purchase_order_stock_entries_sync( *, purchase_order_id: int, @@ -221,6 +284,74 @@ def create_purchase_order_stock_entries_sync( return payload +def _build_stock_flow_items_from_order(purchase_order: models.PurchaseOrder) -> List[Dict[str, Any]]: + """ + 根据采购单明细还原 StockFlowService 所需的 items 结构。 + """ + warehouse_mode = purchase_order.warehouse.mode + items_payload: List[Dict[str, Any]] = [] + + order_items = purchase_order.items.all() + if not order_items: + raise ValueError('采购单没有任何明细,无法生成入库记录') + + if warehouse_mode == basic_info_models.WareHouseModeEnum.UNRESTRICTED: + for item in order_items: + items_payload.append({ + 'product_id': item.product_id, + 'value': str(item.quantity), + 'num_of_rolls': item.num_of_rolls, + }) + return items_payload + + # 严进 / 严进严出模式 + for item in order_items: + raw_numbers = (item.quantity_of_rolls or '').split(',') + normalized = [value.strip() for value in raw_numbers if value.strip()] + if not normalized: + raise ValueError('严进仓采购单缺少 numbers 数据,无法生成入库记录') + items_payload.append({ + 'product_id': item.product_id, + 'quantities': normalized, + }) + return items_payload + + +def _auto_stock_task_enabled(merchant: basic_info_models.Merchant) -> bool: + try: + setting = MerchantSettingService.get_setting( + merchant, + basic_info_models.MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS, + ) + except basic_info_models.MerchantSetting.DoesNotExist: + logger.warning('商户 %s 未配置 auto_create_stock_change_tasks,默认关闭', merchant.id) + return False + return setting.value is True + + +def _purchase_order_has_stock_records(purchase_order: models.PurchaseOrder) -> bool: + return stock_models.StockChangeRecord.objects.filter( + merchant_id=purchase_order.merchant_id, + source_type=stock_models.StockChangeSourceEnum.PURCHASE, + source_id=purchase_order.id, + ).exists() + + +def _resolve_purchase_order_instance( + purchase_order: models.PurchaseOrder | None, + purchase_order_id: int | None, +) -> models.PurchaseOrder: + if purchase_order is None and purchase_order_id is None: + raise ValueError('必须提供 purchase_order 或 purchase_order_id') + + if purchase_order is not None: + purchase_order_id = purchase_order.id + + return models.PurchaseOrder.objects.select_related('merchant', 'warehouse').prefetch_related('items').get( + id=purchase_order_id + ) + + def _to_decimal(value, field_name: str) -> Decimal: try: return Decimal(str(value)) diff --git a/business/tests.py b/business/tests.py index 4a92723..15bc0a6 100644 --- a/business/tests.py +++ b/business/tests.py @@ -15,6 +15,12 @@ def create_basic_fixtures(): name='测试商户', type=basic_models.MerchantTypeEnum.FACTORY, ) + basic_models.MerchantSetting.objects.create( + merchant=merchant, + key=basic_models.MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS, + type=basic_models.MerchantSettingTypeEnum.BOOL, + val_bool=True, + ) supplier = basic_models.Supplier.objects.create( merchant=merchant, name='测试供应商', @@ -68,7 +74,7 @@ class PurchaseOrderServiceTestCase(TestCase): {'product_id': self.product.id, 'quantity': 120, 'num_of_rolls': 3, 'price': '10.0', 'unit': '米'} ] - def test_create_purchase_order_triggers_task_strict(self): + def test_create_purchase_order_does_not_trigger_task_immediately(self): with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay: purchase_order = services.create_purchase_order( merchant=self.merchant, @@ -82,11 +88,31 @@ class PurchaseOrderServiceTestCase(TestCase): ) self.assertIsInstance(purchase_order, business_models.PurchaseOrder) - self.assertEqual(purchase_order.warehouse, self.warehouse_strict) - self.assertEqual(purchase_order.operator, self.operator) + self.assertEqual(purchase_order.status, business_models.PurchaseOrderStatusEnum.PENDING) self.assertEqual(purchase_order.items.count(), 1) item = purchase_order.items.first() self.assertEqual(item.quantity_of_rolls, '10,5') + mock_delay.assert_not_called() + + def test_review_purchase_order_approval_triggers_task(self): + purchase_order = services.create_purchase_order( + merchant=self.merchant, + supplier=self.supplier, + order_date=timezone.now().date(), + warehouse=self.warehouse_strict, + operator=self.operator, + items=self.strict_items, + created_by=self.user, + ) + + with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay: + reviewed = services.review_purchase_order( + purchase_order=purchase_order, + target_status=business_models.PurchaseOrderStatusEnum.APPROVED, + reviewed_by=self.user, + ) + + self.assertEqual(reviewed.status, business_models.PurchaseOrderStatusEnum.APPROVED) mock_delay.assert_called_once_with( purchase_order_id=purchase_order.id, warehouse_id=self.warehouse_strict.id, @@ -105,23 +131,72 @@ class PurchaseOrderServiceTestCase(TestCase): items=[], ) - def test_create_purchase_order_relaxed_items(self): - with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay: - purchase_order = services.create_purchase_order( - merchant=self.merchant, - supplier=self.supplier, - order_date=timezone.now().date(), - warehouse=self.warehouse_relaxed, - operator=self.operator, - items=self.relaxed_items, - created_by=self.user, + def test_review_purchase_order_cancel_without_stock(self): + purchase_order = services.create_purchase_order( + merchant=self.merchant, + supplier=self.supplier, + order_date=timezone.now().date(), + warehouse=self.warehouse_relaxed, + operator=self.operator, + items=self.relaxed_items, + created_by=self.user, + ) + self.assertIsNone(purchase_order.items.first().quantity_of_rolls) + + cancelled = services.review_purchase_order( + purchase_order=purchase_order, + target_status=business_models.PurchaseOrderStatusEnum.CANCELLED, + reviewed_by=self.user, + ) + self.assertEqual(cancelled.status, business_models.PurchaseOrderStatusEnum.CANCELLED) + + def test_review_purchase_order_cancel_blocked_after_stock_created(self): + purchase_order = services.create_purchase_order( + merchant=self.merchant, + supplier=self.supplier, + order_date=timezone.now().date(), + warehouse=self.warehouse_relaxed, + operator=self.operator, + items=self.relaxed_items, + created_by=self.user, + ) + stock_models.StockChangeRecord.objects.create( + merchant=self.merchant, + type=stock_models.StockChangeTypeEnum.ADD, + warehouse=self.warehouse_relaxed, + source_type=stock_models.StockChangeSourceEnum.PURCHASE, + source_id=purchase_order.id, + ) + + with self.assertRaises(ValueError): + services.review_purchase_order( + purchase_order=purchase_order, + target_status=business_models.PurchaseOrderStatusEnum.CANCELLED, + reviewed_by=self.user, + ) + + def test_review_purchase_order_relaxed_mode_approval_payload(self): + purchase_order = services.create_purchase_order( + merchant=self.merchant, + supplier=self.supplier, + order_date=timezone.now().date(), + warehouse=self.warehouse_relaxed, + operator=self.operator, + items=self.relaxed_items, + created_by=self.user, + ) + + with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay: + services.review_purchase_order( + purchase_order=purchase_order, + target_status=business_models.PurchaseOrderStatusEnum.APPROVED, + reviewed_by=self.user, ) - self.assertEqual(purchase_order.items.first().quantity_of_rolls, None) mock_delay.assert_called_once_with( purchase_order_id=purchase_order.id, warehouse_id=self.warehouse_relaxed.id, - items=[{'product_id': self.product.id, 'value': '120', 'num_of_rolls': 3}], + items=[{'product_id': self.product.id, 'value': '120.00', 'num_of_rolls': 3}], created_by_id=self.user.id, ) diff --git a/docs/business_purchase.md b/docs/business_purchase.md index f15f353..017e225 100644 --- a/docs/business_purchase.md +++ b/docs/business_purchase.md @@ -1,10 +1,10 @@ # Business 模块采购单 API -## 创建采购单并触发入库 +## 创建采购单(默认待审批) - **URL**: `POST /api/v1/purchase-orders/` - **权限**: 需要登录且具备员工身份 -- **描述**: 创建业务模块的 `PurchaseOrder`,并异步触发库存入库任务(通过 `stock.services.StockFlowService.stock_in` 实现)。 +- **描述**: 创建业务模块的 `PurchaseOrder`,默认状态为 `PENDING`(审批中)。此时不会立即生成入库记录,需在审批通过后才会触发库存入库任务。 ### 请求体 @@ -53,11 +53,12 @@ ```json { "id": 35, - "message": "采购单创建成功,入库任务已排队" + "status": 1, + "message": "采购单创建成功,等待审批" } ``` -创建成功即刻返回,实际入库明细由后台 Celery 任务生成,可在日志或 `stock_change` 记录中查看。 +创建成功即刻返回,状态为 `审批中`。只有在审批通过后才会调用库存入库任务。 ### 错误示例 @@ -68,15 +69,60 @@ | 400 | `{"error": "供应商 99 不存在"}` | 供应商不属于当前商户 | | 403 | `{"error": "无权限访问"}` | 当前用户无员工信息 | +## 审批 / 作废采购单(API & Service) + +- **URL**: `POST /api/v1/purchase-orders//review/` +- **权限**: 登录 + 员工身份 +- **请求体**: + +```json +{ + "action": "approve" // 可选: approve / cancel +} +``` + +当 `action=approve` 时,会调用 `business.services.review_purchase_order` 将采购单状态更新为 `APPROVED`,并在商户设置 `auto_create_stock_change_tasks=True` 时自动触发 `create_purchase_order_stock_entries` Celery 任务(根据采购单明细重建 `StockFlowService.stock_in` 所需 payload)。 + +当 `action=cancel` 时,会尝试将采购单状态改为 `CANCELLED`。如果该采购单已经生成过出入库记录(`source_type=PURCHASE`、`source_id=采购单ID`),接口会返回 `400`,提示“采购单已生成出入库记录,无法作废”。 + +### 响应示例 + +```json +{ + "id": 12, + "status": 2, + "supplier_name": "供应商A", + "warehouse_name": "严进仓", + "total_amount": "1500.00", + "diff_quantity": "0.00", + "total_quantity": "120.00", + "items": [ + { + "id": 33, + "product": 10, + "quantity": "120.00", + "unit": "米", + "spec": "50D", + "num_of_rolls": 3 + } + ] +} +``` + +### 错误示例 + +| 状态码 | 示例 | 说明 | +|--------|------|------| +| 400 | `{"action": ["This field is required."]}` | 缺少 `action` | +| 400 | `{"error": "采购单已生成出入库记录,无法作废"}` | 尝试作废但已生成出入库单 | +| 404 | `{"error": "采购单不存在"}` | ID 不在当前商户下 | + ### 关联任务(business/tasks.py) -`create_purchase_order_stock_entries` 任务会接收 `purchase_order_id`、`warehouse_id`、已转换好的 `items` 信息,并通过 `StockFlowService.stock_in` 创建入库记录。 - -- 任务日志示例:`采购单 35 入库任务完成` -- 返回 payload 包含 `stock_change_record_id`、`created_details_count` +`create_purchase_order_stock_entries` 任务会接收 `purchase_order_id`、`warehouse_id`、已转换好的 `items` 信息,并通过 `StockFlowService.stock_in` 创建入库记录。只有当采购单审批通过且商户开启自动入库设置时才会派发该任务。 ### 测试 -`api_v1/tests.py` 中的 `PurchaseOrderAPITestCase` 覆盖宽进/严进模式、模式不匹配和未登录场景; -`business/tests.py` 中的 `PurchaseOrderServiceTestCase`、`PurchaseOrderStockTaskTestCase` 验证 service 层逻辑与 Celery 任务(通过 mock `StockFlowService`)。 +- `business/tests.PurchaseOrderServiceTestCase`:覆盖创建采购单、审批触发任务、作废校验以及宽/严模式的 payload 生成逻辑。 +- `business/tests.PurchaseOrderStockServiceTestCase` 与 `PurchaseOrderStockTaskTestCase`:继续验证 service 层生成入库单及 Celery 任务封装逻辑。 diff --git a/docs/purchase_order_approval_and_red_flush.md b/docs/purchase_order_approval_and_red_flush.md new file mode 100644 index 0000000..ce2912d --- /dev/null +++ b/docs/purchase_order_approval_and_red_flush.md @@ -0,0 +1,86 @@ +# 采购单审批流程与红冲规划 + +## 1. 背景 + +- 采购单在创建时默认进入 `PENDING`(审批中)状态,不再立即创建出入库记录。 +- `business.services.review_purchase_order` 统一处理审批通过 (`APPROVED`) 与作废 (`CANCELLED`) 的业务规则。 +- `api_v1/views/purchase_order.py` 暴露 `POST /api/v1/purchase-orders//review/` 接口作为唯一入口,便于前端、自动化流程和第三方系统统一调用。 + +## 2. 审批流程总览 + +| 步骤 | 说明 | +|------|------| +| 1 | 前端在采购单详情页或审批面板触发 `POST /api/v1/purchase-orders//review/`,请求体只包含 `{"action": "approve"}` 或 `{"action": "cancel"}` | +| 2 | API 层验证:当前用户必须具备员工身份、采购单必须属于当前商户;`action` 字段必须是 `approve/cancel` 之一 | +| 3 | API 调用 `business.services.review_purchase_order`,根据 `action` 映射到 `PurchaseOrderStatusEnum.APPROVED` 或 `CANCELLED` | +| 4 | Service 内部基于状态决定调用 `_approve_purchase_order` 或 `_cancel_purchase_order`,并在必要时抛出业务错误(例如重复审批、作废时已有出入库记录等) | +| 5 | API 返回最新的 `PurchaseOrderSerializer` 数据(包含总金额、总数量、明细与状态等)供前端刷新页面 | + +## 3. 审批通过触发的事务 + +1. `_approve_purchase_order` 先构建 `stock_flow_items`: + - 宽进仓:`items=[{'product_id', 'value', 'num_of_rolls'}]` + - 严进/严进严出:`items=[{'product_id', 'quantities': [...] }]` +2. 在数据库事务中,将 `purchase_order.status` 置为 `APPROVED` 并保存,保证状态更新原子性。 +3. 读取商户设置 `auto_create_stock_change_tasks`: + - 若未配置或为 `False`,审批仅改变状态,不会触发后续任务。 + - 若为 `True`,投递 `business.tasks.create_purchase_order_stock_entries` Celery 任务。 +4. Celery 任务执行 `StockFlowService.stock_in`: + - 依据仓库模式创建入库 `StockChangeRecord` 及 `StockChangeDetail`。 + - 后续在 `stock.services.make_stock_change_completed` 中写入 `Inventory` 并生成 `StockSnapshot`。 +5. 任务返回 `stock_change_record_id`、明细数量等信息,并写日志用于追踪审计。 + +## 4. 作废流程约束 + +1. 同样通过 `POST /api/v1/purchase-orders//review/`,但 `{"action": "cancel"}`。 +2. Service 端调用 `_cancel_purchase_order`,首先查询是否存在 `source_type = PURCHASE` 且 `source_id = 采购单ID` 的 `StockChangeRecord`。 +3. 若已存在入库记录,直接抛出错误“采购单已生成出入库记录,无法作废”;API 返回 `400` 并提示原因。 +4. 若未生成记录,则在事务内将状态更新为 `CANCELLED`。不会触发 Celery 任务。 + +## 5. 常见异常与返回 + +- 缺少 `action` 字段:`400` + DRF 序列化错误。 +- 审批已是目标状态:直接返回现状(幂等)。 +- 已作废的单据请求审批:`ValueError('作废状态的采购单无法再次审批')` -> API 返回 `400`。 +- 作废时已有出入库记录:`ValueError('采购单已生成出入库记录,无法作废')` -> API 返回 `400`。 +- ID 不存在或不属于当前商户:API 返回 `404`。 + +## 6. 下一阶段:库存红冲(对冲)方案规划 + +### 6.1 目标与原则 + +- **目标**:允许在采购单及其衍生的出入库记录完成后,通过“红冲”方式撤销或抵消错误的库存变动,同时保持库存快照与主库存的可追溯性。 +- **原则**: + - 采用“新增反向记录”方式对冲,而非直接修改原记录;保留每一次实际发生的库存变更。 + - 红冲记录需要指向原 `StockChangeRecord` / `StockSnapshot`(如 `offset_to`、`offset_id` 字段),便于审计。 + - 红冲动作必须与业务流程挂钩(例如采购单作废或红冲),避免孤立的库存操作。 + +### 6.2 行动步骤 + +1. **业务入口确定** + - 定义哪些业务对象可以触发红冲(采购单、销售单等)。 + - 明确触发条件:如采购单审批通过后,允许“创建红冲请求”并指向原采购单。 + +2. **库存服务层改造** + - 在 `stock.services.StockFlowService` 或相关函数中增加创建红冲记录的能力: + - 根据原 `StockChangeRecord` 生成反向 `StockChangeRecord` 和明细。 + - 更新 `StockSnapshot` 的 `offset_to` / `offset_id` / `cancelled` 字段,确保链路闭环。 + - 确保 `Inventory` 调整遵循相同的原子逻辑(事务 + 快照)。 + +3. **业务服务与模型扩展** + - 在 `business.services` 中引入“红冲采购单”或“撤销审批”的新方法: + - 校验:原单是否允许红冲、是否存在未处理的红冲记录。 + - 调用库存红冲服务并记录关联。 + - 必要时在 `PurchaseOrder` 或新模型中记录红冲状态/引用。 + +4. **API 与文档更新** + - 设计新的红冲 API(例如 `/api/v1/purchase-orders//offset/`),请求体注明原因、操作者。 + - 文档中要说明红冲流程与限制(只能对已审批单据、需管理员权限等)。 + +5. **测试与审计** + - 单元测试:业务 Service、库存 Service、API 都要覆盖正常流程与错误场景。 + - 集成测试:验证红冲后库存数量恢复、快照关系正确、消息/通知链路是否需要补充。 + - 如有需要,增加日志或审计表,记录红冲操作人、时间、关联记录。 + +通过以上步骤,审批流程与红冲机制可以衔接:审批负责“正向入库”与任务分发,红冲负责在业务需要时生成配对的反向库存记录,保持库存账实一致,同时具备可回溯、可审计的业务闭环。 + diff --git a/flower/utils.py b/flower/utils.py new file mode 100644 index 0000000..28a1cf4 --- /dev/null +++ b/flower/utils.py @@ -0,0 +1,227 @@ +# import aiohttp +# from typing import Optional, Dict, Any, List +# from pydantic import BaseModel, Field, computed_field +# from enum import Enum +# import json + + +# class Product(BaseModel): +# """产品模型 - 请在此填入你的字段""" +# uid: str +# name: str +# unit: str | None +# color: str | None +# detail_str: str | None = Field(exclude=True) # 在序列化时排除此字段 +# created_at: str + +# @computed_field +# @property +# def detail(self) -> dict[str, Any]: +# """将 detail_str 转换为字典""" +# try: +# return json.loads(self.detail_str) if self.detail_str else {} +# except (json.JSONDecodeError, TypeError): +# return {} + + +# class ProductListResponse(BaseModel): +# """产品列表响应""" +# products: List[Product] +# total: int + + +# mdy_table_map = { +# 'product': 'spmx', +# 'customer': 'quanbu', +# } + + +# class HTTPMethod(Enum): +# """HTTP 请求方法枚举""" +# GET = "GET" +# POST = "POST" + + +# class MingDaoYunClient: +# """明道云 API 客户端""" + +# def __init__(self, app_key: str, sign: str, base_url: str = ""): +# """ +# 初始化客户端 + +# Args: +# app_key: 应用密钥 +# sign: 签名 +# base_url: API 基础地址 +# """ +# self.app_key = app_key +# self.sign = sign +# self.base_url = base_url + +# async def request( +# self, +# method: HTTPMethod, +# endpoint: str, +# data: Optional[Dict[str, Any]] = None, +# params: Optional[Dict[str, Any]] = None, +# headers: Optional[Dict[str, str]] = None, +# ) -> Any: +# """ +# 通用异步请求函数 + +# Args: +# method: 请求方法 (GET/POST) +# endpoint: API 端点路径 +# data: POST 请求体数据(JSON) +# params: URL 查询参数 +# headers: 自定义请求头 + +# Returns: +# 响应 JSON 数据 + +# Raises: +# aiohttp.ClientError: 请求失败时抛出 +# """ +# url = f"{self.base_url}{endpoint}" + +# # 构建默认请求头 +# default_headers = { +# "Content-Type": "application/json", +# } + +# if headers: +# default_headers.update(headers) + +# # 添加认证参数 +# auth_params = { +# "appKey": self.app_key, +# "sign": self.sign +# } + +# async with aiohttp.ClientSession() as session: +# if method == HTTPMethod.GET: +# async with session.get(url, params=params, json=auth_params) as response: +# response.raise_for_status() +# return await response.json() + +# elif method == HTTPMethod.POST: +# if data is None: +# data = {} + +# data.update(auth_params) +# async with session.post(url, json=data) as response: +# response.raise_for_status() +# return await response.json() + +# else: +# raise ValueError(f"Unsupported HTTP method: {method}") + +# async def get( +# self, +# endpoint: str, +# params: Optional[Dict[str, Any]] = None, +# headers: Optional[Dict[str, str]] = None, +# ) -> Any: +# """ +# 发送 GET 请求 + +# Args: +# endpoint: API 端点路径 +# params: URL 查询参数 +# headers: 自定义请求头 + +# Returns: +# 响应 JSON 数据 +# """ +# return await self.request( +# method=HTTPMethod.GET, +# endpoint=endpoint, +# params=params, +# headers=headers, +# ) + +# async def post( +# self, +# endpoint: str, +# data: Optional[Dict[str, Any]] = None, +# params: Optional[Dict[str, Any]] = None, +# headers: Optional[Dict[str, str]] = None, +# ) -> Any: +# """ +# 发送 POST 请求 + +# Args: +# endpoint: API 端点路径 +# data: POST 请求体数据(JSON) +# params: URL 查询参数 +# headers: 自定义请求头 + +# Returns: +# 响应 JSON 数据 +# """ +# return await self.request( +# method=HTTPMethod.POST, +# endpoint=endpoint, +# data=data, +# params=params, +# headers=headers, +# ) + + +# product_type_map = { +# 'name': '668caa5eb80969563ecaee7d', +# 'color': '66ed3b9ae01d5599bdb45f6d', +# 'uid': '668caa5eb80969563ecaee7c', +# 'unit': '668e2ee2ca758c8cc5d0dc1d', +# 'detail_str': '668caa5eb80969563ecaee7e', +# 'created_at': 'ctime', +# } + + +# # json param example: +# # { +# # "appKey": "208e55fea5cea59f", +# # "sign": "MWU0YmViYjkwZmM1ZDIzYzRiN2U3ZGQ4MmE4ZGNkMjc0MWM1ZmQ2ZjkwMjljODE4YmNkZTBhMzA0OTU2YzE2NA==", +# # "worksheetId": "quanbu", +# # "listType": 1, +# # "sortId": "ctime", +# # "isAsc": false, +# # "notGetTotal": true +# # } + + +# def pick_product(fields: dict) -> Product: +# """ +# 从字段字典中提取产品信息 +# """ +# data = {k: fields.get(v, '') for k, v in product_type_map.items()} +# return Product(**data) + + +# async def fetch_products_from_mingdaoyun(page: int = 1, page_size: int = 100) -> tuple[list[Product], int]: +# """ +# 从明道云获取产品列表 + +# Returns: +# tuple: (产品列表, 总数量) +# """ +# client = MingDaoYunClient( +# app_key='208e55fea5cea59f', +# sign='MWU0YmViYjkwZmM1ZDIzYzRiN2U3ZGQ4MmE4ZGNkMjc0MWM1ZmQ2ZjkwMjljODE4YmNkZTBhMzA0OTU2YzE2NA==', +# base_url="https://api.mingdao.com" +# ) +# response = await client.post( +# endpoint='/v2/open/worksheet/getFilterRows', +# data={ +# 'worksheetId': 'spmx', +# 'pageIndex': page, +# 'pageSize': page_size, +# } +# ) +# data = response.get('data') +# if not data: +# return [], 0 + +# products = [pick_product(item) for item in data.get('rows', [])] +# total_count = data.get('total', 0) +# return products, total_count diff --git a/pyproject.toml b/pyproject.toml index a8faf2a..ffb63ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "watchfiles>=0.22.0", "psycopg[binary]>=3.2.12", "redis>=5.0.0", + "aiohttp>=3.13.2", ] [dependency-groups] diff --git a/uv.lock b/uv.lock index 014de76..5a7bba9 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,78 @@ version = 1 revision = 3 requires-python = ">=3.14" +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b", size = 735234, upload-time = "2025-10-28T20:57:36.415Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61", size = 490733, upload-time = "2025-10-28T20:57:38.205Z" }, + { url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4", size = 491303, upload-time = "2025-10-28T20:57:40.122Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b", size = 1717965, upload-time = "2025-10-28T20:57:42.28Z" }, + { url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694", size = 1667221, upload-time = "2025-10-28T20:57:44.869Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906", size = 1757178, upload-time = "2025-10-28T20:57:47.216Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9", size = 1838001, upload-time = "2025-10-28T20:57:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011", size = 1716325, upload-time = "2025-10-28T20:57:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6", size = 1547978, upload-time = "2025-10-28T20:57:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213", size = 1682042, upload-time = "2025-10-28T20:57:55.617Z" }, + { url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49", size = 1680085, upload-time = "2025-10-28T20:57:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae", size = 1728238, upload-time = "2025-10-28T20:57:59.525Z" }, + { url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa", size = 1544395, upload-time = "2025-10-28T20:58:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4", size = 1742965, upload-time = "2025-10-28T20:58:03.972Z" }, + { url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a", size = 1697585, upload-time = "2025-10-28T20:58:06.189Z" }, + { url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940", size = 431621, upload-time = "2025-10-28T20:58:08.636Z" }, + { url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4", size = 457627, upload-time = "2025-10-28T20:58:11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673", size = 767360, upload-time = "2025-10-28T20:58:13.358Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd", size = 504616, upload-time = "2025-10-28T20:58:15.339Z" }, + { url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3", size = 509131, upload-time = "2025-10-28T20:58:17.693Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf", size = 1864168, upload-time = "2025-10-28T20:58:20.113Z" }, + { url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e", size = 1719200, upload-time = "2025-10-28T20:58:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5", size = 1843497, upload-time = "2025-10-28T20:58:24.672Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad", size = 1935703, upload-time = "2025-10-28T20:58:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e", size = 1792738, upload-time = "2025-10-28T20:58:29.787Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61", size = 1624061, upload-time = "2025-10-28T20:58:32.529Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661", size = 1789201, upload-time = "2025-10-28T20:58:34.618Z" }, + { url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98", size = 1776868, upload-time = "2025-10-28T20:58:38.835Z" }, + { url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693", size = 1790660, upload-time = "2025-10-28T20:58:41.507Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a", size = 1617548, upload-time = "2025-10-28T20:58:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be", size = 1817240, upload-time = "2025-10-28T20:58:45.787Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c", size = 1762334, upload-time = "2025-10-28T20:58:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734", size = 464685, upload-time = "2025-10-28T20:58:50.642Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f", size = 498093, upload-time = "2025-10-28T20:58:52.782Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + [[package]] name = "amqp" version = "5.3.1" @@ -338,6 +410,7 @@ name = "flower" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "aiohttp" }, { name = "celery" }, { name = "coverage" }, { name = "django" }, @@ -365,6 +438,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "aiohttp", specifier = ">=3.13.2" }, { name = "celery", specifier = ">=5.5.3" }, { name = "coverage", specifier = ">=7.12.0" }, { name = "django", specifier = ">=5.2.7" }, @@ -388,6 +462,47 @@ requires-dist = [ [package.metadata.requires-dev] dev = [{ name = "pytest", specifier = ">=9.0.1" }] +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -475,6 +590,51 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, ] +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, + { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, + { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, + { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -538,6 +698,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + [[package]] name = "psycopg" version = "3.2.12" @@ -913,3 +1112,49 @@ sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc7 wheels = [ { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, ] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +]