From 26c3df876f67a65dc6b131f180d948cbb158145c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Wed, 21 Jan 2026 18:31:08 -0300 Subject: [PATCH] add fulfillment --- api.saladeaula.digital/app/exceptions.py | 2 +- .../app/routes/orders/__init__.py | 52 ++++++++++++++- api.saladeaula.digital/template.yaml | 4 +- .../tests/routes/orders/test_fulfillment.py | 25 +++++++ .../tests/routes/orgs/test_subscription.py | 3 +- api.saladeaula.digital/tests/seeds.jsonl | 2 +- api.saladeaula.digital/uv.lock | 66 ++++++++++++------- 7 files changed, 120 insertions(+), 34 deletions(-) create mode 100644 api.saladeaula.digital/tests/routes/orders/test_fulfillment.py diff --git a/api.saladeaula.digital/app/exceptions.py b/api.saladeaula.digital/app/exceptions.py index 9265815..4f216bc 100644 --- a/api.saladeaula.digital/app/exceptions.py +++ b/api.saladeaula.digital/app/exceptions.py @@ -34,7 +34,7 @@ class MemberNotFoundError(NotFoundError): ... class OrderNotFoundError(NotFoundError): ... -class OrderConflictError(NotFoundError): ... +class OrderConflictError(ConflictError): ... class UserNotFoundError(NotFoundError): ... diff --git a/api.saladeaula.digital/app/routes/orders/__init__.py b/api.saladeaula.digital/app/routes/orders/__init__.py index e3bcbde..d1f18f3 100644 --- a/api.saladeaula.digital/app/routes/orders/__init__.py +++ b/api.saladeaula.digital/app/routes/orders/__init__.py @@ -1,14 +1,22 @@ +from http import HTTPStatus +from typing import Annotated + from aws_lambda_powertools.event_handler.api_gateway import Router +from aws_lambda_powertools.event_handler.openapi.params import Body +from layercake.dateutils import now from layercake.dynamodb import ( DynamoDBPersistenceLayer, KeyPair, SortKey, TransactKey, ) +from pydantic import UUID4 +from api_gateway import JSONResponse from boto3clients import dynamodb_client from config import ORDER_TABLE -from exceptions import OrderNotFoundError +from exceptions import ConflictError, OrderConflictError, OrderNotFoundError +from middlewares.authentication_middleware import User as Authenticated from .checkout import router as checkout from .payment_retries import router as payment_retries @@ -19,6 +27,9 @@ router = Router() dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) +class FulfillmentConflictError(ConflictError): ... + + @router.get('/') def get_order(order_id: str): order = dyn.collection.get_items( @@ -29,9 +40,9 @@ def get_order(order_id: str): + SortKey('ADDRESS', rename_key='address') + SortKey('CREDIT_CARD', rename_key='credit_card') + SortKey('INVOICE', rename_key='invoice') - + SortKey('NFSE', rename_key='nfse') + SortKey('FEE', rename_key='fee') - + SortKey('TRANSACTION#STATS', rename_key='stats'), + + SortKey('TRANSACTION#STATS', rename_key='stats') + + SortKey('FULFILLMENT', rename_key='fulfillment'), ) if not order: @@ -50,3 +61,38 @@ def get_order(order_id: str): | ({'created_at': order['create_date']} if 'create_date' in order else {}) | ({'paid_at': order['payment_date']} if 'payment_date' in order else {}) ) + + +@router.post('//fulfillment') +def fulfillment( + order_id: str, + org_id: Annotated[str | UUID4, Body(embed=True)], +): + created_by: Authenticated = router.context['user'] + + with dyn.transact_writer() as transact: + transact.condition( + key=KeyPair(order_id, '0'), + cond_expr='attribute_exists(sk) AND org_id = :org_id', + expr_attr_values={ + ':org_id': org_id, + }, + exc_cls=OrderConflictError, + ) + transact.put( + item={ + 'id': order_id, + 'sk': 'FULFILLMENT', + 'status': 'IN_PROGRESS', + 'org_id': org_id, + 'created_by': { + 'id': created_by.id, + 'name': created_by.name, + }, + 'created_at': now(), + }, + cond_expr='attribute_not_exists(sk)', + exc_cls=FulfillmentConflictError, + ) + + return JSONResponse(status_code=HTTPStatus.NO_CONTENT) diff --git a/api.saladeaula.digital/template.yaml b/api.saladeaula.digital/template.yaml index 2aa74a2..475ac2f 100644 --- a/api.saladeaula.digital/template.yaml +++ b/api.saladeaula.digital/template.yaml @@ -21,12 +21,12 @@ Parameters: Globals: Function: CodeUri: app/ - Runtime: python3.13 + Runtime: python3.14 Tracing: Active Architectures: - x86_64 Layers: - - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:104 + - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:106 Environment: Variables: TZ: America/Sao_Paulo diff --git a/api.saladeaula.digital/tests/routes/orders/test_fulfillment.py b/api.saladeaula.digital/tests/routes/orders/test_fulfillment.py new file mode 100644 index 0000000..f4c293e --- /dev/null +++ b/api.saladeaula.digital/tests/routes/orders/test_fulfillment.py @@ -0,0 +1,25 @@ +from http import HTTPMethod, HTTPStatus + +from layercake.dynamodb import DynamoDBPersistenceLayer + +from ...conftest import HttpApiProxy, LambdaContext + + +def test_fulfillment( + app, + seeds, + http_api_proxy: HttpApiProxy, + dynamodb_persistence_layer: DynamoDBPersistenceLayer, + lambda_context: LambdaContext, +): + r = app.lambda_handler( + http_api_proxy( + raw_path='/orders/4b23f6f5-5377-476b-b1de-79427c0295f6/fulfillment', + method=HTTPMethod.POST, + body={ + 'org_id': '123', + }, + ), + lambda_context, + ) + assert r['statusCode'] == HTTPStatus.NO_CONTENT diff --git a/api.saladeaula.digital/tests/routes/orgs/test_subscription.py b/api.saladeaula.digital/tests/routes/orgs/test_subscription.py index 0a747d5..2604ca3 100644 --- a/api.saladeaula.digital/tests/routes/orgs/test_subscription.py +++ b/api.saladeaula.digital/tests/routes/orgs/test_subscription.py @@ -23,7 +23,6 @@ def test_add_subscription( body={ 'name': 'pytest', 'billing_day': 1, - 'payment_method': 'MANUAL', }, ), lambda_context, @@ -37,5 +36,5 @@ def test_add_subscription( ) assert r['metadata']['billing_day'] == 1 - assert r['metadata']['payment_method'] == 'MANUAL' + # assert r['metadata']['payment_method'] == 'MANUAL' assert r['subscription']['name'] == 'pytest' diff --git a/api.saladeaula.digital/tests/seeds.jsonl b/api.saladeaula.digital/tests/seeds.jsonl index d6922c9..57092a7 100644 --- a/api.saladeaula.digital/tests/seeds.jsonl +++ b/api.saladeaula.digital/tests/seeds.jsonl @@ -46,7 +46,7 @@ // Seeds for Order // file: tests/routes/orders/test_payment_retries.py -{"id": "4b23f6f5-5377-476b-b1de-79427c0295f6", "sk": "0", "installments": 3, "status": "PENDING"} +{"id": "4b23f6f5-5377-476b-b1de-79427c0295f6", "sk": "0", "installments": 3, "status": "PENDING", "org_id": "123"} {"id": "4b23f6f5-5377-476b-b1de-79427c0295f6", "sk": "INVOICE", "invoice_id": "123"} {"id": "4b23f6f5-5377-476b-b1de-79427c0295f6", "sk": "TRANSACTION#STATS", "last_attempt_succeeded": false} diff --git a/api.saladeaula.digital/uv.lock b/api.saladeaula.digital/uv.lock index af62394..bcfef3d 100644 --- a/api.saladeaula.digital/uv.lock +++ b/api.saladeaula.digital/uv.lock @@ -88,6 +88,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/51/321e821856452f7386c4e9df866f196720b1ad0c5ea1623ea7399969ae3b/authlib-1.6.6-py2.py3-none-any.whl", hash = "sha256:7d9e9bc535c13974313a87f53e8430eb6ea3d1cf6ae4f6efcd793f2e949143fd", size = 244005, upload-time = "2025-12-12T08:01:40.209Z" }, ] +[[package]] +name = "aws-durable-execution-sdk-python" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/9d/f3646d325d6c5ce3ad143d6e916d046ea94685c17ec6991331f6a233e187/aws_durable_execution_sdk_python-1.1.1.tar.gz", hash = "sha256:3812b60a72ab5c5fd9c1c1ffeca96260a9a79910b2c1fe4cb47c758b7768b1ce", size = 266978, upload-time = "2026-01-12T23:32:16.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/98/c7ff676db3306ac59733d971167c69a9d86fef0fa410eafde270b2f80380/aws_durable_execution_sdk_python-1.1.1-py3-none-any.whl", hash = "sha256:d724cb5e59ba1dbfce9228b527daac88a24001bc00bac9e3adb6f5b79a283f11", size = 89799, upload-time = "2026-01-12T23:32:15.057Z" }, +] + [[package]] name = "aws-encryption-sdk" version = "4.0.3" @@ -150,29 +162,29 @@ wheels = [ [[package]] name = "boto3" -version = "1.42.30" +version = "1.42.32" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/79/2dac8b7cb075cfa43908ee9af3f8ee06880d84b86013854c5cca8945afac/boto3-1.42.30.tar.gz", hash = "sha256:ba9cd2f7819637d15bfbeb63af4c567fcc8a7dcd7b93dd12734ec58601169538", size = 112809, upload-time = "2026-01-16T20:37:23.636Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/73/2a8065918dcc9f07046f7e87e17f54a62914a8b7f1f9e506799ec533d2e9/boto3-1.42.32.tar.gz", hash = "sha256:0ba535985f139cf38455efd91f3801fe72e5cce6ded2df5aadfd63177d509675", size = 112830, upload-time = "2026-01-21T20:40:10.891Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/b3/2c0d828c9f668292e277ca5232e6160dd5b4b660a3f076f20dd5378baa1e/boto3-1.42.30-py3-none-any.whl", hash = "sha256:d7e548bea65e0ae2c465c77de937bc686b591aee6a352d5a19a16bc751e591c1", size = 140573, upload-time = "2026-01-16T20:37:22.089Z" }, + { url = "https://files.pythonhosted.org/packages/60/e3/c86658f1fd0191aa8131cb1baacd337b037546d902980ea5a9c8f0c5cd9b/boto3-1.42.32-py3-none-any.whl", hash = "sha256:695ac7e62dfde28cc1d3b28a581cce37c53c729d48ea0f4cd0dbf599856850cf", size = 140573, upload-time = "2026-01-21T20:40:09.1Z" }, ] [[package]] name = "boto3-stubs" -version = "1.42.30" +version = "1.42.32" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore-stubs" }, { name = "types-s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/a7/9ef634d9dfea14acf6852be022b2dafe0ac1be66aed3e9e5b6615543e43c/boto3_stubs-1.42.30.tar.gz", hash = "sha256:68a2ca754686c980d79d1c67f2d4d5eb8dc3d89f4ec62d4080b95fbdad3ee01b", size = 100897, upload-time = "2026-01-16T20:53:26.666Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/79/f7e536663d0136dc2771467307d866132e6c240f3a4291b02f7d6cfbfb6f/boto3_stubs-1.42.32.tar.gz", hash = "sha256:6fe9c8fd16aec68e4693d53b2155ad5c11d0a1984610622bcd3a6eb003787648", size = 100901, upload-time = "2026-01-21T20:59:18.275Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/6a/ac708656b0f5fbc3f8c212e1a029a681029ae625acfb7e8e983aabe7672d/boto3_stubs-1.42.30-py3-none-any.whl", hash = "sha256:e1d106cf9c662ecfd6044483e53c6e9584b6da916e753510f51a8bfc8d19016d", size = 69783, upload-time = "2026-01-16T20:53:22.081Z" }, + { url = "https://files.pythonhosted.org/packages/17/3a/ede6bbb4cd40eb18086d4ad180176cc07666de88a0bc868392012e4c5bb9/boto3_stubs-1.42.32-py3-none-any.whl", hash = "sha256:722629d882efacc9a7c4744748493c8bec1d0441ce81b1537f4238c293202082", size = 69784, upload-time = "2026-01-21T20:59:14.456Z" }, ] [package.optional-dependencies] @@ -188,28 +200,28 @@ essential = [ [[package]] name = "botocore" -version = "1.42.30" +version = "1.42.32" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/38/23862628a0eb044c8b8b3d7a9ad1920b3bfd6bce6d746d5a871e8382c7e4/botocore-1.42.30.tar.gz", hash = "sha256:9bf1662b8273d5cc3828a49f71ca85abf4e021011c1f0a71f41a2ea5769a5116", size = 14891439, upload-time = "2026-01-16T20:37:13.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/5e/84404e094be8e2145c7f6bb8b3709193bc4488c385edffc6cc6890b5c88b/botocore-1.42.32.tar.gz", hash = "sha256:4c0a9fe23e060c019e327cd5e4ea1976a1343faba74e5301ebfc9549cc584ccb", size = 14898756, upload-time = "2026-01-21T20:39:59.698Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/8d/6d7b016383b1f74dd93611b1c5078bbaddaca901553ab886dcda87cae365/botocore-1.42.30-py3-none-any.whl", hash = "sha256:97070a438cac92430bb7b65f8ebd7075224f4a289719da4ee293d22d1e98db02", size = 14566340, upload-time = "2026-01-16T20:37:10.94Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ab/55062f6eaf9fc537b62b7425ab53ef4366032256e1dda8ef52a9a31f7a6e/botocore-1.42.32-py3-none-any.whl", hash = "sha256:9c1ce43687cc4c0bba12054b229b3464265c699e2de4723998d86791254a5a37", size = 14573367, upload-time = "2026-01-21T20:39:56.65Z" }, ] [[package]] name = "botocore-stubs" -version = "1.42.30" +version = "1.42.31" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "types-awscrt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/92/f935fa89aa6d2111e50ae7abe7911134d674e724382ca47ef5114758f3ee/botocore_stubs-1.42.30.tar.gz", hash = "sha256:c4d11678eb172263feb1de805452c376d9c11e54f1903a7cfa132ba765d57b7d", size = 42419, upload-time = "2026-01-16T21:27:46.737Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/1d/5be12f86e2095b0318f95d32c724acdb6f275e59a1052b7180e58c8e52c3/botocore_stubs-1.42.31.tar.gz", hash = "sha256:e7e762e37b205c7ba79782c67cc5c35151748ce267470a70ac9c026083655d9f", size = 42413, upload-time = "2026-01-20T21:28:08.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/9a/707da28295bf11078856654f3876d7687479ad8f6c37f0e0e4fdd30fcded/botocore_stubs-1.42.30-py3-none-any.whl", hash = "sha256:e41f864440d7d84ef514384557454ca094a5ee74e72c4ff907f637bc8a786df4", size = 66762, upload-time = "2026-01-16T21:27:45.439Z" }, + { url = "https://files.pythonhosted.org/packages/40/c9/724dfd7cb610d93899df71e70faf5626ee82c86f139b90ae70c2dab22ce3/botocore_stubs-1.42.31-py3-none-any.whl", hash = "sha256:b6ae47a39a6c185e610b7bbf087923dc83e54a52378ad74aff76a6708850d38d", size = 66760, upload-time = "2026-01-20T21:28:06.697Z" }, ] [[package]] @@ -677,11 +689,12 @@ wheels = [ [[package]] name = "layercake" -version = "0.12.0" +version = "0.13.1" source = { directory = "../layercake" } dependencies = [ { name = "arnparse" }, { name = "authlib" }, + { name = "aws-durable-execution-sdk-python" }, { name = "aws-lambda-powertools", extra = ["all"] }, { name = "cloudflare" }, { name = "dictdiffer" }, @@ -694,6 +707,7 @@ dependencies = [ { name = "psycopg", extra = ["binary"] }, { name = "pycpfcnpj" }, { name = "pydantic", extra = ["email"] }, + { name = "pydantic-core" }, { name = "pydantic-extra-types" }, { name = "python-calamine" }, { name = "python-multipart" }, @@ -708,6 +722,7 @@ dependencies = [ requires-dist = [ { name = "arnparse", specifier = ">=0.0.2" }, { name = "authlib", specifier = ">=1.6.5" }, + { name = "aws-durable-execution-sdk-python", specifier = ">=1.1.1" }, { name = "aws-lambda-powertools", extras = ["all"], specifier = ">=3.23.0" }, { name = "cloudflare", specifier = ">=4.3.1" }, { name = "dictdiffer", specifier = ">=0.9.0" }, @@ -720,6 +735,7 @@ requires-dist = [ { name = "psycopg", extras = ["binary"], specifier = ">=3.2.9" }, { name = "pycpfcnpj", specifier = ">=1.8" }, { name = "pydantic", extras = ["email"], specifier = ">=2.10.6" }, + { name = "pydantic-core", specifier = ">=2.41.5" }, { name = "pydantic-extra-types", specifier = ">=2.10.3" }, { name = "python-calamine", specifier = ">=0.5.4" }, { name = "python-multipart", specifier = ">=0.0.20" }, @@ -774,11 +790,11 @@ wheels = [ [[package]] name = "mypy-boto3-ec2" -version = "1.42.29" +version = "1.42.32" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/e4/c8e6b8a6a129335ebce7061142353461ac865ca12e8923928bab51b89c28/mypy_boto3_ec2-1.42.29.tar.gz", hash = "sha256:a3ba4006939c8b96747db9464dca5ea7b1671535b7ece4164e7c5b224f02649a", size = 432811, upload-time = "2026-01-15T20:42:16.209Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/c6/5ed703e1d459cfcaa9048de4b0a841fcece744b1c19b8041affc6259fc7d/mypy_boto3_ec2-1.42.32.tar.gz", hash = "sha256:13470e97936a1e7b0a77750780ab1928736a89fed927a6b872df4ee5de78ca22", size = 433033, upload-time = "2026-01-21T20:59:09.674Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/13/066508053c930e7f1f1c5adc538c9ebd53905e9bfa6b2dcfc0e57ee6a4d4/mypy_boto3_ec2-1.42.29-py3-none-any.whl", hash = "sha256:6a66131ccb9fdf72fc1f8c13646ee118861cb5ce081e3a7037c9abde20682f00", size = 423065, upload-time = "2026-01-15T20:42:08.579Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/bf41b4f12b290959344794daa5a7a3d6bd2c9184254f2e34ac0eb0f5bf97/mypy_boto3_ec2-1.42.32-py3-none-any.whl", hash = "sha256:ddc2ab58bf1efdc40533727786f94ceb8bb720436ad82da63eb8c1773074e924", size = 423287, upload-time = "2026-01-21T20:59:05.228Z" }, ] [[package]] @@ -857,11 +873,11 @@ wheels = [ [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -948,11 +964,11 @@ wheels = [ [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] @@ -1420,11 +1436,11 @@ wheels = [ [[package]] name = "wcwidth" -version = "0.2.14" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/75/2144b65e4fba12a2d9868e9a3f99db7fa0760670d064603634bef9ff1709/wcwidth-0.3.0.tar.gz", hash = "sha256:af1a2fb0b83ef4a7fc0682a4c95ca2576e14d0280bca2a9e67b7dc9f2733e123", size = 172238, upload-time = "2026-01-21T17:44:09.508Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/18/0e/a5f0257ab47492b7afb5fb60347d14ba19445e2773fc8352d4be6bd2f6f8/wcwidth-0.3.0-py3-none-any.whl", hash = "sha256:073a1acb250e4add96cfd5ef84e0036605cd6e0d0782c8c15c80e42202348458", size = 85520, upload-time = "2026-01-21T17:44:08.002Z" }, ] [[package]]