diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsAuthIntegration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsAuthIntegration.java index 8358c54be..707429954 100644 --- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsAuthIntegration.java +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsAuthIntegration.java @@ -7,7 +7,6 @@ import static software.amazon.smithy.python.aws.codegen.AwsConfiguration.REGION; import java.util.List; -import java.util.Set; import software.amazon.smithy.aws.traits.auth.SigV4Trait; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.model.shapes.ServiceShape; @@ -16,6 +15,7 @@ import software.amazon.smithy.python.codegen.CodegenUtils; import software.amazon.smithy.python.codegen.ConfigProperty; import software.amazon.smithy.python.codegen.GenerationContext; +import software.amazon.smithy.python.codegen.RuntimeTypes; import software.amazon.smithy.python.codegen.SmithyPythonDependency; import software.amazon.smithy.python.codegen.integrations.AuthScheme; import software.amazon.smithy.python.codegen.integrations.PythonIntegration; @@ -98,20 +98,21 @@ public void customize(GenerationContext context) { // must be accounted for. context.writerDelegator().useFileWriter(resolver.getDefinitionFile(), resolver.getNamespace(), writer -> { writer.addDependency(SmithyPythonDependency.SMITHY_HTTP); - writer.addImport("smithy_core.interfaces.auth", "AuthOption", "AuthOptionProtocol"); - writer.addImports("smithy_core.auth", Set.of("AuthOption", "AuthParams")); - writer.addImport("smithy_core.shapes", "ShapeID"); writer.pushState(); writer.write(""" - def $1L(auth_params: AuthParams[Any, Any]) -> AuthOptionProtocol | None: - return AuthOption( - scheme_id=ShapeID($2S), + def $1L(auth_params: $2T[Any, Any]) -> $3T | None: + return $4T( + scheme_id=$5T($6S), identity_properties={}, # type: ignore signer_properties={} # type: ignore ) """, SIGV4_OPTION_GENERATOR_NAME, + RuntimeTypes.AUTH_PARAMS, + RuntimeTypes.AUTH_OPTION_INTERFACE, + RuntimeTypes.AUTH_OPTION, + RuntimeTypes.SHAPE_ID, SigV4Trait.ID.toString()); writer.popState(); }); diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsQueryProtocolGenerator.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsQueryProtocolGenerator.java index f02c5e815..d03463b45 100644 --- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsQueryProtocolGenerator.java +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsQueryProtocolGenerator.java @@ -47,12 +47,11 @@ public ApplicationProtocol getApplicationProtocol(GenerationContext context) { @Override public void initializeProtocol(GenerationContext context, PythonWriter writer) { writer.addDependency(AwsPythonDependency.SMITHY_AWS_CORE.withOptionalDependencies("xml")); - writer.addImport("smithy_aws_core.aio.protocols", "AwsQueryClientProtocol"); var service = context.settings().service(context.model()); var serviceSymbol = context.symbolProvider().toSymbol(service); var serviceSchema = serviceSymbol.expectProperty(SymbolProperties.SCHEMA); var version = service.getVersion(); - writer.write("AwsQueryClientProtocol($T, $S)", serviceSchema, version); + writer.write("$1T($2T, $3S)", AwsRuntimeTypes.AWS_QUERY_CLIENT_PROTOCOL, serviceSchema, version); } @Override diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsRuntimeTypes.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsRuntimeTypes.java new file mode 100644 index 000000000..8d78d0809 --- /dev/null +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsRuntimeTypes.java @@ -0,0 +1,47 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.python.aws.codegen; + +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.utils.SmithyUnstableApi; + +/** + * Pre-defined Symbol constants for AWS-specific framework types used across generators. + * + *
Using these symbols with the {@code $T} formatter ensures that all framework type
+ * references participate in the writer's collision detection system.
+ */
+@SmithyUnstableApi
+public final class AwsRuntimeTypes {
+
+ // smithy_aws_core.aio.protocols
+ public static final Symbol REST_JSON_CLIENT_PROTOCOL = createSymbol(
+ "aio.protocols",
+ "RestJsonClientProtocol");
+ public static final Symbol AWS_QUERY_CLIENT_PROTOCOL = createSymbol(
+ "aio.protocols",
+ "AwsQueryClientProtocol");
+
+ // smithy_aws_core.identity
+ public static final Symbol STATIC_CREDENTIALS_RESOLVER = createSymbol(
+ "identity",
+ "StaticCredentialsResolver");
+
+ // smithy_aws_core.endpoints.standard_regional
+ public static final Symbol STANDARD_REGIONAL_ENDPOINTS_RESOLVER = createSymbol(
+ "endpoints.standard_regional",
+ "StandardRegionalEndpointsResolver");
+
+ private AwsRuntimeTypes() {}
+
+ private static Symbol createSymbol(String module, String name) {
+ var namespace = module.isEmpty() ? "smithy_aws_core" : "smithy_aws_core." + module;
+ return Symbol.builder()
+ .name(name)
+ .namespace(namespace, ".")
+ .addDependency(AwsPythonDependency.SMITHY_AWS_CORE)
+ .build();
+ }
+}
diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsStandardRegionalEndpointsIntegration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsStandardRegionalEndpointsIntegration.java
index c290468b4..346f94fe3 100644
--- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsStandardRegionalEndpointsIntegration.java
+++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsStandardRegionalEndpointsIntegration.java
@@ -59,11 +59,9 @@ public void write(PythonWriter writer, String previousText, InitDefaultEndpointR
.map(ServiceTrait::getEndpointPrefix)
.orElse(context.settings().service().getName());
- writer.addImport("smithy_aws_core.endpoints.standard_regional",
- "StandardRegionalEndpointsResolver",
- "_RegionalResolver");
writer.write(
- "self.endpoint_resolver = endpoint_resolver or _RegionalResolver(endpoint_prefix=$S)",
+ "self.endpoint_resolver = endpoint_resolver or $1T(endpoint_prefix=$2S)",
+ AwsRuntimeTypes.STANDARD_REGIONAL_ENDPOINTS_RESOLVER,
endpointPrefix);
}
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 a56aff818..6a3f65c4a 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
@@ -50,6 +50,7 @@ public void run() {
private void generateService(PythonWriter writer) {
var serviceSymbol = symbolProvider.toSymbol(service);
+ writer.addLocallyDefinedSymbol(serviceSymbol);
var configSymbol = CodegenUtils.getConfigSymbol(context.settings());
var pluginSymbol = CodegenUtils.getPluginSymbol(context.settings());
writer.addLogger();
@@ -71,7 +72,6 @@ private void generateService(PythonWriter writer) {
}
writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
- writer.addImport("smithy_core.retries", "RetryStrategyResolver");
writer.write("""
def __init__(self, config: $1T | None = None, plugins: list[$2T] | None = None):
$3C
@@ -86,12 +86,13 @@ def __init__(self, config: $1T | None = None, plugins: list[$2T] | None = None):
for plugin in client_plugins:
plugin(self._config)
- self._retry_strategy_resolver = RetryStrategyResolver()
+ self._retry_strategy_resolver = $5T()
""",
configSymbol,
pluginSymbol,
writer.consumer(w -> writeConstructorDocs(w, serviceSymbol.getName())),
- writer.consumer(w -> writeDefaultPlugins(w, defaultPlugins)));
+ writer.consumer(w -> writeDefaultPlugins(w, defaultPlugins)),
+ RuntimeTypes.RETRY_STRATEGY_RESOLVER);
var topDownIndex = TopDownIndex.of(model);
var eventStreamIndex = EventStreamIndex.of(model);
@@ -209,18 +210,11 @@ private void writeSharedOperationInit(
}
writer.putContext("operation", symbolProvider.toSymbol(operation));
- writer.addImport("smithy_core.aio.client", "ClientCall");
- writer.addImport("smithy_core.interceptors", "InterceptorChain");
- writer.addImport("smithy_core.types", "TypedProperties");
- writer.addImport("smithy_core.aio.client", "RequestPipeline");
- writer.addImport("smithy_core.exceptions", "ExpectationNotMetError");
- writer.addImport("smithy_core.retries", "RetryStrategyOptions");
- writer.addImport("smithy_core.interfaces.retries", "RetryStrategy");
writer.addStdlibImport("copy", "deepcopy");
writer.write("""
operation_plugins: list[Plugin] = [
- $C
+ $1C
]
if plugins:
operation_plugins.extend(plugins)
@@ -228,27 +222,33 @@ private void writeSharedOperationInit(
for plugin in operation_plugins:
plugin(config)
if config.protocol is None or config.transport is None:
- raise ExpectationNotMetError("protocol and transport MUST be set on the config to make calls.")
+ raise $2T("protocol and transport MUST be set on the config to make calls.")
retry_strategy = await self._retry_strategy_resolver.resolve_retry_strategy(
retry_strategy=config.retry_strategy
)
- pipeline = RequestPipeline(
+ pipeline = $3T(
protocol=config.protocol,
transport=config.transport
)
- call = ClientCall(
+ call = $4T(
input=input,
operation=${operation:T},
- context=TypedProperties({"config": config}),
- interceptor=InterceptorChain(config.interceptors),
+ context=$5T({"config": config}),
+ interceptor=$6T(config.interceptors),
auth_scheme_resolver=config.auth_scheme_resolver,
supported_auth_schemes=config.auth_schemes,
endpoint_resolver=config.endpoint_resolver,
retry_strategy=retry_strategy,
)
- """, writer.consumer(w -> writeDefaultPlugins(w, defaultPlugins)));
+ """,
+ writer.consumer(w -> writeDefaultPlugins(w, defaultPlugins)),
+ RuntimeTypes.EXPECTATION_NOT_MET_ERROR,
+ RuntimeTypes.REQUEST_PIPELINE,
+ RuntimeTypes.CLIENT_CALL,
+ RuntimeTypes.TYPED_PROPERTIES,
+ RuntimeTypes.INTERCEPTOR_CHAIN);
}
@@ -291,14 +291,14 @@ private void generateEventStreamOperation(PythonWriter writer, OperationShape op
// the type declaration so much that it's no better than Any.
if (inputStreamSymbol.isPresent()) {
if (outputStreamSymbol.isPresent()) {
- writer.addImport("smithy_core.aio.eventstream", "DuplexEventStream");
+ writer.putContext("duplexEventStream", RuntimeTypes.DUPLEX_EVENT_STREAM);
var outputDocs = "A `DuplexEventStream` for bidirectional streaming.";
writer.write("""
async def ${operationName:L}(
self,
input: ${input:T},
plugins: list[${plugin:T}] | None = None
- ) -> DuplexEventStream[${inputStream:T}, ${outputStream:T}, ${output:T}]:
+ ) -> ${duplexEventStream:T}[${inputStream:T}, ${outputStream:T}, ${output:T}]:
${C|}
return await pipeline.duplex_stream(
call,
@@ -309,14 +309,14 @@ private void generateEventStreamOperation(PythonWriter writer, OperationShape op
""",
writer.consumer(w -> writeSharedOperationInit(w, operation, input, output, outputDocs)));
} else {
- writer.addImport("smithy_core.aio.eventstream", "InputEventStream");
+ writer.putContext("inputEventStream", RuntimeTypes.INPUT_EVENT_STREAM);
var outputDocs = "An `InputEventStream` for client-to-server streaming.";
writer.write("""
async def ${operationName:L}(
self,
input: ${input:T},
plugins: list[${plugin:T}] | None = None
- ) -> InputEventStream[${inputStream:T}, ${output:T}]:
+ ) -> ${inputEventStream:T}[${inputStream:T}, ${output:T}]:
${C|}
return await pipeline.input_stream(
call,
@@ -326,14 +326,14 @@ private void generateEventStreamOperation(PythonWriter writer, OperationShape op
writer.consumer(w -> writeSharedOperationInit(w, operation, input, output, outputDocs)));
}
} else {
- writer.addImport("smithy_core.aio.eventstream", "OutputEventStream");
+ writer.putContext("outputEventStream", RuntimeTypes.OUTPUT_EVENT_STREAM);
var outputDocs = "An `OutputEventStream` for server-to-client streaming.";
writer.write("""
async def ${operationName:L}(
self,
input: ${input:T},
plugins: list[${plugin:T}] | None = None
- ) -> OutputEventStream[${outputStream:T}, ${output:T}]:
+ ) -> ${outputEventStream:T}[${outputStream:T}, ${output:T}]:
${C|}
return await pipeline.output_stream(
call,
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpAuthGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpAuthGenerator.java
index 69780212a..ebf4269f0 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpAuthGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpAuthGenerator.java
@@ -69,21 +69,22 @@ private void generateAuthSchemeResolver(
.toList();
writer.pushState(new GenerateHttpAuthSchemeResolverSection(resolvedAuthSchemes));
+ writer.addLocallyDefinedSymbol(resolverSymbol);
writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
writer.addDependency(SmithyPythonDependency.SMITHY_HTTP);
- writer.addImport("smithy_core.interfaces.auth", "AuthOption", "AuthOptionProtocol");
- writer.addImport("smithy_core.auth", "AuthParams");
writer.addStdlibImport("typing", "Any");
writer.write("""
class $1L:
- def resolve_auth_scheme(self, auth_parameters: AuthParams[Any, Any]) -> list[AuthOptionProtocol]:
- auth_options: list[AuthOptionProtocol] = []
+ def resolve_auth_scheme(self, auth_parameters: $2T[Any, Any]) -> list[$3T]:
+ auth_options: list[$3T] = []
- ${2C|}
- ${3C|}
+ ${4C|}
+ ${5C|}
""",
resolverSymbol.getName(),
+ RuntimeTypes.AUTH_PARAMS,
+ RuntimeTypes.AUTH_OPTION_INTERFACE,
writer.consumer(w -> writeOperationAuthOptions(w, supportedAuthSchemes)),
writer.consumer(w -> writeAuthOptions(w, resolvedAuthSchemes)));
writer.popState();
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpProtocolTestGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpProtocolTestGenerator.java
index c432847bb..169d2c035 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpProtocolTestGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/HttpProtocolTestGenerator.java
@@ -182,13 +182,12 @@ private void generateRequestTest(OperationShape operation, HttpRequestTestCase t
} else {
path = "";
}
- writer.addImport("smithy_core.retries", "SimpleRetryStrategy");
writeClientBlock(context.symbolProvider().toSymbol(service), testCase, Optional.of(() -> {
writer.write("""
config = $T(
endpoint_uri="https://$L/$L",
transport = $T(),
- retry_strategy=SimpleRetryStrategy(max_attempts=1),
+ retry_strategy=$T(max_attempts=1),
${C|}
)
""",
@@ -196,6 +195,7 @@ private void generateRequestTest(OperationShape operation, HttpRequestTestCase t
host,
path,
REQUEST_TEST_ASYNC_HTTP_CLIENT_SYMBOL,
+ RuntimeTypes.SIMPLE_RETRY_STRATEGY,
(Runnable) this::writeSigV4TestConfig);
}));
@@ -403,8 +403,7 @@ private void writeRequestBodyComparison(HttpMessageTestCase testCase, PythonWrit
return;
}
writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
- writer.addImport("smithy_core.aio.types", "AsyncBytesReader");
- writer.write("actual_body_content = await AsyncBytesReader(actual.body or b'').read()");
+ writer.write("actual_body_content = await $T(actual.body or b'').read()", RuntimeTypes.ASYNC_BYTES_READER);
writer.write("expected_body_content = b$S", testCase.getBody().get());
compareMediaBlob(testCase, writer);
}
@@ -563,13 +562,11 @@ private void assertResponseEqual(HttpMessageTestCase testCase, Shape operationOr
var memberName = context.symbolProvider().toMemberName(member);
if (member.equals(streamingMember)) {
writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
- writer.addImport("smithy_core.aio.interfaces", "AsyncByteStream");
- writer.addImport("smithy_core.aio.types", "AsyncBytesReader");
writer.write("""
- assert isinstance(actual.$1L, AsyncByteStream)
+ assert isinstance(actual.$1L, $2T)
actual_body_content = await actual.$1L.read()
- expected_body_content = await AsyncBytesReader(expected.$1L).read()
- """, memberName);
+ expected_body_content = await $3T(expected.$1L).read()
+ """, memberName, RuntimeTypes.ASYNC_BYTE_STREAM, RuntimeTypes.ASYNC_BYTES_READER);
compareMediaBlob(testCase, writer);
continue;
}
@@ -628,52 +625,46 @@ private void writeSigV4TestConfig() {
if (!service.hasTrait(SigV4Trait.class)) {
return;
}
- writer.addImport("smithy_aws_core.identity", "StaticCredentialsResolver");
writer.write("""
region="us-east-1",
aws_access_key_id="test-access-key-id",
aws_secret_access_key="test-secret-access-key",
- aws_credentials_identity_resolver=StaticCredentialsResolver(),
- """);
+ aws_credentials_identity_resolver=$T(),
+ """, RuntimeTypes.STATIC_CREDENTIALS_RESOLVER);
}
private void writeUtilStubs(Symbol serviceSymbol) {
LOGGER.fine(String.format("Writing utility stubs for %s : %s", serviceSymbol.getName(), protocol.getName()));
+ writer.addLocallyDefinedSymbol(TEST_HTTP_SERVICE_ERR_SYMBOL);
+ writer.addLocallyDefinedSymbol(REQUEST_TEST_ASYNC_HTTP_CLIENT_SYMBOL);
+ writer.addLocallyDefinedSymbol(RESPONSE_TEST_ASYNC_HTTP_CLIENT_SYMBOL);
writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
writer.addDependency(SmithyPythonDependency.SMITHY_HTTP);
- writer.addImports("smithy_http.interfaces",
- Set.of(
- "HTTPRequestConfiguration",
- "HTTPClientConfiguration"));
- writer.addImports("smithy_http.aio.interfaces", Set.of("HTTPRequest", "HTTPResponse"));
- writer.addImport("smithy_http", "tuples_to_fields");
- writer.addImport("smithy_http.aio", "HTTPResponse", "_HTTPResponse");
- writer.addImport("smithy_core.aio.utils", "async_list");
writer.write("""
class $1L($2T):
""\"A test error that subclasses the service-error for protocol tests.""\"
- def __init__(self, request: HTTPRequest):
+ def __init__(self, request: $3T):
self.request = request
- class $3L:
+ class $4L:
""\"An asynchronous HTTP client solely for testing purposes.""\"
TIMEOUT_EXCEPTIONS = ()
- def __init__(self, *, client_config: HTTPClientConfiguration | None = None):
+ def __init__(self, *, client_config: $5T | None = None):
self._client_config = client_config
async def send(
- self, request: HTTPRequest, *, request_config: HTTPRequestConfiguration | None = None
- ) -> HTTPResponse:
+ self, request: $3T, *, request_config: $6T | None = None
+ ) -> $7T:
# Raise the exception with the request object to bypass actual request handling
raise $1T(request)
- class $4L:
+ class $8L:
""\"An asynchronous HTTP client solely for testing purposes.""\"
TIMEOUT_EXCEPTIONS = ()
@@ -681,30 +672,37 @@ class $4L:
def __init__(
self,
*,
- client_config: HTTPClientConfiguration | None = None,
+ client_config: $5T | None = None,
status: int = 200,
headers: list[tuple[str, str]] | None = None,
body: bytes = b"",
):
self._client_config = client_config
self.status = status
- self.fields = tuples_to_fields(headers or [])
+ self.fields = $9T(headers or [])
self.body = body
async def send(
- self, request: HTTPRequest, *, request_config: HTTPRequestConfiguration | None = None
- ) -> _HTTPResponse:
+ self, request: $3T, *, request_config: $6T | None = None
+ ) -> ${10T}:
# Pre-construct the response from the request and return it
- return _HTTPResponse(
+ return ${10T}(
status=self.status,
fields=self.fields,
- body=async_list([self.body]),
+ body=${11T}([self.body]),
)
""",
TEST_HTTP_SERVICE_ERR_SYMBOL,
CodegenUtils.getServiceError(context.settings()),
+ RuntimeTypes.HTTP_REQUEST,
REQUEST_TEST_ASYNC_HTTP_CLIENT_SYMBOL,
- RESPONSE_TEST_ASYNC_HTTP_CLIENT_SYMBOL);
+ RuntimeTypes.HTTP_CLIENT_CONFIGURATION,
+ RuntimeTypes.HTTP_REQUEST_CONFIGURATION,
+ RuntimeTypes.HTTP_RESPONSE,
+ RESPONSE_TEST_ASYNC_HTTP_CLIENT_SYMBOL,
+ RuntimeTypes.TUPLES_TO_FIELDS,
+ RuntimeTypes.HTTP_RESPONSE_IMPL,
+ RuntimeTypes.ASYNC_LIST);
}
/**
@@ -726,8 +724,8 @@ public Void arrayNode(ArrayNode node) {
if (inputShape.isListShape()) {
var target = model.expectShape(inputShape.asListShape().get().getMember().getTarget());
if (target.isDocumentShape()) {
- writer.addImport("smithy_core.documents", "Document");
- openList = "Document([";
+ var docName = writer.format("$T", RuntimeTypes.DOCUMENT);
+ openList = docName + "([";
closeList = "])";
}
}
@@ -832,8 +830,8 @@ private Void structureMemberShapes(StructureShape container, ObjectNode node) {
var formatString = "$L = $C,";
if (targetShape.isDocumentShape()) {
- writer.addImport("smithy_core.documents", "Document");
- formatString = "$L = Document($C),";
+ var docName = writer.format("$T", RuntimeTypes.DOCUMENT);
+ formatString = "$L = " + docName + "($C),";
}
writer.write(formatString,
@@ -860,8 +858,8 @@ private Void mapShape(MapShape shape, ObjectNode node) {
var formatString = "$S: $C,";
if (targetShape.isDocumentShape()) {
- writer.addImport("smithy_core.documents", "Document");
- formatString = "$S: Document($C),";
+ var docName = writer.format("$T", RuntimeTypes.DOCUMENT);
+ formatString = "$S: " + docName + "($C),";
}
writer.write(formatString,
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSymbolProvider.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSymbolProvider.java
index a44f7cb91..654d005cb 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSymbolProvider.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSymbolProvider.java
@@ -6,7 +6,9 @@
import static java.lang.String.format;
+import java.util.HashSet;
import java.util.Locale;
+import java.util.Set;
import java.util.logging.Logger;
import software.amazon.smithy.codegen.core.ReservedWordSymbolProvider;
import software.amazon.smithy.codegen.core.ReservedWordsBuilder;
@@ -70,6 +72,7 @@ public final class PythonSymbolProvider implements SymbolProvider, ShapeVisitor<
private final ReservedWordSymbolProvider.Escaper errorMemberEscaper;
private final PythonSettings settings;
private final ServiceShape service;
+ private final Set Using these symbols with the {@code $T} formatter ensures that all framework type
+ * references participate in the writer's collision detection system. When a service model
+ * defines a shape with the same name as a framework type, the writer will automatically
+ * alias the import to avoid shadowing.
+ */
+@SmithyUnstableApi
+public final class RuntimeTypes {
+
+ // smithy_core.schemas
+ public static final Symbol SCHEMA = createSymbol("schemas", "Schema", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol API_OPERATION =
+ createSymbol("schemas", "APIOperation", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.shapes
+ public static final Symbol SHAPE_ID = createSymbol("shapes", "ShapeID", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol SHAPE_TYPE = createSymbol("shapes", "ShapeType", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.serializers
+ public static final Symbol SHAPE_SERIALIZER =
+ createSymbol("serializers", "ShapeSerializer", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.deserializers
+ public static final Symbol SHAPE_DESERIALIZER =
+ createSymbol("deserializers", "ShapeDeserializer", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.documents
+ public static final Symbol DOCUMENT = createSymbol("documents", "Document", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol TYPE_REGISTRY =
+ createSymbol("documents", "TypeRegistry", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.exceptions
+ public static final Symbol MODELED_ERROR =
+ createSymbol("exceptions", "ModeledError", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol SERIALIZATION_ERROR =
+ createSymbol("exceptions", "SerializationError", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol EXPECTATION_NOT_MET_ERROR =
+ createSymbol("exceptions", "ExpectationNotMetError", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.traits
+ public static final Symbol TRAIT = createSymbol("traits", "Trait", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol API_KEY_LOCATION =
+ createSymbol("traits", "APIKeyLocation", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.auth
+ public static final Symbol AUTH_PARAMS = createSymbol("auth", "AuthParams", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol AUTH_OPTION = createSymbol("auth", "AuthOption", SmithyPythonDependency.SMITHY_CORE);
+
+ // Note: AuthOption from smithy_core.interfaces.auth has the same simple name as
+ // AuthOption from smithy_core.auth. The collision resolver will automatically
+ // disambiguate them with module-based aliases.
+ public static final Symbol AUTH_OPTION_INTERFACE =
+ createSymbol("interfaces.auth", "AuthOption", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.retries
+ public static final Symbol RETRY_STRATEGY_RESOLVER =
+ createSymbol("retries", "RetryStrategyResolver", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol RETRY_STRATEGY_OPTIONS =
+ createSymbol("retries", "RetryStrategyOptions", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol SIMPLE_RETRY_STRATEGY =
+ createSymbol("retries", "SimpleRetryStrategy", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.interfaces.retries
+ public static final Symbol RETRY_STRATEGY =
+ createSymbol("interfaces.retries", "RetryStrategy", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.types
+ public static final Symbol TYPED_PROPERTIES =
+ createSymbol("types", "TypedProperties", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.interceptors
+ public static final Symbol INTERCEPTOR_CHAIN =
+ createSymbol("interceptors", "InterceptorChain", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol INTERCEPTOR =
+ createSymbol("interceptors", "Interceptor", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.aio.client
+ public static final Symbol CLIENT_CALL =
+ createSymbol("aio.client", "ClientCall", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol REQUEST_PIPELINE =
+ createSymbol("aio.client", "RequestPipeline", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.aio.eventstream
+ public static final Symbol DUPLEX_EVENT_STREAM =
+ createSymbol("aio.eventstream", "DuplexEventStream", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol INPUT_EVENT_STREAM =
+ createSymbol("aio.eventstream", "InputEventStream", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol OUTPUT_EVENT_STREAM =
+ createSymbol("aio.eventstream", "OutputEventStream", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.aio.interfaces
+ public static final Symbol ASYNC_BYTE_STREAM =
+ createSymbol("aio.interfaces", "AsyncByteStream", SmithyPythonDependency.SMITHY_CORE);
+ public static final Symbol ENDPOINT_RESOLVER =
+ createSymbol("aio.interfaces", "EndpointResolver", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.aio.endpoints
+ public static final Symbol STATIC_ENDPOINT_RESOLVER =
+ createSymbol("aio.endpoints", "StaticEndpointResolver", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.aio.types
+ public static final Symbol ASYNC_BYTES_READER =
+ createSymbol("aio.types", "AsyncBytesReader", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_core.aio.utils
+ public static final Symbol ASYNC_LIST = createSymbol("aio.utils", "async_list", SmithyPythonDependency.SMITHY_CORE);
+
+ // smithy_http
+ public static final Symbol TUPLES_TO_FIELDS =
+ createSymbol("", "tuples_to_fields", SmithyPythonDependency.SMITHY_HTTP);
+
+ // Note: HTTPResponse from smithy_http.aio has the same simple name as HTTPResponse
+ // from smithy_http.aio.interfaces. The collision resolver will automatically
+ // disambiguate them with module-based aliases.
+ public static final Symbol HTTP_RESPONSE_IMPL =
+ createSymbol("aio", "HTTPResponse", SmithyPythonDependency.SMITHY_HTTP);
+
+ // smithy_http.interfaces
+ public static final Symbol HTTP_REQUEST_CONFIGURATION =
+ createSymbol("interfaces", "HTTPRequestConfiguration", SmithyPythonDependency.SMITHY_HTTP);
+ public static final Symbol HTTP_CLIENT_CONFIGURATION =
+ createSymbol("interfaces", "HTTPClientConfiguration", SmithyPythonDependency.SMITHY_HTTP);
+
+ // smithy_http.aio.interfaces
+ public static final Symbol HTTP_REQUEST =
+ createSymbol("aio.interfaces", "HTTPRequest", SmithyPythonDependency.SMITHY_HTTP);
+ public static final Symbol HTTP_RESPONSE =
+ createSymbol("aio.interfaces", "HTTPResponse", SmithyPythonDependency.SMITHY_HTTP);
+
+ // smithy_http.aio.crt
+ public static final Symbol AWS_CRT_HTTP_CLIENT =
+ createSymbol("aio.crt", "AWSCRTHTTPClient", SmithyPythonDependency.SMITHY_HTTP);
+
+ // smithy_http.aio.aiohttp
+ public static final Symbol AIOHTTP_CLIENT =
+ createSymbol("aio.aiohttp", "AIOHTTPClient", SmithyPythonDependency.SMITHY_HTTP);
+
+ // smithy_http.aio.identity.apikey
+ public static final Symbol API_KEY_IDENTITY_RESOLVER =
+ createSymbol("aio.identity.apikey", "APIKeyIdentityResolver", SmithyPythonDependency.SMITHY_HTTP);
+
+ // smithy_aws_core.aio.protocols
+ public static final Symbol REST_JSON_CLIENT_PROTOCOL = createSymbol(
+ "aio.protocols",
+ "RestJsonClientProtocol",
+ SmithyPythonDependency.SMITHY_AWS_CORE);
+
+ // smithy_aws_core.identity
+ public static final Symbol STATIC_CREDENTIALS_RESOLVER = createSymbol(
+ "identity",
+ "StaticCredentialsResolver",
+ SmithyPythonDependency.SMITHY_AWS_CORE);
+
+ private RuntimeTypes() {}
+
+ private static Symbol createSymbol(String module, String name, PythonDependency dependency) {
+ var namespace = module.isEmpty() ? dependency.packageName() : dependency.packageName() + "." + module;
+ return Symbol.builder()
+ .name(name)
+ .namespace(namespace, ".")
+ .addDependency(dependency)
+ .build();
+ }
+}
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ConfigGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ConfigGenerator.java
index de03d42d0..b02f0b31f 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ConfigGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ConfigGenerator.java
@@ -22,6 +22,7 @@
import software.amazon.smithy.python.codegen.ConfigProperty;
import software.amazon.smithy.python.codegen.GenerationContext;
import software.amazon.smithy.python.codegen.PythonSettings;
+import software.amazon.smithy.python.codegen.RuntimeTypes;
import software.amazon.smithy.python.codegen.SmithyPythonDependency;
import software.amazon.smithy.python.codegen.SymbolProperties;
import software.amazon.smithy.python.codegen.integrations.PythonIntegration;
@@ -84,18 +85,15 @@ public final class ConfigGenerator implements Runnable {
.build(),
ConfigProperty.builder()
.name("endpoint_resolver")
- .type(Symbol.builder()
- .name("_EndpointResolver")
- .build())
+ .type(RuntimeTypes.ENDPOINT_RESOLVER)
.documentation("""
The endpoint resolver used to resolve the final endpoint per-operation based on the \
configuration.""")
.nullable(false)
.initialize(writer -> {
- writer.addImport("smithy_core.aio.interfaces", "EndpointResolver", "_EndpointResolver");
writer.pushState(new InitDefaultEndpointResolverSection());
- writer.addImport("smithy_core.aio.endpoints", "StaticEndpointResolver");
- writer.write("self.endpoint_resolver = endpoint_resolver or StaticEndpointResolver()");
+ writer.write("self.endpoint_resolver = endpoint_resolver or $T()",
+ RuntimeTypes.STATIC_ENDPOINT_RESOLVER);
writer.popState();
})
.build());
@@ -155,8 +153,8 @@ private static List This is called by {@link PythonWriter} at {@code toString()} time when a collision
+ * is detected between an imported name and a locally-defined name. The import statement
+ * is rewritten from {@code from namespace import name} to {@code from namespace import name as alias}.
+ *
+ * @param namespace The module namespace of the import.
+ * @param name The original imported name.
+ * @param alias The alias to use instead.
+ */
+ void aliasImport(String namespace, String name, String alias) {
+ aliasImportInMap(namespace, name, alias, externalImports);
+ aliasImportInMap(namespace, name, alias, localImports);
+ }
+
+ private void aliasImportInMap(
+ String namespace,
+ String name,
+ String alias,
+ Map Use the {@code $T} formatter to refer to {@link Symbol}s.
+ * Use the {@code $T} formatter to refer to {@link Symbol}s. The formatter writes a
+ * placeholder token into the body text and records the symbol in a symbol table. At
+ * {@link #toString()} time, the writer detects name collisions between imported symbols
+ * and locally-defined names, aliases colliding imports with a {@code _} prefix, and
+ * replaces all placeholder tokens with the resolved (possibly aliased) names.
*
* Use the {@code $N} formatter to render {@link Node}s.
*/
@@ -40,9 +46,25 @@ public final class PythonWriter extends SymbolWriter The writer uses this information at {@link #toString()} time to detect collisions
+ * between locally-defined names and imported names. When a collision is detected, the
+ * imported name is aliased with a {@code _} prefix.
+ *
+ * @param symbol The symbol being defined locally in this file.
+ * @return Returns the writer.
+ */
+ public PythonWriter addLocallyDefinedSymbol(Symbol symbol) {
+ locallyDefinedNames.add(symbol.getName());
+ return this;
+ }
+
@Override
public String toString() {
String header = "# Code generated by smithy-python-codegen DO NOT EDIT.\n\n";
- String imports = getImportContainer().toString();
String logger = addLogger ? "\nlogger = logging.getLogger(__name__)\n\n" : "";
String mainContent = super.toString();
+ // Resolve collisions: for each imported name that collides with a locally-defined
+ // name, alias the import with a "_" prefix and rewrite placeholders in the body.
+ Map Collision cases handled:
+ * For example, {@code AuthOption} from {@code smithy_core.interfaces.auth} becomes
+ * {@code _smithy_core_interfaces_auth_AuthOption}, and {@code AuthOption} from
+ * {@code smithy_core.auth} becomes {@code _smithy_core_auth_AuthOption}.
+ *
+ * The alias is constructed by joining all namespace segments with the symbol name.
+ * This guarantees uniqueness since no two modules share the same full namespace.
+ */
+ private static String buildModuleAlias(Symbol symbol) {
+ String namespace = symbol.getNamespace();
+ String[] parts = namespace.split("\\.");
+ var sb = new StringBuilder("_");
+ for (String part : parts) {
+ sb.append(part).append("_");
+ }
+ sb.append(symbol.getName());
+ return sb.toString();
+ }
+
/**
* Implements Python symbol formatting for the {@code $T} formatter.
+ *
+ * Instead of returning the symbol's name directly, this formatter writes a placeholder
+ * token and registers the symbol in the symbol table. The placeholder is resolved at
+ * {@link PythonWriter#toString()} time, where collision detection determines the final name.
*/
private final class PythonSymbolFormatter implements BiFunction
+ *
+ *
+ * @return A map from placeholder key (namespace.name) to resolved name.
+ */
+ private Map