diff --git a/CHANGELOG.md b/CHANGELOG.md index 14b923414..997de51e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,28 @@ ## Unreleased +## v0.4.0 + ### Breaking Changes -* Removed the `http_client` config option in favor of the generic `transport`. +* Renamed generated AWS service clients to include `Async` (e.g. + `BedrockRuntimeClient` is now `AsyncBedrockRuntimeClient`) to match the + Python community convention for async clients. For clients that were already + published under the unprefixed name, the old name remains available as a + deprecated alias that emits a `DeprecationWarning` and will be removed in an + upcoming release. The unprefixed name will later be reintroduced for + synchronous clients. -### Features + diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsServiceIdIntegration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsServiceIdIntegration.java index 182d0184f..cb32a3005 100644 --- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsServiceIdIntegration.java +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsServiceIdIntegration.java @@ -4,6 +4,7 @@ */ package software.amazon.smithy.python.aws.codegen; +import java.util.Set; import software.amazon.smithy.aws.traits.ServiceTrait; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; @@ -11,10 +12,30 @@ import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.python.codegen.PythonSettings; +import software.amazon.smithy.python.codegen.SymbolProperties; import software.amazon.smithy.python.codegen.integrations.PythonIntegration; import software.amazon.smithy.utils.StringUtils; public final class AwsServiceIdIntegration implements PythonIntegration { + + /** + * SDK IDs of the AWS clients that were published under the unprefixed + * {@code Client} name before the {@code Async} prefix was adopted. + * + *

Only these clients generate a deprecated alias for the old name so that + * existing imports keep working. New clients are generated with the + * {@code Async}-prefixed name from the start and need no alias. This set can + * be removed once the aliases are dropped. + */ + private static final Set LEGACY_ALIAS_SDK_IDS = Set.of( + "Bedrock Runtime", + "ConnectHealth", + "Lex Runtime V2", + "Polly", + "QBusiness", + "SageMaker Runtime HTTP2", + "Transcribe Streaming"); + @Override public SymbolProvider decorateSymbolProvider(Model model, PythonSettings settings, SymbolProvider symbolProvider) { return new ServiceIdSymbolProvider(symbolProvider); @@ -33,8 +54,14 @@ public Symbol toSymbol(Shape shape) { Symbol symbol = this.delegate.toSymbol(shape); if (shape.isServiceShape() && shape.hasTrait(ServiceTrait.class)) { var serviceTrait = shape.expectTrait(ServiceTrait.class); - var serviceName = StringUtils.capitalize(serviceTrait.getSdkId() + "Client").replace(" ", ""); - symbol = symbol.toBuilder().name(serviceName).build(); + var baseClientName = StringUtils.capitalize(serviceTrait.getSdkId() + "Client").replace(" ", ""); + var symbolBuilder = symbol.toBuilder().name("Async" + baseClientName); + // Only clients that already shipped under the unprefixed name get a + // backwards-compatible alias; new clients start life Async-prefixed. + if (LEGACY_ALIAS_SDK_IDS.contains(serviceTrait.getSdkId())) { + symbolBuilder.putProperty(SymbolProperties.DEPRECATED_ALIAS, baseClientName); + } + symbol = symbolBuilder.build(); } return symbol; } diff --git a/codegen/aws/core/src/test/java/software/amazon/smithy/python/aws/codegen/AwsServiceIdIntegrationTest.java b/codegen/aws/core/src/test/java/software/amazon/smithy/python/aws/codegen/AwsServiceIdIntegrationTest.java new file mode 100644 index 000000000..ee9e107b0 --- /dev/null +++ b/codegen/aws/core/src/test/java/software/amazon/smithy/python/aws/codegen/AwsServiceIdIntegrationTest.java @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.python.aws.codegen; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.python.codegen.PythonSettings; +import software.amazon.smithy.python.codegen.PythonSymbolProvider; +import software.amazon.smithy.python.codegen.SymbolProperties; + +public class AwsServiceIdIntegrationTest { + + private static final String NS = "smithy.example"; + + @Test + public void testLegacyClientGetsAsyncNameWithDeprecatedAlias() { + var symbol = toServiceSymbol("Bedrock Runtime"); + + assertEquals("AsyncBedrockRuntimeClient", symbol.getName()); + assertEquals("BedrockRuntimeClient", symbol.expectProperty(SymbolProperties.DEPRECATED_ALIAS)); + } + + @Test + public void testNewClientGetsAsyncNameWithoutDeprecatedAlias() { + var symbol = toServiceSymbol("Weather"); + + assertEquals("AsyncWeatherClient", symbol.getName()); + assertFalse(symbol.getProperty(SymbolProperties.DEPRECATED_ALIAS).isPresent()); + } + + private static Symbol toServiceSymbol(String sdkId) { + Model model = Model.assembler() + .discoverModels(AwsServiceIdIntegrationTest.class.getClassLoader()) + .addUnparsedModel("test.smithy", """ + $version: "2" + namespace smithy.example + + use aws.api#service + + @service(sdkId: "%s") + service TestService { + version: "2024-01-01" + } + """.formatted(sdkId)) + .assemble() + .unwrap(); + PythonSettings settings = PythonSettings.builder() + .service(ShapeId.from(NS + "#TestService")) + .moduleName("test_client") + .moduleVersion("0.0.1") + .build(); + var integration = new AwsServiceIdIntegration(); + var provider = integration.decorateSymbolProvider(model, settings, new PythonSymbolProvider(model, settings)); + return provider.toSymbol(model.expectShape(ShapeId.from(NS + "#TestService"))); + } +} diff --git a/codegen/build.gradle.kts b/codegen/build.gradle.kts index df6e5391c..af380a8b7 100644 --- a/codegen/build.gradle.kts +++ b/codegen/build.gradle.kts @@ -15,5 +15,5 @@ allprojects { group = "software.amazon.smithy.python" - version = "0.3.1" + version = "0.4.0" } diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ClientGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ClientGenerator.java index 6a3f65c4a..e9f5d7a35 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ClientGenerator.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ClientGenerator.java @@ -105,6 +105,30 @@ def __init__(self, config: $1T | None = None, plugins: list[$2T] | None = None): } } }); + + serviceSymbol.getProperty(SymbolProperties.DEPRECATED_ALIAS).ifPresent(alias -> { + writer.addStdlibImport("typing", "TYPE_CHECKING"); + writer.addStdlibImport("typing", "Any"); + writer.addStdlibImport("warnings"); + writer.write(""" + + if TYPE_CHECKING: + # Deprecated alias for backwards compatibility, to be removed. + $1L = $2L + + + def __getattr__(name: str) -> Any: + if name == $1S: + warnings.warn( + "$1L is deprecated, use $2L instead. " + "This alias will be removed in a future version.", + DeprecationWarning, + stacklevel=2, + ) + return $2L + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + """, alias, serviceSymbol.getName()); + }); } private void writeDefaultPlugins(PythonWriter writer, Collection plugins) { diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/SymbolProperties.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/SymbolProperties.java index 992b8af21..d41f86db6 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/SymbolProperties.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/SymbolProperties.java @@ -77,5 +77,11 @@ public final class SymbolProperties { */ public static final Property IMPORTABLE = Property.named("nonImportable"); + /** + * Contains a deprecated name for the symbol that is generated as an alias + * for backwards compatibility. This is only used for services. + */ + public static final Property DEPRECATED_ALIAS = Property.named("deprecatedAlias"); + private SymbolProperties() {} }