Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<!--
TODO: Backfill pre-0.4.0 codegen releases into this file. Prior changes were
accumulated under "Unreleased" and never cut over per release, so the following
already shipped (in-tree as of the 0.3.0 codegen release) and need to be placed
under their correct version section:

* Removed code-generated protocol implementations in favor of hand-written
implementations based on schemas.
* Moved documentation for structure members into doc strings after the member's
dataclass field declaration.
* (Breaking) Removed the `http_client` config option in favor of the generic
`transport`.
* (Feature) Removed code-generated protocol implementations in favor of
hand-written implementations based on schemas.
* (Feature) Moved documentation for structure members into doc strings after
the member's dataclass field declaration.
-->
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,38 @@
*/
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;
import software.amazon.smithy.model.Model;
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 <SdkId>Client} name before the {@code Async} prefix was adopted.
*
* <p>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<String> 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);
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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")));
}
}
2 changes: 1 addition & 1 deletion codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@

allprojects {
group = "software.amazon.smithy.python"
version = "0.3.1"
version = "0.4.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<SymbolReference> plugins) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,11 @@ public final class SymbolProperties {
*/
public static final Property<Boolean> 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<String> DEPRECATED_ALIAS = Property.named("deprecatedAlias");

private SymbolProperties() {}
}
Loading