From 6ae52ff3118fa912cad43044f92ddcfc719c37f5 Mon Sep 17 00:00:00 2001 From: jonathan343 Date: Thu, 12 Nov 2026 17:25:43 -0500 Subject: [PATCH 1/3] Make client transport dependencies opt-in Expose sync and async extras for generated clients while keeping shared models and custom transports usable without either default HTTP dependency. Preserve test dependency extras and add installation advice for missing transports. --- CHANGELOG.md | 8 ++ codegen-output/aws-json-1-0/README.md | 12 +++ codegen-output/aws-json-1-0/pyproject.toml | 10 ++- codegen-output/aws-json-1-1/README.md | 12 +++ codegen-output/aws-json-1-1/pyproject.toml | 10 ++- codegen-output/aws-query/README.md | 12 +++ codegen-output/aws-query/pyproject.toml | 10 ++- codegen-output/ec2-query/README.md | 12 +++ codegen-output/ec2-query/pyproject.toml | 10 ++- .../my-handwritten-json-model/README.md | 12 +++ .../my-handwritten-json-model/pyproject.toml | 10 ++- codegen-output/my-handwritten-model/README.md | 12 +++ .../my-handwritten-model/pyproject.toml | 10 ++- .../non-query-compatible-rpcv2-cbor/README.md | 12 +++ .../pyproject.toml | 10 ++- .../query-compatible-aws-json-1-0/README.md | 12 +++ .../pyproject.toml | 10 ++- .../query-compatible-rpcv2-cbor/README.md | 12 +++ .../pyproject.toml | 10 ++- codegen-output/rest-json-1/README.md | 12 +++ codegen-output/rest-json-1/pyproject.toml | 10 ++- .../rest-xml-with-namespace/README.md | 12 +++ .../rest-xml-with-namespace/pyproject.toml | 10 ++- codegen-output/rest-xml/README.md | 12 +++ codegen-output/rest-xml/pyproject.toml | 10 ++- codegen-output/rpcv2-cbor/README.md | 12 +++ codegen-output/rpcv2-cbor/pyproject.toml | 10 ++- codegen-output/rpcv2-json/README.md | 12 +++ codegen-output/rpcv2-json/pyproject.toml | 10 ++- .../codegen/generators/SetupGenerator.java | 81 +++++++++++++++++-- .../generators/SetupGeneratorTest.java | 48 +++++++++++ designs/async-and-sync-duality.md | 16 ++++ ...ment-3f9c2a7e5b1d4e08a6c4d2b9e7f1a053.json | 4 + .../src/smithy_http/aio/aiohttp.py | 3 +- .../smithy-http/src/smithy_http/urllib3.py | 3 +- .../tests/unit/aio/test_aiohttp.py | 13 +++ .../smithy-http/tests/unit/test_urllib3.py | 14 +++- 37 files changed, 476 insertions(+), 22 deletions(-) create mode 100644 packages/smithy-http/.changes/next-release/smithy-http-enhancement-3f9c2a7e5b1d4e08a6c4d2b9e7f1a053.json diff --git a/CHANGELOG.md b/CHANGELOG.md index bac0c5156..2d9e1135e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +### Breaking Changes + +* Generated HTTP clients no longer install aiohttp and urllib3 by default. + Install the `sync` extra for the synchronous client's default transport, the + `async` extra for the asynchronous client's default transport, or both, such + as `pip install "aws_sdk_s3[sync,async]"`. Clients configured with a custom + transport work without either extra. Imports are unchanged. + ### Features * Generated AWS clients validate the checksums of response payloads for operations with the `aws.protocols#httpChecksum` trait, such as Amazon S3's `GetObject`. These clients depend on the `checksums` extra of `smithy-aws-core`. diff --git a/codegen-output/aws-json-1-0/README.md b/codegen-output/aws-json-1-0/README.md index 19ea5f3c1..a33bd7c4b 100644 --- a/codegen-output/aws-json-1-0/README.md +++ b/codegen-output/aws-json-1-0/README.md @@ -3,3 +3,15 @@ ## Sample Json 1.0 Protocol Service Client Python client for Sample Json 1.0 Protocol Service + +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "awsjson10[sync]" +pip install "awsjson10[async]" +pip install "awsjson10[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. diff --git a/codegen-output/aws-json-1-0/pyproject.toml b/codegen-output/aws-json-1-0/pyproject.toml index a9b4f5b94..7d9809e48 100644 --- a/codegen-output/aws-json-1-0/pyproject.toml +++ b/codegen-output/aws-json-1-0/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core[json]~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[aiohttp, urllib3]~=0.7.0" + "smithy_http~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/aws-json-1-1/README.md b/codegen-output/aws-json-1-1/README.md index 984d1fbde..a924f4e1b 100644 --- a/codegen-output/aws-json-1-1/README.md +++ b/codegen-output/aws-json-1-1/README.md @@ -3,3 +3,15 @@ ## Sample Json 1.1 Protocol Service Client Python client for Sample Json 1.1 Protocol Service + +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "awsjson11[sync]" +pip install "awsjson11[async]" +pip install "awsjson11[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. diff --git a/codegen-output/aws-json-1-1/pyproject.toml b/codegen-output/aws-json-1-1/pyproject.toml index c6f588ad9..6366b311b 100644 --- a/codegen-output/aws-json-1-1/pyproject.toml +++ b/codegen-output/aws-json-1-1/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core[json]~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[aiohttp, urllib3]~=0.7.0" + "smithy_http~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/aws-query/README.md b/codegen-output/aws-query/README.md index fdd59c80f..27eb1b649 100644 --- a/codegen-output/aws-query/README.md +++ b/codegen-output/aws-query/README.md @@ -4,6 +4,18 @@ Python client for Sample Query Protocol Service +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "awsquery[sync]" +pip install "awsquery[async]" +pip install "awsquery[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. + ### Documentation A query service that sends query requests and XML responses. diff --git a/codegen-output/aws-query/pyproject.toml b/codegen-output/aws-query/pyproject.toml index eade7c2a8..4c7311f37 100644 --- a/codegen-output/aws-query/pyproject.toml +++ b/codegen-output/aws-query/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core[xml]~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[aiohttp, urllib3]~=0.7.0" + "smithy_http~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/ec2-query/README.md b/codegen-output/ec2-query/README.md index c24b4b691..58ac2d016 100644 --- a/codegen-output/ec2-query/README.md +++ b/codegen-output/ec2-query/README.md @@ -4,6 +4,18 @@ Python client for Sample Ec2 Protocol Service +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "ec2query[sync]" +pip install "ec2query[async]" +pip install "ec2query[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. + ### Documentation An EC2 query service that sends query requests and XML responses. diff --git a/codegen-output/ec2-query/pyproject.toml b/codegen-output/ec2-query/pyproject.toml index 4b6ce3a64..00b332137 100644 --- a/codegen-output/ec2-query/pyproject.toml +++ b/codegen-output/ec2-query/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core[xml]~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[aiohttp, urllib3]~=0.7.0" + "smithy_http~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/my-handwritten-json-model/README.md b/codegen-output/my-handwritten-json-model/README.md index 006564531..ad4f8bcd6 100644 --- a/codegen-output/my-handwritten-json-model/README.md +++ b/codegen-output/my-handwritten-json-model/README.md @@ -4,6 +4,18 @@ Python client for My Handwritten JSON Model +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "my_handwritten_json_model[sync]" +pip install "my_handwritten_json_model[async]" +pip install "my_handwritten_json_model[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. + ### Documentation The rpcv2Json counterpart of diff --git a/codegen-output/my-handwritten-json-model/pyproject.toml b/codegen-output/my-handwritten-json-model/pyproject.toml index 6f1e2134b..b7753b36f 100644 --- a/codegen-output/my-handwritten-json-model/pyproject.toml +++ b/codegen-output/my-handwritten-json-model/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core[eventstream]~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[json, aiohttp, urllib3]~=0.7.0" + "smithy_http[json]~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/my-handwritten-model/README.md b/codegen-output/my-handwritten-model/README.md index a7227557a..c4b34a7ad 100644 --- a/codegen-output/my-handwritten-model/README.md +++ b/codegen-output/my-handwritten-model/README.md @@ -3,3 +3,15 @@ ## My Handwritten Model Client Python client for My Handwritten Model + +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "my_handwritten_model[sync]" +pip install "my_handwritten_model[async]" +pip install "my_handwritten_model[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. diff --git a/codegen-output/my-handwritten-model/pyproject.toml b/codegen-output/my-handwritten-model/pyproject.toml index e58cf27f1..86d58fdc1 100644 --- a/codegen-output/my-handwritten-model/pyproject.toml +++ b/codegen-output/my-handwritten-model/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core[eventstream]~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[cbor, aiohttp, urllib3]~=0.7.0" + "smithy_http[cbor]~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/non-query-compatible-rpcv2-cbor/README.md b/codegen-output/non-query-compatible-rpcv2-cbor/README.md index 7b955d6a8..512143a7a 100644 --- a/codegen-output/non-query-compatible-rpcv2-cbor/README.md +++ b/codegen-output/non-query-compatible-rpcv2-cbor/README.md @@ -3,3 +3,15 @@ ## Non Query Compatible RpcV2 Protocol Service Client Python client for Non Query Compatible RpcV2 Protocol Service + +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "nonquerycompatiblerpcv2cbor[sync]" +pip install "nonquerycompatiblerpcv2cbor[async]" +pip install "nonquerycompatiblerpcv2cbor[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. diff --git a/codegen-output/non-query-compatible-rpcv2-cbor/pyproject.toml b/codegen-output/non-query-compatible-rpcv2-cbor/pyproject.toml index 5937a8056..5a594da75 100644 --- a/codegen-output/non-query-compatible-rpcv2-cbor/pyproject.toml +++ b/codegen-output/non-query-compatible-rpcv2-cbor/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[cbor, aiohttp, urllib3]~=0.7.0" + "smithy_http[cbor]~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/query-compatible-aws-json-1-0/README.md b/codegen-output/query-compatible-aws-json-1-0/README.md index 2f8ba8249..8af9e27fa 100644 --- a/codegen-output/query-compatible-aws-json-1-0/README.md +++ b/codegen-output/query-compatible-aws-json-1-0/README.md @@ -3,3 +3,15 @@ ## Query Compatible Json 1.0 Protocol Service Client Python client for Query Compatible Json 1.0 Protocol Service + +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "querycompatibleawsjson10[sync]" +pip install "querycompatibleawsjson10[async]" +pip install "querycompatibleawsjson10[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. diff --git a/codegen-output/query-compatible-aws-json-1-0/pyproject.toml b/codegen-output/query-compatible-aws-json-1-0/pyproject.toml index 0fdccd779..3441618ae 100644 --- a/codegen-output/query-compatible-aws-json-1-0/pyproject.toml +++ b/codegen-output/query-compatible-aws-json-1-0/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core[json]~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[aiohttp, urllib3]~=0.7.0" + "smithy_http~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/query-compatible-rpcv2-cbor/README.md b/codegen-output/query-compatible-rpcv2-cbor/README.md index 5928f1851..70ea7d556 100644 --- a/codegen-output/query-compatible-rpcv2-cbor/README.md +++ b/codegen-output/query-compatible-rpcv2-cbor/README.md @@ -3,3 +3,15 @@ ## Query Compatible RpcV2 Protocol Service Client Python client for Query Compatible RpcV2 Protocol Service + +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "querycompatiblerpcv2cbor[sync]" +pip install "querycompatiblerpcv2cbor[async]" +pip install "querycompatiblerpcv2cbor[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. diff --git a/codegen-output/query-compatible-rpcv2-cbor/pyproject.toml b/codegen-output/query-compatible-rpcv2-cbor/pyproject.toml index 8be942985..897ee5c00 100644 --- a/codegen-output/query-compatible-rpcv2-cbor/pyproject.toml +++ b/codegen-output/query-compatible-rpcv2-cbor/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[cbor, aiohttp, urllib3]~=0.7.0" + "smithy_http[cbor]~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/rest-json-1/README.md b/codegen-output/rest-json-1/README.md index 239dd0567..2bcafaf8b 100644 --- a/codegen-output/rest-json-1/README.md +++ b/codegen-output/rest-json-1/README.md @@ -3,3 +3,15 @@ ## Sample Rest Json Protocol Service Client Python client for Sample Rest Json Protocol Service + +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "restjson[sync]" +pip install "restjson[async]" +pip install "restjson[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. diff --git a/codegen-output/rest-json-1/pyproject.toml b/codegen-output/rest-json-1/pyproject.toml index 28eae9ce9..89638e431 100644 --- a/codegen-output/rest-json-1/pyproject.toml +++ b/codegen-output/rest-json-1/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core[eventstream, json]~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[aiohttp, urllib3]~=0.7.0" + "smithy_http~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/rest-xml-with-namespace/README.md b/codegen-output/rest-xml-with-namespace/README.md index 9c005dcfc..25cf7815e 100644 --- a/codegen-output/rest-xml-with-namespace/README.md +++ b/codegen-output/rest-xml-with-namespace/README.md @@ -4,6 +4,18 @@ Python client for Sample Rest Xml Protocol Service With Namespace +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "restxmlwithnamespace[sync]" +pip install "restxmlwithnamespace[async]" +pip install "restxmlwithnamespace[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. + ### Documentation A REST XML service that sends XML requests and responses. This service diff --git a/codegen-output/rest-xml-with-namespace/pyproject.toml b/codegen-output/rest-xml-with-namespace/pyproject.toml index 2a0359aa8..cae6d76a2 100644 --- a/codegen-output/rest-xml-with-namespace/pyproject.toml +++ b/codegen-output/rest-xml-with-namespace/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core[xml]~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[aiohttp, urllib3]~=0.7.0" + "smithy_http~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/rest-xml/README.md b/codegen-output/rest-xml/README.md index 365c8f067..c62a1b2ad 100644 --- a/codegen-output/rest-xml/README.md +++ b/codegen-output/rest-xml/README.md @@ -4,6 +4,18 @@ Python client for Sample Rest Xml Protocol Service +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "restxml[sync]" +pip install "restxml[async]" +pip install "restxml[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. + ### Documentation A REST XML service that sends XML requests and responses. diff --git a/codegen-output/rest-xml/pyproject.toml b/codegen-output/rest-xml/pyproject.toml index 6d34622cd..28e4e01eb 100644 --- a/codegen-output/rest-xml/pyproject.toml +++ b/codegen-output/rest-xml/pyproject.toml @@ -26,10 +26,18 @@ classifiers = [ dependencies = [ "smithy_aws_core[xml]~=0.13.0", "smithy_core~=0.10.0", - "smithy_http[aiohttp, urllib3]~=0.7.0" + "smithy_http~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/rpcv2-cbor/README.md b/codegen-output/rpcv2-cbor/README.md index 7beeef19f..d1e45ea23 100644 --- a/codegen-output/rpcv2-cbor/README.md +++ b/codegen-output/rpcv2-cbor/README.md @@ -3,3 +3,15 @@ ## RpcV2 Protocol Service Client Python client for RpcV2 Protocol Service + +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "rpcv2cbor[sync]" +pip install "rpcv2cbor[async]" +pip install "rpcv2cbor[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. diff --git a/codegen-output/rpcv2-cbor/pyproject.toml b/codegen-output/rpcv2-cbor/pyproject.toml index 1c45fc0bd..81a0752fd 100644 --- a/codegen-output/rpcv2-cbor/pyproject.toml +++ b/codegen-output/rpcv2-cbor/pyproject.toml @@ -25,10 +25,18 @@ classifiers = [ dependencies = [ "smithy_core~=0.10.0", - "smithy_http[cbor, aiohttp, urllib3]~=0.7.0" + "smithy_http[cbor]~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen-output/rpcv2-json/README.md b/codegen-output/rpcv2-json/README.md index 49b85983a..bfe65e8d4 100644 --- a/codegen-output/rpcv2-json/README.md +++ b/codegen-output/rpcv2-json/README.md @@ -3,3 +3,15 @@ ## RpcV2 JSON Protocol Service Client Python client for RpcV2 JSON Protocol Service + +### Installation + +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. + +```sh +pip install "rpcv2json[sync]" +pip install "rpcv2json[async]" +pip install "rpcv2json[sync,async]" +``` + +Without either extra, the models can be used, and either client works with an explicitly configured custom transport. diff --git a/codegen-output/rpcv2-json/pyproject.toml b/codegen-output/rpcv2-json/pyproject.toml index 68835928e..005134974 100644 --- a/codegen-output/rpcv2-json/pyproject.toml +++ b/codegen-output/rpcv2-json/pyproject.toml @@ -25,10 +25,18 @@ classifiers = [ dependencies = [ "smithy_core~=0.10.0", - "smithy_http[json, aiohttp, urllib3]~=0.7.0" + "smithy_http[json]~=0.7.0" ] [project.optional-dependencies] +sync = [ + "smithy_http[urllib3]~=0.7.0" +] + +async = [ + "smithy_http[aiohttp]~=0.7.0" +] + awscrt = [ "smithy_http[awscrt]~=0.7.0" ] diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java index dda9482e2..f86749d1b 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java @@ -4,6 +4,7 @@ */ package software.amazon.smithy.python.codegen.generators; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; @@ -34,6 +35,13 @@ @SmithyInternalApi public final class SetupGenerator { + // Client mode extras, each mapped to the smithy_http extra that provides that mode's default + // transport. These transports are opt-in rather than base requirements so that customers only + // install what the modes they use need. + private static final List> MODE_TRANSPORT_EXTRAS = List.of( + Map.entry("sync", "urllib3"), + Map.entry("async", "aiohttp")); + private SetupGenerator() {} public static void generateSetup( @@ -42,8 +50,10 @@ public static void generateSetup( ) { var dependencies = gatherDependencies(context.writerDelegator().getDependencies().stream()); var description = resolveDescription(settings, context); + var extras = collectOptionalDependencies( + dependencies.getOrDefault(PythonDependency.Type.DEPENDENCY.getType(), Map.of())); writePyproject(settings, description, context.writerDelegator(), dependencies); - writeReadme(settings, description, context); + writeReadme(settings, description, context, extras); } /** @@ -251,6 +261,23 @@ static Map> collectOptionalDependencies( Map dependencies ) { var extras = new LinkedHashMap>(); + var smithyHttp = dependencies.get(SmithyPythonDependency.SMITHY_HTTP.packageName()); + if (smithyHttp == null) { + return extras; + } + var smithyHttpExtras = getOptionalDependencies(smithyHttp); + + // Expose each requested default transport through the extra for the client mode that + // uses it (e.g. pip install [sync]) instead of requiring it in the base package. + // Like awscrt below, this re-exports smithy_http's extra so the version constraint stays + // sourced from smithy-http. The base requirement drops these, see + // getRequiredOptionalDependencies. + for (var mode : MODE_TRANSPORT_EXTRAS) { + if (smithyHttpExtras.contains(mode.getValue())) { + extras.put(mode.getKey(), + List.of("smithy_http[" + mode.getValue() + "]" + smithyHttp.getVersion())); + } + } // Let users opt into the CRT transport (AWSCRTHTTPClient) without guessing a compatible // awscrt version, by re-exporting smithy_http's awscrt extra. This keeps the version @@ -258,19 +285,33 @@ static Map> collectOptionalDependencies( // already requires awscrt as a hard dependency, where there is nothing to opt into. No // client defaults to CRT today, so the guard is currently always taken; it remains as a // safeguard should a client ever pull awscrt as a hard dependency again. - var smithyHttp = dependencies.get(SmithyPythonDependency.SMITHY_HTTP.packageName()); - if (smithyHttp != null && !getOptionalDependencies(smithyHttp).contains("awscrt")) { + if (!smithyHttpExtras.contains("awscrt")) { extras.put("awscrt", List.of("smithy_http[awscrt]" + smithyHttp.getVersion())); } return extras; } + /** + * Get the optional dependencies to require along with a dependency, leaving out the default + * transports of base dependencies, which are exposed through client mode extras instead. + * Other dependency groups, such as test dependencies, keep their requested extras. + */ + private static List getRequiredOptionalDependencies(SymbolDependency dependency) { + var optional = getOptionalDependencies(dependency); + if (!dependency.getDependencyType().equals(PythonDependency.Type.DEPENDENCY.getType()) + || !dependency.getPackageName().equals(SmithyPythonDependency.SMITHY_HTTP.packageName())) { + return optional; + } + var transports = MODE_TRANSPORT_EXTRAS.stream().map(Map.Entry::getValue).toList(); + return optional.stream().filter(extra -> !transports.contains(extra)).toList(); + } + private static void writeDependencyList(PythonWriter writer, Collection dependencies) { for (var iter = dependencies.iterator(); iter.hasNext();) { writer.pushState(); var dependency = iter.next(); - writer.putContext("deps", getOptionalDependencies(dependency)); + writer.putContext("deps", getRequiredOptionalDependencies(dependency)); writer.putContext("isLink", dependency.getProperty(SymbolProperties.IS_LINK).orElse(false)); writer.putContext("last", !iter.hasNext()); writer.write(""" @@ -301,7 +342,8 @@ private static List getOptionalDependencies(SymbolDependency dependency) private static void writeReadme( PythonSettings settings, String description, - GenerationContext context + GenerationContext context, + Map> extras ) { var service = context.model().expectShape(settings.service()); @@ -310,6 +352,11 @@ private static void writeReadme( .map(StringTrait::getValue) .orElse(StringUtils.capitalize(settings.moduleName())); + var modes = MODE_TRANSPORT_EXTRAS.stream() + .map(Map.Entry::getKey) + .filter(extras::containsKey) + .toList(); + context.writerDelegator().useFileWriter("README.md", writer -> { writer.pushState(new ReadmeSection()); writer.write(""" @@ -318,6 +365,30 @@ private static void writeReadme( $L """, title, StringUtils.wrap(description, 80)); + if (!modes.isEmpty()) { + var installs = new ArrayList<>(modes); + if (modes.size() > 1) { + installs.add(String.join(",", modes)); + } + writer.write(""" + ### Installation + + Install the `sync` extra for the synchronous client's default HTTP \ + transport, the `async` extra for the asynchronous client's default HTTP \ + transport, or both. + + ```sh + $L + ``` + + Without either extra, the models can be used, and either client works \ + with an explicitly configured custom transport. + """, + installs.stream() + .map(extra -> "pip install \"" + settings.moduleName() + "[" + extra + "]\"") + .collect(Collectors.joining("\n"))); + } + service.getTrait(DocumentationTrait.class).map(StringTrait::getValue).ifPresent(documentation -> { writer.write(""" ### Documentation diff --git a/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java b/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java index 097191436..8297e9f2e 100644 --- a/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java +++ b/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java @@ -48,6 +48,54 @@ public void exposesAwscrtExtraForSmithyHttpDependency() { extras); } + /** + * The sync (urllib3) and async (aiohttp) default transports are opt-in through {@code sync} + * and {@code async} mode extras rather than base requirements, while unrelated smithy_http + * extras (here the cbor protocol extra) stay in the base requirement. The README shows how to + * install them. + */ + @Test + public void movesDefaultTransportsIntoModeExtras() { + var service = service("smithy.example#TestService", null); + var model = Model.builder().addShape(service).build(); + var settings = PythonSettings.builder() + .service(service.getId()) + .moduleName("test_client") + .moduleVersion("0.0.1") + .build(); + var manifest = new MockManifest(); + var delegator = new PythonDelegator(manifest, new PythonSymbolProvider(model, settings), settings); + var context = GenerationContext.builder() + .model(model) + .settings(settings) + .fileManifest(manifest) + .writerDelegator(delegator) + .build(); + var smithyHttp = SmithyPythonDependency.SMITHY_HTTP; + delegator.useFileWriter("test_client/client.py", "test_client.client", writer -> { + writer.addDependency(smithyHttp.withOptionalDependencies("cbor")); + writer.addDependency(smithyHttp.withOptionalDependencies("aiohttp")); + writer.addDependency(smithyHttp.withOptionalDependencies("urllib3")); + writer.addDependency(smithyHttp.withOptionalDependencies("aiohttp", "urllib3").asTestDependency()); + }); + + SetupGenerator.generateSetup(settings, context); + delegator.flushWriters(); + String pyproject = manifest.expectFileString("pyproject.toml"); + String readme = manifest.expectFileString("README.md"); + + var version = smithyHttp.version(); + assertAll( + () -> assertTrue(readme.contains("pip install \"test_client[sync,async]\""), readme), + () -> assertTrue(pyproject.contains("\"smithy_http[cbor]" + version + "\"\n]"), pyproject), + () -> assertTrue(pyproject.contains("sync = [\n \"smithy_http[urllib3]" + version + "\"\n]"), + pyproject), + () -> assertTrue(pyproject.contains("async = [\n \"smithy_http[aiohttp]" + version + "\"\n]"), + pyproject), + () -> assertTrue(pyproject.contains("[dependency-groups]\ntest = [\n \"smithy_http[aiohttp, urllib3]" + + version + "\"\n]"), pyproject)); + } + @Test public void readmeKeepsRawTripleQuotesInCodeSpans() { // The README is Markdown, not a docstring, so the docstring quote escaping diff --git a/designs/async-and-sync-duality.md b/designs/async-and-sync-duality.md index ed825bd93..e2f98b74c 100644 --- a/designs/async-and-sync-duality.md +++ b/designs/async-and-sync-duality.md @@ -2,6 +2,22 @@ Smithy-Python generated clients export two different classes that run in Python's synchronous and asynchronous (aio, async/await) operation modes respectively. For example, the `aws_sdk_s3` package will export both the `S3Client` (synchronous) and `AsyncS3Client` (asynchronous) classes. This allows the customer to operate one or both modes depending on application requirements. +## Client mode extras + +Generated HTTP clients expose a `sync` and an `async` extra that install each mode's default HTTP transport (urllib3 for `sync`, aiohttp for `async`): + +```sh +pip install "aws_sdk_s3[sync]" +pip install "aws_sdk_s3[async]" +pip install "aws_sdk_s3[sync,async]" +``` + +- A bare install, without either extra, does not install aiohttp or urllib3. It supports the shared models, and either client with an explicitly configured custom transport. Constructing a missing default transport fails with a `MissingDependencyError`. +- Public import paths are unchanged. +- Other extras keep their meaning: protocol requirements stay in the base package, and the `awscrt` extra still opts into the CRT transport. + +This describes the current single-distribution package. If the modes are ever split into separate distributions, how installs that use a custom transport migrate will be designed as part of that split. + ## `make aio-check` `make aio-check` is a verification step that runs as part of CI's static analysis suite. It detects simple mechanical violations of sync/async duality, such as placing a class in the wrong location or with the wrong name. diff --git a/packages/smithy-http/.changes/next-release/smithy-http-enhancement-3f9c2a7e5b1d4e08a6c4d2b9e7f1a053.json b/packages/smithy-http/.changes/next-release/smithy-http-enhancement-3f9c2a7e5b1d4e08a6c4d2b9e7f1a053.json new file mode 100644 index 000000000..19585293f --- /dev/null +++ b/packages/smithy-http/.changes/next-release/smithy-http-enhancement-3f9c2a7e5b1d4e08a6c4d2b9e7f1a053.json @@ -0,0 +1,4 @@ +{ + "type": "enhancement", + "description": "The errors raised when aiohttp or urllib3 is missing now say how to install the matching smithy-http extra." +} \ No newline at end of file diff --git a/packages/smithy-http/src/smithy_http/aio/aiohttp.py b/packages/smithy-http/src/smithy_http/aio/aiohttp.py index 078bb4d5b..66660165f 100644 --- a/packages/smithy-http/src/smithy_http/aio/aiohttp.py +++ b/packages/smithy-http/src/smithy_http/aio/aiohttp.py @@ -213,7 +213,8 @@ def _upload_data(source: Any, body: AsyncBytesReader | None) -> Any: def _assert_aiohttp() -> None: if not HAS_AIOHTTP: raise MissingDependencyError( - "Attempted to use aiohttp component, but aiohttp is not installed." + "Attempted to use aiohttp component, but aiohttp is not installed. " + 'Install it with: pip install "smithy-http[aiohttp]"' ) diff --git a/packages/smithy-http/src/smithy_http/urllib3.py b/packages/smithy-http/src/smithy_http/urllib3.py index 189d28003..1d2ac1767 100644 --- a/packages/smithy-http/src/smithy_http/urllib3.py +++ b/packages/smithy-http/src/smithy_http/urllib3.py @@ -262,5 +262,6 @@ def close(self) -> None: def _assert_urllib3() -> None: if not HAS_URLLIB3: raise MissingDependencyError( - "Attempted to use the urllib3 component, but urllib3 is not installed." + "Attempted to use the urllib3 component, but urllib3 is not installed. " + 'Install it with: pip install "smithy-http[urllib3]"' ) diff --git a/packages/smithy-http/tests/unit/aio/test_aiohttp.py b/packages/smithy-http/tests/unit/aio/test_aiohttp.py index 058407c9b..0651e5bd5 100644 --- a/packages/smithy-http/tests/unit/aio/test_aiohttp.py +++ b/packages/smithy-http/tests/unit/aio/test_aiohttp.py @@ -7,9 +7,11 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest +import smithy_http.aio.aiohttp from smithy_core import URI from smithy_core.aio.types import AsyncBytesReader from smithy_core.aio.utils import read_streaming_blob_async +from smithy_core.exceptions import MissingDependencyError from smithy_http import Field, Fields, HTTPRequest from smithy_http.aio.aiohttp import AIOHTTPClient, _AIOHTTPStreamingBody from smithy_http.exceptions import SmithyHTTPError @@ -36,6 +38,17 @@ def _create_request() -> HTTPRequest: ) +def test_missing_aiohttp_recommends_installing_the_extra( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr(smithy_http.aio.aiohttp, "HAS_AIOHTTP", False) + + with pytest.raises(MissingDependencyError) as error: + AIOHTTPClient() + + assert 'pip install "smithy-http[aiohttp]"' in str(error.value) + + async def test_close_closes_session() -> None: client, session = _create_client() diff --git a/packages/smithy-http/tests/unit/test_urllib3.py b/packages/smithy-http/tests/unit/test_urllib3.py index 04fb21d8d..2786aa4c6 100644 --- a/packages/smithy-http/tests/unit/test_urllib3.py +++ b/packages/smithy-http/tests/unit/test_urllib3.py @@ -9,9 +9,10 @@ from unittest.mock import Mock import pytest +import smithy_http.urllib3 import urllib3 from smithy_core import URI -from smithy_core.exceptions import ClientTimeoutError +from smithy_core.exceptions import ClientTimeoutError, MissingDependencyError from smithy_core.interfaces import is_bytes_reader from smithy_core.utils import read_streaming_blob from smithy_http import Field, Fields, HTTPRequest @@ -75,6 +76,17 @@ def test_pool_keeps_the_configured_connections_per_host( client.close() +def test_missing_urllib3_recommends_installing_the_extra( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr(smithy_http.urllib3, "HAS_URLLIB3", False) + + with pytest.raises(MissingDependencyError) as error: + Urllib3HTTPClient() + + assert 'pip install "smithy-http[urllib3]"' in str(error.value) + + def test_repeated_response_headers_are_merged_case_insensitively() -> None: pool = Mock() raw_headers = urllib3.HTTPHeaderDict() From 7383f4c849c13fcbf55b6cbe9a130ad1a1db6582 Mon Sep 17 00:00:00 2001 From: jonathan343 Date: Thu, 12 Nov 2026 18:30:51 -0500 Subject: [PATCH 2/3] Prefer uv examples and installer-neutral dependency errors --- CHANGELOG.md | 5 +++-- codegen-output/aws-json-1-0/README.md | 10 +++++++++- codegen-output/aws-json-1-1/README.md | 10 +++++++++- codegen-output/aws-query/README.md | 10 +++++++++- codegen-output/ec2-query/README.md | 10 +++++++++- .../my-handwritten-json-model/README.md | 10 +++++++++- codegen-output/my-handwritten-model/README.md | 10 +++++++++- .../non-query-compatible-rpcv2-cbor/README.md | 10 +++++++++- .../query-compatible-aws-json-1-0/README.md | 10 +++++++++- .../query-compatible-rpcv2-cbor/README.md | 10 +++++++++- codegen-output/rest-json-1/README.md | 10 +++++++++- codegen-output/rest-xml-with-namespace/README.md | 10 +++++++++- codegen-output/rest-xml/README.md | 10 +++++++++- codegen-output/rpcv2-cbor/README.md | 10 +++++++++- codegen-output/rpcv2-json/README.md | 10 +++++++++- .../python/codegen/generators/SetupGenerator.java | 15 +++++++++++++-- .../codegen/generators/SetupGeneratorTest.java | 1 + designs/async-and-sync-duality.md | 10 +++++++++- ...ancement-3f9c2a7e5b1d4e08a6c4d2b9e7f1a053.json | 2 +- .../smithy-http/src/smithy_http/aio/aiohttp.py | 2 +- packages/smithy-http/src/smithy_http/urllib3.py | 2 +- .../smithy-http/tests/unit/aio/test_aiohttp.py | 4 +++- packages/smithy-http/tests/unit/test_urllib3.py | 4 +++- 23 files changed, 161 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d9e1135e..8c9daf236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,9 @@ * Generated HTTP clients no longer install aiohttp and urllib3 by default. Install the `sync` extra for the synchronous client's default transport, the `async` extra for the asynchronous client's default transport, or both, such - as `pip install "aws_sdk_s3[sync,async]"`. Clients configured with a custom - transport work without either extra. Imports are unchanged. + as `uv add "aws_sdk_s3[sync,async]"` or + `pip install "aws_sdk_s3[sync,async]"`. Clients configured with a + custom transport work without either extra. Imports are unchanged. ### Features diff --git a/codegen-output/aws-json-1-0/README.md b/codegen-output/aws-json-1-0/README.md index a33bd7c4b..33cbc70a4 100644 --- a/codegen-output/aws-json-1-0/README.md +++ b/codegen-output/aws-json-1-0/README.md @@ -6,7 +6,15 @@ Python client for Sample Json 1.0 Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "awsjson10[sync]" +uv add "awsjson10[async]" +uv add "awsjson10[sync,async]" +``` + +Or install it with pip: ```sh pip install "awsjson10[sync]" diff --git a/codegen-output/aws-json-1-1/README.md b/codegen-output/aws-json-1-1/README.md index a924f4e1b..6e3791a54 100644 --- a/codegen-output/aws-json-1-1/README.md +++ b/codegen-output/aws-json-1-1/README.md @@ -6,7 +6,15 @@ Python client for Sample Json 1.1 Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "awsjson11[sync]" +uv add "awsjson11[async]" +uv add "awsjson11[sync,async]" +``` + +Or install it with pip: ```sh pip install "awsjson11[sync]" diff --git a/codegen-output/aws-query/README.md b/codegen-output/aws-query/README.md index 27eb1b649..1b35dc7cf 100644 --- a/codegen-output/aws-query/README.md +++ b/codegen-output/aws-query/README.md @@ -6,7 +6,15 @@ Python client for Sample Query Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "awsquery[sync]" +uv add "awsquery[async]" +uv add "awsquery[sync,async]" +``` + +Or install it with pip: ```sh pip install "awsquery[sync]" diff --git a/codegen-output/ec2-query/README.md b/codegen-output/ec2-query/README.md index 58ac2d016..81451933e 100644 --- a/codegen-output/ec2-query/README.md +++ b/codegen-output/ec2-query/README.md @@ -6,7 +6,15 @@ Python client for Sample Ec2 Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "ec2query[sync]" +uv add "ec2query[async]" +uv add "ec2query[sync,async]" +``` + +Or install it with pip: ```sh pip install "ec2query[sync]" diff --git a/codegen-output/my-handwritten-json-model/README.md b/codegen-output/my-handwritten-json-model/README.md index ad4f8bcd6..c427e2bf4 100644 --- a/codegen-output/my-handwritten-json-model/README.md +++ b/codegen-output/my-handwritten-json-model/README.md @@ -6,7 +6,15 @@ Python client for My Handwritten JSON Model ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "my_handwritten_json_model[sync]" +uv add "my_handwritten_json_model[async]" +uv add "my_handwritten_json_model[sync,async]" +``` + +Or install it with pip: ```sh pip install "my_handwritten_json_model[sync]" diff --git a/codegen-output/my-handwritten-model/README.md b/codegen-output/my-handwritten-model/README.md index c4b34a7ad..921e8c506 100644 --- a/codegen-output/my-handwritten-model/README.md +++ b/codegen-output/my-handwritten-model/README.md @@ -6,7 +6,15 @@ Python client for My Handwritten Model ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "my_handwritten_model[sync]" +uv add "my_handwritten_model[async]" +uv add "my_handwritten_model[sync,async]" +``` + +Or install it with pip: ```sh pip install "my_handwritten_model[sync]" diff --git a/codegen-output/non-query-compatible-rpcv2-cbor/README.md b/codegen-output/non-query-compatible-rpcv2-cbor/README.md index 512143a7a..d70d02be3 100644 --- a/codegen-output/non-query-compatible-rpcv2-cbor/README.md +++ b/codegen-output/non-query-compatible-rpcv2-cbor/README.md @@ -6,7 +6,15 @@ Python client for Non Query Compatible RpcV2 Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "nonquerycompatiblerpcv2cbor[sync]" +uv add "nonquerycompatiblerpcv2cbor[async]" +uv add "nonquerycompatiblerpcv2cbor[sync,async]" +``` + +Or install it with pip: ```sh pip install "nonquerycompatiblerpcv2cbor[sync]" diff --git a/codegen-output/query-compatible-aws-json-1-0/README.md b/codegen-output/query-compatible-aws-json-1-0/README.md index 8af9e27fa..322f9b04e 100644 --- a/codegen-output/query-compatible-aws-json-1-0/README.md +++ b/codegen-output/query-compatible-aws-json-1-0/README.md @@ -6,7 +6,15 @@ Python client for Query Compatible Json 1.0 Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "querycompatibleawsjson10[sync]" +uv add "querycompatibleawsjson10[async]" +uv add "querycompatibleawsjson10[sync,async]" +``` + +Or install it with pip: ```sh pip install "querycompatibleawsjson10[sync]" diff --git a/codegen-output/query-compatible-rpcv2-cbor/README.md b/codegen-output/query-compatible-rpcv2-cbor/README.md index 70ea7d556..6f5efe55b 100644 --- a/codegen-output/query-compatible-rpcv2-cbor/README.md +++ b/codegen-output/query-compatible-rpcv2-cbor/README.md @@ -6,7 +6,15 @@ Python client for Query Compatible RpcV2 Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "querycompatiblerpcv2cbor[sync]" +uv add "querycompatiblerpcv2cbor[async]" +uv add "querycompatiblerpcv2cbor[sync,async]" +``` + +Or install it with pip: ```sh pip install "querycompatiblerpcv2cbor[sync]" diff --git a/codegen-output/rest-json-1/README.md b/codegen-output/rest-json-1/README.md index 2bcafaf8b..50ca0c053 100644 --- a/codegen-output/rest-json-1/README.md +++ b/codegen-output/rest-json-1/README.md @@ -6,7 +6,15 @@ Python client for Sample Rest Json Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "restjson[sync]" +uv add "restjson[async]" +uv add "restjson[sync,async]" +``` + +Or install it with pip: ```sh pip install "restjson[sync]" diff --git a/codegen-output/rest-xml-with-namespace/README.md b/codegen-output/rest-xml-with-namespace/README.md index 25cf7815e..1ab00ba42 100644 --- a/codegen-output/rest-xml-with-namespace/README.md +++ b/codegen-output/rest-xml-with-namespace/README.md @@ -6,7 +6,15 @@ Python client for Sample Rest Xml Protocol Service With Namespace ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "restxmlwithnamespace[sync]" +uv add "restxmlwithnamespace[async]" +uv add "restxmlwithnamespace[sync,async]" +``` + +Or install it with pip: ```sh pip install "restxmlwithnamespace[sync]" diff --git a/codegen-output/rest-xml/README.md b/codegen-output/rest-xml/README.md index c62a1b2ad..4e441fc8d 100644 --- a/codegen-output/rest-xml/README.md +++ b/codegen-output/rest-xml/README.md @@ -6,7 +6,15 @@ Python client for Sample Rest Xml Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "restxml[sync]" +uv add "restxml[async]" +uv add "restxml[sync,async]" +``` + +Or install it with pip: ```sh pip install "restxml[sync]" diff --git a/codegen-output/rpcv2-cbor/README.md b/codegen-output/rpcv2-cbor/README.md index d1e45ea23..ad4cc3bfd 100644 --- a/codegen-output/rpcv2-cbor/README.md +++ b/codegen-output/rpcv2-cbor/README.md @@ -6,7 +6,15 @@ Python client for RpcV2 Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "rpcv2cbor[sync]" +uv add "rpcv2cbor[async]" +uv add "rpcv2cbor[sync,async]" +``` + +Or install it with pip: ```sh pip install "rpcv2cbor[sync]" diff --git a/codegen-output/rpcv2-json/README.md b/codegen-output/rpcv2-json/README.md index bfe65e8d4..a098e3978 100644 --- a/codegen-output/rpcv2-json/README.md +++ b/codegen-output/rpcv2-json/README.md @@ -6,7 +6,15 @@ Python client for RpcV2 JSON Protocol Service ### Installation -Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. +Install the `sync` extra for the synchronous client's default HTTP transport, the `async` extra for the asynchronous client's default HTTP transport, or both. In a project managed by uv, add the client with the extras you need: + +```sh +uv add "rpcv2json[sync]" +uv add "rpcv2json[async]" +uv add "rpcv2json[sync,async]" +``` + +Or install it with pip: ```sh pip install "rpcv2json[sync]" diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java index f86749d1b..c94f6b7d4 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java @@ -375,7 +375,14 @@ private static void writeReadme( Install the `sync` extra for the synchronous client's default HTTP \ transport, the `async` extra for the asynchronous client's default HTTP \ - transport, or both. + transport, or both. In a project managed by uv, add the client with \ + the extras you need: + + ```sh + $L + ``` + + Or install it with pip: ```sh $L @@ -385,7 +392,11 @@ private static void writeReadme( with an explicitly configured custom transport. """, installs.stream() - .map(extra -> "pip install \"" + settings.moduleName() + "[" + extra + "]\"") + .map(extra -> "uv add \"" + settings.moduleName() + "[" + extra + "]\"") + .collect(Collectors.joining("\n")), + installs.stream() + .map(extra -> "pip install \"" + settings.moduleName() + "[" + extra + + "]\"") .collect(Collectors.joining("\n"))); } diff --git a/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java b/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java index 8297e9f2e..9c4c6800a 100644 --- a/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java +++ b/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java @@ -86,6 +86,7 @@ public void movesDefaultTransportsIntoModeExtras() { var version = smithyHttp.version(); assertAll( + () -> assertTrue(readme.contains("uv add \"test_client[sync,async]\""), readme), () -> assertTrue(readme.contains("pip install \"test_client[sync,async]\""), readme), () -> assertTrue(pyproject.contains("\"smithy_http[cbor]" + version + "\"\n]"), pyproject), () -> assertTrue(pyproject.contains("sync = [\n \"smithy_http[urllib3]" + version + "\"\n]"), diff --git a/designs/async-and-sync-duality.md b/designs/async-and-sync-duality.md index e2f98b74c..88bb0a96d 100644 --- a/designs/async-and-sync-duality.md +++ b/designs/async-and-sync-duality.md @@ -4,7 +4,15 @@ Smithy-Python generated clients export two different classes that run in Python' ## Client mode extras -Generated HTTP clients expose a `sync` and an `async` extra that install each mode's default HTTP transport (urllib3 for `sync`, aiohttp for `async`): +Generated HTTP clients expose a `sync` and an `async` extra that install each mode's default HTTP transport (urllib3 for `sync`, aiohttp for `async`). In a project managed by uv: + +```sh +uv add "aws_sdk_s3[sync]" +uv add "aws_sdk_s3[async]" +uv add "aws_sdk_s3[sync,async]" +``` + +Or with pip: ```sh pip install "aws_sdk_s3[sync]" diff --git a/packages/smithy-http/.changes/next-release/smithy-http-enhancement-3f9c2a7e5b1d4e08a6c4d2b9e7f1a053.json b/packages/smithy-http/.changes/next-release/smithy-http-enhancement-3f9c2a7e5b1d4e08a6c4d2b9e7f1a053.json index 19585293f..65a2307ac 100644 --- a/packages/smithy-http/.changes/next-release/smithy-http-enhancement-3f9c2a7e5b1d4e08a6c4d2b9e7f1a053.json +++ b/packages/smithy-http/.changes/next-release/smithy-http-enhancement-3f9c2a7e5b1d4e08a6c4d2b9e7f1a053.json @@ -1,4 +1,4 @@ { "type": "enhancement", - "description": "The errors raised when aiohttp or urllib3 is missing now say how to install the matching smithy-http extra." + "description": "The errors raised when aiohttp or urllib3 is missing now identify the matching smithy-http extra without prescribing a package manager." } \ No newline at end of file diff --git a/packages/smithy-http/src/smithy_http/aio/aiohttp.py b/packages/smithy-http/src/smithy_http/aio/aiohttp.py index 66660165f..f8b9b527e 100644 --- a/packages/smithy-http/src/smithy_http/aio/aiohttp.py +++ b/packages/smithy-http/src/smithy_http/aio/aiohttp.py @@ -214,7 +214,7 @@ def _assert_aiohttp() -> None: if not HAS_AIOHTTP: raise MissingDependencyError( "Attempted to use aiohttp component, but aiohttp is not installed. " - 'Install it with: pip install "smithy-http[aiohttp]"' + 'Install the optional dependency "smithy-http[aiohttp]".' ) diff --git a/packages/smithy-http/src/smithy_http/urllib3.py b/packages/smithy-http/src/smithy_http/urllib3.py index 1d2ac1767..f78d1f0ea 100644 --- a/packages/smithy-http/src/smithy_http/urllib3.py +++ b/packages/smithy-http/src/smithy_http/urllib3.py @@ -263,5 +263,5 @@ def _assert_urllib3() -> None: if not HAS_URLLIB3: raise MissingDependencyError( "Attempted to use the urllib3 component, but urllib3 is not installed. " - 'Install it with: pip install "smithy-http[urllib3]"' + 'Install the optional dependency "smithy-http[urllib3]".' ) diff --git a/packages/smithy-http/tests/unit/aio/test_aiohttp.py b/packages/smithy-http/tests/unit/aio/test_aiohttp.py index 0651e5bd5..3c4d5455e 100644 --- a/packages/smithy-http/tests/unit/aio/test_aiohttp.py +++ b/packages/smithy-http/tests/unit/aio/test_aiohttp.py @@ -46,7 +46,9 @@ def test_missing_aiohttp_recommends_installing_the_extra( with pytest.raises(MissingDependencyError) as error: AIOHTTPClient() - assert 'pip install "smithy-http[aiohttp]"' in str(error.value) + message = str(error.value) + assert 'Install the optional dependency "smithy-http[aiohttp]".' in message + assert "pip install" not in message async def test_close_closes_session() -> None: diff --git a/packages/smithy-http/tests/unit/test_urllib3.py b/packages/smithy-http/tests/unit/test_urllib3.py index 2786aa4c6..5c5a45c12 100644 --- a/packages/smithy-http/tests/unit/test_urllib3.py +++ b/packages/smithy-http/tests/unit/test_urllib3.py @@ -84,7 +84,9 @@ def test_missing_urllib3_recommends_installing_the_extra( with pytest.raises(MissingDependencyError) as error: Urllib3HTTPClient() - assert 'pip install "smithy-http[urllib3]"' in str(error.value) + message = str(error.value) + assert 'Install the optional dependency "smithy-http[urllib3]".' in message + assert "pip install" not in message def test_repeated_response_headers_are_merged_case_insensitively() -> None: From 1ee544c5ec36aa6afb6125b840e999361bb9c50e Mon Sep 17 00:00:00 2001 From: jonathan343 Date: Thu, 12 Nov 2026 19:31:31 -0500 Subject: [PATCH 3/3] Remove redundant error-message assertions --- packages/smithy-http/tests/unit/aio/test_aiohttp.py | 1 - packages/smithy-http/tests/unit/test_urllib3.py | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/smithy-http/tests/unit/aio/test_aiohttp.py b/packages/smithy-http/tests/unit/aio/test_aiohttp.py index 3c4d5455e..7d78d12a7 100644 --- a/packages/smithy-http/tests/unit/aio/test_aiohttp.py +++ b/packages/smithy-http/tests/unit/aio/test_aiohttp.py @@ -48,7 +48,6 @@ def test_missing_aiohttp_recommends_installing_the_extra( message = str(error.value) assert 'Install the optional dependency "smithy-http[aiohttp]".' in message - assert "pip install" not in message async def test_close_closes_session() -> None: diff --git a/packages/smithy-http/tests/unit/test_urllib3.py b/packages/smithy-http/tests/unit/test_urllib3.py index 5c5a45c12..04b61454d 100644 --- a/packages/smithy-http/tests/unit/test_urllib3.py +++ b/packages/smithy-http/tests/unit/test_urllib3.py @@ -86,7 +86,6 @@ def test_missing_urllib3_recommends_installing_the_extra( message = str(error.value) assert 'Install the optional dependency "smithy-http[urllib3]".' in message - assert "pip install" not in message def test_repeated_response_headers_are_merged_case_insensitively() -> None: