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 AWS_QUERY_CLIENT_PROTOCOL = createSymbol(
+ "aio.protocols",
+ "AwsQueryClientProtocol");
+
+ // 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/build.gradle.kts b/codegen/build.gradle.kts
index a0a733616..df6e5391c 100644
--- a/codegen/build.gradle.kts
+++ b/codegen/build.gradle.kts
@@ -15,5 +15,5 @@
allprojects {
group = "software.amazon.smithy.python"
- version = "0.3.0"
+ version = "0.3.1"
}
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 allShapeNames;
public PythonSymbolProvider(Model model, PythonSettings settings) {
this.model = model;
@@ -99,6 +102,25 @@ public PythonSymbolProvider(Model model, PythonSettings settings) {
.memberReservedWords(reservedErrorMembers.build())
.escapePredicate((shape, symbol) -> !StringUtils.isEmpty(symbol.getDefinitionFile()))
.buildEscaper();
+
+ // Collect all shape names that will be generated as PascalCase classes in models.py.
+ // Used to detect collisions with synthesized names (union variants, unknown types).
+ this.allShapeNames = collectAllShapeNames();
+ }
+
+ /**
+ * Collects the PascalCase names of all shapes that will be generated as classes.
+ */
+ private Set collectAllShapeNames() {
+ var names = new HashSet();
+ for (Shape shape : model.toSet()) {
+ if (shape.isStructureShape() || shape.isUnionShape()
+ || shape.isEnumShape()
+ || shape.isIntEnumShape()) {
+ names.add(getDefaultShapeName(shape));
+ }
+ }
+ return names;
}
private String escapeWord(String word) {
@@ -352,6 +374,9 @@ public Symbol unionShape(UnionShape shape) {
String name = getDefaultShapeName(shape);
var unknownName = name + "Unknown";
+ if (allShapeNames.contains(unknownName)) {
+ unknownName = name + "_Unknown";
+ }
var unknownSymbol = createGeneratedSymbolBuilder(shape, unknownName, SHAPES_FILE).build();
var builder = createGeneratedSymbolBuilder(shape, name, SHAPES_FILE)
.putProperty(SymbolProperties.UNION_UNKNOWN, unknownSymbol);
@@ -374,6 +399,12 @@ public Symbol memberShape(MemberShape shape) {
// Union members, unlike other shape members, have types generated for them.
var containerSymbol = container.accept(this);
var name = containerSymbol.getName() + StringUtils.capitalize(shape.getMemberName());
+ // Check if the synthesized variant name collides with any service-defined shape.
+ // Smithy shape names cannot contain underscores, so using "_" as separator
+ // guarantees the disambiguated name won't collide with any shape.
+ if (allShapeNames.contains(name)) {
+ name = containerSymbol.getName() + "_" + StringUtils.capitalize(shape.getMemberName());
+ }
return createGeneratedSymbolBuilder(shape, name, SHAPES_FILE, false)
.putProperty(SymbolProperties.SCHEMA, containerSymbol.expectProperty(SymbolProperties.SCHEMA))
.build();
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/RuntimeTypes.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/RuntimeTypes.java
new file mode 100644
index 000000000..3f0173d90
--- /dev/null
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/RuntimeTypes.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package software.amazon.smithy.python.codegen;
+
+import software.amazon.smithy.codegen.core.Symbol;
+import software.amazon.smithy.utils.SmithyUnstableApi;
+
+/**
+ * Pre-defined Symbol constants for all 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. 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 getProtocolProperties(GenerationContext cont
transportBuilder
.initialize(writer -> {
writer.addDependency(SmithyPythonDependency.SMITHY_HTTP.withOptionalDependencies("awscrt"));
- writer.addImport("smithy_http.aio.crt", "AWSCRTHTTPClient");
- writer.write("self.transport = transport or AWSCRTHTTPClient()");
+ writer.write("self.transport = transport or $T()",
+ RuntimeTypes.AWS_CRT_HTTP_CLIENT);
});
} else {
@@ -164,8 +162,8 @@ private static List getProtocolProperties(GenerationContext cont
.initialize(writer -> {
writer.addDependency(
SmithyPythonDependency.SMITHY_HTTP.withOptionalDependencies("aiohttp"));
- writer.addImport("smithy_http.aio.aiohttp", "AIOHTTPClient");
- writer.write("self.transport = transport or AIOHTTPClient()");
+ writer.write("self.transport = transport or $T()",
+ RuntimeTypes.AIOHTTP_CLIENT);
});
}
}
@@ -244,12 +242,12 @@ private static void writeDefaultAuthSchemes(GenerationContext context, PythonWri
var service = context.settings().service(context.model());
writer.openBlock("self.auth_schemes = auth_schemes or {");
- writer.addImport("smithy_core.shapes", "ShapeID");
for (PythonIntegration integration : context.integrations()) {
for (RuntimeClientPlugin plugin : integration.getClientPlugins(context)) {
if (plugin.matchesService(context.model(), service) && plugin.getAuthScheme().isPresent()) {
var scheme = plugin.getAuthScheme().get();
- writer.write("ShapeID($S): ${C|},",
+ writer.write("$T($S): ${C|},",
+ RuntimeTypes.SHAPE_ID,
scheme.getAuthTrait(),
writer.consumer(w -> scheme.initializeScheme(context, writer, service)));
}
@@ -286,7 +284,6 @@ private void writeInterceptorsType(PythonWriter writer) {
writer.addStdlibImport("typing", "Union");
writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
- writer.getImportContainer().addImport("smithy_core.interceptors", "Interceptor", "Interceptor");
writer.writeInline("_ServiceInterceptor = Union[");
var iter = operationShapes.iterator();
@@ -297,7 +294,7 @@ private void writeInterceptorsType(PythonWriter writer) {
// TODO: pull the transport request/response types off of the application protocol
writer.addStdlibImport("typing", "Any");
- writer.writeInline("Interceptor[$T, $T, Any, Any]", input, output);
+ writer.writeInline("$T[$T, $T, Any, Any]", RuntimeTypes.INTERCEPTOR, input, output);
if (iter.hasNext()) {
writer.writeInline(", ");
} else {
@@ -341,6 +338,7 @@ private void generateConfig(GenerationContext context, PythonWriter writer) {
.map(ServiceTrait::getSdkId)
.orElse(context.settings().service().getName());
writer.pushState(new ConfigSection(finalProperties));
+ writer.addLocallyDefinedSymbol(configSymbol);
writer.addStdlibImport("dataclasses", "dataclass");
writer.write("""
@dataclass(init=False)
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/EnumGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/EnumGenerator.java
index 354054503..15fe18497 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/EnumGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/EnumGenerator.java
@@ -30,6 +30,7 @@ public void run() {
var enumSymbol = context.symbolProvider().toSymbol(shape).expectProperty(SymbolProperties.ENUM_SYMBOL);
context.writerDelegator().useShapeWriter(shape, writer -> {
writer.addStdlibImport("enum", "StrEnum");
+ writer.addLocallyDefinedSymbol(enumSymbol);
writer.openBlock("class $L(StrEnum):", "", enumSymbol.getName(), () -> {
shape.getTrait(DocumentationTrait.class).ifPresent(trait -> {
writer.writeDocs(trait.getValue(), context);
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/IntEnumGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/IntEnumGenerator.java
index 8816f9d38..12cee0e50 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/IntEnumGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/IntEnumGenerator.java
@@ -27,6 +27,7 @@ public void run() {
var enumSymbol = directive.symbol().expectProperty(SymbolProperties.ENUM_SYMBOL);
directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> {
writer.addStdlibImport("enum", "IntEnum");
+ writer.addLocallyDefinedSymbol(enumSymbol);
writer.openBlock("class $L(IntEnum):", "", enumSymbol.getName(), () -> {
directive.shape().getTrait(DocumentationTrait.class).ifPresent(trait -> {
writer.writeDocs(trait.getValue(), directive.context());
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ListGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ListGenerator.java
index 7fe3c2d6e..f5026914e 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ListGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ListGenerator.java
@@ -7,6 +7,7 @@
import software.amazon.smithy.model.shapes.ListShape;
import software.amazon.smithy.model.traits.SparseTrait;
import software.amazon.smithy.python.codegen.GenerationContext;
+import software.amazon.smithy.python.codegen.RuntimeTypes;
import software.amazon.smithy.python.codegen.SymbolProperties;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
import software.amazon.smithy.utils.SmithyInternalApi;
@@ -36,13 +37,11 @@ private void generateSerializer() {
var listSymbol = context.symbolProvider().toSymbol(shape);
var serializerSymbol = listSymbol.expectProperty(SymbolProperties.SERIALIZER);
writer.pushState();
- writer.addImport("smithy_core.serializers", "ShapeSerializer");
- writer.addImport("smithy_core.schemas", "Schema");
writer.putContext("sparse", shape.hasTrait(SparseTrait.class));
writer.putContext("propertyName", "e");
var memberTarget = context.model().expectShape(shape.getMember().getTarget());
writer.write("""
- def $1L(serializer: ShapeSerializer, schema: Schema, value: $2T) -> None:
+ def $1L(serializer: $2T, schema: $3T, value: $4T) -> None:
member_schema = schema.members["member"]
with serializer.begin_list(schema, len(value)) as ls:
for e in value:
@@ -50,14 +49,16 @@ private void generateSerializer() {
if e is None:
ls.write_null(member_schema)
else:
- ${3C|}
+ ${5C|}
${/sparse}
${^sparse}
- ${3C|}
+ ${5C|}
${/sparse}
""",
serializerSymbol.getName(),
+ RuntimeTypes.SHAPE_SERIALIZER,
+ RuntimeTypes.SCHEMA,
listSymbol,
writer.consumer(w -> memberTarget.accept(
new MemberSerializerGenerator(context, w, shape.getMember(), "ls"))));
@@ -70,28 +71,28 @@ private void generateDeserializer() {
var memberTarget = context.model().expectShape(shape.getMember().getTarget());
writer.pushState();
- writer.addImport("smithy_core.serializers", "ShapeSerializer");
- writer.addImport("smithy_core.schemas", "Schema");
var sparse = shape.hasTrait(SparseTrait.class);
writer.putContext("sparse", sparse);
writer.putContext("includeSchema",
sparse || (!memberTarget.isUnionShape() && !memberTarget.isStructureShape()));
writer.write("""
- def $1L(deserializer: ShapeDeserializer, schema: Schema) -> $2T:
- result: $2T = []
+ def $1L(deserializer: $2T, schema: $3T) -> $4T:
+ result: $4T = []
${?includeSchema}
member_schema = schema.members["member"]
${/includeSchema}
- def _read_value(d: ShapeDeserializer):
+ def _read_value(d: $2T):
if d.is_null():
d.read_null()
${?sparse}result.append(None)${/sparse}
else:
- result.append(${3C|})
+ result.append(${5C|})
deserializer.read_list(schema, _read_value)
return result
""",
deserializerSymbol.getName(),
+ RuntimeTypes.SHAPE_DESERIALIZER,
+ RuntimeTypes.SCHEMA,
listSymbol,
writer.consumer(w -> memberTarget.accept(
new MemberDeserializerGenerator(context, w, shape.getMember(), "d"))));
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/MapGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/MapGenerator.java
index 0923d7ee7..cf65a83c7 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/MapGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/MapGenerator.java
@@ -7,6 +7,7 @@
import software.amazon.smithy.model.shapes.MapShape;
import software.amazon.smithy.model.traits.SparseTrait;
import software.amazon.smithy.python.codegen.GenerationContext;
+import software.amazon.smithy.python.codegen.RuntimeTypes;
import software.amazon.smithy.python.codegen.SymbolProperties;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
import software.amazon.smithy.utils.SmithyInternalApi;
@@ -36,8 +37,6 @@ private void generateSerializer() {
var mapSymbol = context.symbolProvider().toSymbol(shape);
var serializerSymbol = mapSymbol.expectProperty(SymbolProperties.SERIALIZER);
writer.pushState();
- writer.addImport("smithy_core.serializers", "ShapeSerializer");
- writer.addImport("smithy_core.schemas", "Schema");
writer.putContext("sparse", shape.hasTrait(SparseTrait.class));
writer.putContext("propertyName", "v");
var valueTarget = context.model().expectShape(shape.getValue().getTarget());
@@ -45,7 +44,7 @@ private void generateSerializer() {
// Note that we have to disable typing in the sparse case because pyright for some reason isn't
// narrowing out the None even though there's an explicit is None check.
writer.write("""
- def $1L(serializer: ShapeSerializer, schema: Schema, value: $2T) -> None:
+ def $1L(serializer: $2T, schema: $3T, value: $4T) -> None:
with serializer.begin_map(schema, len(value)) as m:
value_schema = schema.members["value"]
for k, v in value.items():
@@ -53,14 +52,16 @@ private void generateSerializer() {
if v is None:
m.entry(k, lambda vs: vs.write_null(value_schema))
else:
- m.entry(k, lambda vs: ${3C|}) # type: ignore
+ m.entry(k, lambda vs: ${5C|}) # type: ignore
${/sparse}
${^sparse}
- m.entry(k, lambda vs: ${3C|})
+ m.entry(k, lambda vs: ${5C|})
${/sparse}
""",
serializerSymbol.getName(),
+ RuntimeTypes.SHAPE_SERIALIZER,
+ RuntimeTypes.SCHEMA,
mapSymbol,
writer.consumer(w -> valueTarget.accept(
new MemberSerializerGenerator(context, w, shape.getValue(), "vs"))));
@@ -73,25 +74,25 @@ private void generateDeserializer() {
var valueTarget = context.model().expectShape(shape.getValue().getTarget());
writer.pushState();
- writer.addImport("smithy_core.serializers", "ShapeSerializer");
- writer.addImport("smithy_core.schemas", "Schema");
var sparse = shape.hasTrait(SparseTrait.class);
writer.putContext("sparse", sparse);
writer.putContext("includeSchema", sparse || (!valueTarget.isUnionShape() && !valueTarget.isStructureShape()));
writer.write("""
- def $1L(deserializer: ShapeDeserializer, schema: Schema) -> $2T:
- result: $2T = {}
+ def $1L(deserializer: $2T, schema: $3T) -> $4T:
+ result: $4T = {}
value_schema = schema.members["value"]
- def _read_value(k: str, d: ShapeDeserializer):
+ def _read_value(k: str, d: $2T):
if d.is_null():
d.read_null()
${?sparse}result[k] = None${/sparse}
else:
- result[k] = ${3C|}
+ result[k] = ${5C|}
deserializer.read_map(schema, _read_value)
return result
""",
deserializerSymbol.getName(),
+ RuntimeTypes.SHAPE_DESERIALIZER,
+ RuntimeTypes.SCHEMA,
listSymbol,
writer.consumer(w -> valueTarget.accept(
new MemberDeserializerGenerator(context, w, shape.getValue(), "d"))));
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/OperationGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/OperationGenerator.java
index 9557a93ae..65386f33f 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/OperationGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/OperationGenerator.java
@@ -12,6 +12,7 @@
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ShapeId;
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.SymbolProperties;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
@@ -38,38 +39,39 @@ public OperationGenerator(GenerationContext context, PythonWriter writer, Operat
@Override
public void run() {
var opSymbol = symbolProvider.toSymbol(shape);
+ writer.addLocallyDefinedSymbol(opSymbol);
var inSymbol = symbolProvider.toSymbol(model.expectShape(shape.getInputShape()));
var outSymbol = symbolProvider.toSymbol(model.expectShape(shape.getOutputShape()));
writer.addStdlibImport("dataclasses", "dataclass");
writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
- writer.addImport("smithy_core.schemas", "APIOperation");
- writer.addImport("smithy_core.documents", "TypeRegistry");
writer.write("""
- $1L = APIOperation(
- input = $2T,
- output = $3T,
- schema = $4T,
- input_schema = $5T,
- output_schema = $6T,
- error_registry = TypeRegistry({
- $7C
+ $1L = $2T(
+ input = $3T,
+ output = $4T,
+ schema = $5T,
+ input_schema = $6T,
+ output_schema = $7T,
+ error_registry = $8T({
+ $9C
}),
effective_auth_schemes = [
- $8C
+ ${10C}
],
error_schemas = [
- $9C
+ ${11C}
]
)
""",
opSymbol.getName(),
+ RuntimeTypes.API_OPERATION,
inSymbol,
outSymbol,
opSymbol.expectProperty(SymbolProperties.SCHEMA),
inSymbol.expectProperty(SymbolProperties.SCHEMA),
outSymbol.expectProperty(SymbolProperties.SCHEMA),
+ RuntimeTypes.TYPE_REGISTRY,
writer.consumer(this::writeErrorTypeRegistry),
writer.consumer(this::writeAuthSchemes),
writer.consumer(this::writeErrorSchemas));
@@ -77,12 +79,9 @@ public void run() {
private void writeErrorTypeRegistry(PythonWriter writer) {
List errors = shape.getErrors();
- if (!errors.isEmpty()) {
- writer.addImport("smithy_core.shapes", "ShapeID");
- }
for (var error : errors) {
var errSymbol = symbolProvider.toSymbol(model.expectShape(error));
- writer.write("ShapeID($S): $T,", error, errSymbol);
+ writer.write("$1T($2S): $3T,", RuntimeTypes.SHAPE_ID, error, errSymbol);
}
}
@@ -99,12 +98,8 @@ private void writeAuthSchemes(PythonWriter writer) {
shape.getId(),
ServiceIndex.AuthSchemeMode.NO_AUTH_AWARE);
- if (!authSchemes.isEmpty()) {
- writer.addImport("smithy_core.shapes", "ShapeID");
- }
-
for (var authSchemeId : authSchemes.keySet()) {
- writer.write("ShapeID($S),", authSchemeId);
+ writer.write("$1T($2S),", RuntimeTypes.SHAPE_ID, authSchemeId);
}
}
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SchemaGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SchemaGenerator.java
index 4f97023ae..ac6bd4657 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SchemaGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SchemaGenerator.java
@@ -27,6 +27,7 @@
import software.amazon.smithy.model.traits.UnitTypeTrait;
import software.amazon.smithy.model.traits.synthetic.SyntheticEnumTrait;
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.SymbolProperties;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
@@ -96,11 +97,10 @@ public static void generateAll(GenerationContext context, Collection shap
}
private void writeShapeSchema(PythonWriter writer, Shape shape) {
- writer.addImport("smithy_core.schemas", "Schema");
- writer.addImports("smithy_core.shapes", Set.of("ShapeID", "ShapeType"));
writer.pushState();
var symbol = context.symbolProvider().toSymbol(shape).expectProperty(SymbolProperties.SCHEMA).getSymbol();
+ writer.addLocallyDefinedSymbol(symbol);
var traits = filterTraits(shape);
writer.putContext("isCollection", shape.isStructureShape() || !shape.members().isEmpty());
@@ -109,19 +109,22 @@ private void writeShapeSchema(PythonWriter writer, Shape shape) {
writer.putContext("hasTraits", !traits.isEmpty());
writer.write("""
- $L = Schema${?isCollection}.collection${/isCollection}(
- id=ShapeID($S),
- ${^isStructure}shape_type=ShapeType.${shapeType:L},${/isStructure}
+ $1L = $2T${?isCollection}.collection${/isCollection}(
+ id=$3T($4S),
+ ${^isStructure}shape_type=$5T.${shapeType:L},${/isStructure}
${?hasTraits}
traits=[
- ${C|}
+ ${6C|}
],
${/hasTraits}
- ${C|}
+ ${7C|}
)
""",
symbol.getName(),
+ RuntimeTypes.SCHEMA,
+ RuntimeTypes.SHAPE_ID,
shape.getId(),
+ RuntimeTypes.SHAPE_TYPE,
writer.consumer(w -> writeTraits(w, traits)),
writer.consumer(w -> writeSchemaMembers(w, shape)));
writer.popState();
@@ -142,13 +145,14 @@ private Map> filterTraits(Shape shape) {
}
private void writeTraits(PythonWriter writer, Map> traits) {
- writer.addImport("smithy_core.traits", "Trait");
writer.pushState();
writer.putContext("traits", traits);
writer.write("""
${#traits}
- Trait.new(id=ShapeID(${key:S})${?value}, value=${value:N}${/value}),
- ${/traits}""");
+ $1T.new(id=$2T(${key:S})${?value}, value=${value:N}${/value}),
+ ${/traits}""",
+ RuntimeTypes.TRAIT,
+ RuntimeTypes.SHAPE_ID);
writer.popState();
}
@@ -216,9 +220,6 @@ public void finalizeRecursiveShapes() {
}
private void finalizeRecursiveShapes(PythonWriter writer) {
- writer.addImport("smithy_core.schemas", "Schema");
- writer.addImport("smithy_core.shapes", "ShapeType");
-
for (Map.Entry entry : deferredMembers.entrySet()) {
var member = entry.getKey();
LOGGER.warning("Generating member: " + member.getId());
@@ -236,7 +237,7 @@ private void finalizeRecursiveShapes(PythonWriter writer) {
writer.putContext("hasTraits", !traits.isEmpty());
writer.write("""
- $1T.members[$2S] = Schema.member(
+ $1T.members[$2S] = $6T.member(
id=$1T.id.with_member($2S),
target=$3T,
index=$5L,
@@ -252,7 +253,8 @@ private void finalizeRecursiveShapes(PythonWriter writer) {
member.getMemberName(),
target,
writer.consumer(w -> writeTraits(w, traits)),
- entry.getValue());
+ entry.getValue(),
+ RuntimeTypes.SCHEMA);
writer.popState();
}
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ServiceErrorGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ServiceErrorGenerator.java
index a4fcf04c1..171cbfd99 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ServiceErrorGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/ServiceErrorGenerator.java
@@ -7,6 +7,7 @@
import software.amazon.smithy.codegen.core.WriterDelegator;
import software.amazon.smithy.python.codegen.CodegenUtils;
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.writer.PythonWriter;
import software.amazon.smithy.utils.SmithyInternalApi;
@@ -29,16 +30,16 @@ public void run() {
var serviceError = CodegenUtils.getServiceError(settings);
writers.useFileWriter(serviceError.getDefinitionFile(), serviceError.getNamespace(), writer -> {
writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
- writer.addImport("smithy_core.exceptions", "ModeledError");
+ writer.addLocallyDefinedSymbol(serviceError);
writer.write("""
- class $L(ModeledError):
+ class $L($T):
""\"
Base error for all errors in the service.
Some exceptions do not extend from this class, including
synthetic, implicit, and shared exception types.
""\"
- """, serviceError.getName());
+ """, serviceError.getName(), RuntimeTypes.MODELED_ERROR);
});
}
}
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/StructureGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/StructureGenerator.java
index 55d4daf67..e4975f65e 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/StructureGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/StructureGenerator.java
@@ -29,6 +29,7 @@
import software.amazon.smithy.python.codegen.CodegenUtils;
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.SymbolProperties;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
import software.amazon.smithy.utils.SmithyInternalApi;
@@ -93,6 +94,7 @@ public void run() {
private void renderStructure() {
writer.addStdlibImport("dataclasses", "dataclass");
var symbol = symbolProvider.toSymbol(shape);
+ writer.addLocallyDefinedSymbol(symbol);
writer.write("""
@dataclass(kw_only=True)
class $L:
@@ -119,6 +121,7 @@ private void renderError() {
var fault = errorTrait.getValue();
var symbol = symbolProvider.toSymbol(shape);
+ writer.addLocallyDefinedSymbol(symbol);
var baseError = CodegenUtils.getServiceError(settings);
writer.putContext("retryable", false);
writer.putContext("throttling", false);
@@ -275,7 +278,9 @@ private String getDefaultValue(PythonWriter writer, MemberShape member) {
}
if (target.isDocumentShape()) {
- return String.format("lambda: Document(%s)", switch (defaultNode.getType()) {
+ var docSymbol = RuntimeTypes.DOCUMENT;
+ var docName = writer.format("$T", docSymbol);
+ return String.format("lambda: %s(%s)", docName, switch (defaultNode.getType()) {
case NULL -> "None";
case BOOLEAN -> defaultNode.expectBooleanNode().getValue() ? "True" : "False";
case ARRAY -> "list()";
@@ -296,17 +301,17 @@ private String getDefaultValue(PythonWriter writer, MemberShape member) {
private void generateSerializeMethod() {
writer.pushState();
- writer.addImport("smithy_core.serializers", "ShapeSerializer");
+ writer.putContext("shapeSerializer", RuntimeTypes.SHAPE_SERIALIZER);
writer.putContext("schema", symbolProvider.toSymbol(shape).expectProperty(SymbolProperties.SCHEMA));
writer.write("""
- def serialize(self, serializer: ShapeSerializer):
+ def serialize(self, serializer: ${shapeSerializer:T}):
serializer.write_struct(${schema:T}, self)
""");
var serializeableMembers = filterMembers();
- writer.write("def serialize_members(self, serializer: ShapeSerializer):").indent();
+ writer.write("def serialize_members(self, serializer: ${shapeSerializer:T}):").indent();
if (serializeableMembers.isEmpty()) {
writer.write("pass");
} else {
@@ -352,8 +357,6 @@ private void generateDeserializeMethod() {
writer.pushState();
writer.addLogger();
writer.addStdlibImports("typing", Set.of("Self", "Any"));
- writer.addImport("smithy_core.deserializers", "ShapeDeserializer");
- writer.addImport("smithy_core.schemas", "Schema");
var schemaSymbol = symbolProvider.toSymbol(shape).expectProperty(SymbolProperties.SCHEMA);
writer.putContext("schema", schemaSymbol);
@@ -361,23 +364,25 @@ private void generateDeserializeMethod() {
// TODO: either formalize deserialize_kwargs or remove it when http serde is converted
writer.write("""
@classmethod
- def deserialize(cls, deserializer: ShapeDeserializer) -> Self:
+ def deserialize(cls, deserializer: $1T) -> Self:
return cls(**cls.deserialize_kwargs(deserializer))
@classmethod
- def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]:
+ def deserialize_kwargs(cls, deserializer: $1T) -> dict[str, Any]:
kwargs: dict[str, Any] = {}
- def _consumer(schema: Schema, de: ShapeDeserializer) -> None:
+ def _consumer(schema: $2T, de: $1T) -> None:
match schema.expect_member_index():
- ${C|}
+ ${3C|}
case _:
logger.debug("Unexpected member schema: %s", schema)
- deserializer.read_struct($T, consumer=_consumer)
+ deserializer.read_struct($4T, consumer=_consumer)
return kwargs
""",
+ RuntimeTypes.SHAPE_DESERIALIZER,
+ RuntimeTypes.SCHEMA,
writer.consumer(w -> deserializeMembers(shape.members())),
schemaSymbol);
writer.popState();
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/UnionGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/UnionGenerator.java
index 4fe83d051..197a6fa16 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/UnionGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/UnionGenerator.java
@@ -14,6 +14,7 @@
import software.amazon.smithy.model.traits.DocumentationTrait;
import software.amazon.smithy.model.traits.StringTrait;
import software.amazon.smithy.python.codegen.GenerationContext;
+import software.amazon.smithy.python.codegen.RuntimeTypes;
import software.amazon.smithy.python.codegen.SymbolProperties;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
import software.amazon.smithy.utils.SmithyInternalApi;
@@ -49,9 +50,13 @@ public UnionGenerator(
public void run() {
writer.addStdlibImports("typing", Set.of("Union"));
writer.pushState();
- var parentName = symbolProvider.toSymbol(shape).getName();
+ var parentSymbol = symbolProvider.toSymbol(shape);
+ var parentName = parentSymbol.getName();
+ writer.addLocallyDefinedSymbol(parentSymbol);
writer.addStdlibImport("dataclasses", "dataclass");
- writer.addImport("smithy_core.serializers", "ShapeSerializer");
+ writer.putContext("shapeSerializer", RuntimeTypes.SHAPE_SERIALIZER);
+ writer.putContext("shapeDeserializer", RuntimeTypes.SHAPE_DESERIALIZER);
+ writer.putContext("serializationError", RuntimeTypes.SERIALIZATION_ERROR);
var schemaSymbol = symbolProvider.toSymbol(shape).expectProperty(SymbolProperties.SCHEMA);
writer.putContext("schema", schemaSymbol);
@@ -59,6 +64,7 @@ public void run() {
for (MemberShape member : shape.members()) {
var memberSymbol = symbolProvider.toSymbol(member);
memberNames.add(memberSymbol.getName());
+ writer.addLocallyDefinedSymbol(memberSymbol);
var target = model.expectShape(member.getTarget());
var targetSymbol = symbolProvider.toSymbol(target);
@@ -69,14 +75,14 @@ class $1L:
value: $3T
- def serialize(self, serializer: ShapeSerializer):
+ def serialize(self, serializer: ${shapeSerializer:T}):
serializer.write_struct($4T, self)
- def serialize_members(self, serializer: ShapeSerializer):
+ def serialize_members(self, serializer: ${shapeSerializer:T}):
${5C|}
@classmethod
- def deserialize(cls, deserializer: ShapeDeserializer) -> Self:
+ def deserialize(cls, deserializer: ${shapeDeserializer:T}) -> Self:
return cls(value=${6C|})
""",
@@ -99,7 +105,7 @@ def deserialize(cls, deserializer: ShapeDeserializer) -> Self:
// Since the underlying value is unknown and un-comparable, that is the only
// realistic implementation.
var unknownSymbol = symbolProvider.toSymbol(shape).expectProperty(SymbolProperties.UNION_UNKNOWN);
- writer.addImport("smithy_core.exceptions", "SerializationError");
+ writer.addLocallyDefinedSymbol(unknownSymbol);
writer.write("""
@dataclass
class $1L:
@@ -114,14 +120,14 @@ class $1L:
tag: str
- def serialize(self, serializer: ShapeSerializer):
- raise SerializationError("Unknown union variants may not be serialized.")
+ def serialize(self, serializer: ${shapeSerializer:T}):
+ raise ${serializationError:T}("Unknown union variants may not be serialized.")
- def serialize_members(self, serializer: ShapeSerializer):
- raise SerializationError("Unknown union variants may not be serialized.")
+ def serialize_members(self, serializer: ${shapeSerializer:T}):
+ raise ${serializationError:T}("Unknown union variants may not be serialized.")
@classmethod
- def deserialize(cls, deserializer: ShapeDeserializer) -> Self:
+ def deserialize(cls, deserializer: ${shapeDeserializer:T}) -> Self:
raise NotImplementedError()
""", unknownSymbol.getName());
@@ -141,11 +147,10 @@ raise NotImplementedError()
private void generateDeserializer() {
writer.addLogger();
writer.addStdlibImports("typing", Set.of("Self", "Any"));
- writer.addImport("smithy_core.deserializers", "ShapeDeserializer");
- writer.addImport("smithy_core.exceptions", "SerializationError");
var symbol = symbolProvider.toSymbol(shape);
var deserializerSymbol = symbol.expectProperty(SymbolProperties.DESERIALIZER);
+ writer.addLocallyDefinedSymbol(deserializerSymbol);
var schemaSymbol = symbol.expectProperty(SymbolProperties.SCHEMA);
var unknownSymbol = symbol.expectProperty(SymbolProperties.UNION_UNKNOWN);
writer.putContext("schema", schemaSymbol);
@@ -153,29 +158,30 @@ private void generateDeserializer() {
class $1L:
_result: $2T | None = None
- def deserialize(self, deserializer: ShapeDeserializer) -> $2T:
+ def deserialize(self, deserializer: ${shapeDeserializer:T}) -> $2T:
self._result = None
deserializer.read_struct($3T, self._consumer)
if self._result is None:
- raise SerializationError("Unions must have exactly one value, but found none.")
+ raise ${serializationError:T}("Unions must have exactly one value, but found none.")
return self._result
- def _consumer(self, schema: Schema, de: ShapeDeserializer) -> None:
+ def _consumer(self, schema: $4T, de: ${shapeDeserializer:T}) -> None:
match schema.expect_member_index():
- ${4C|}
+ ${5C|}
case _:
- self._set_result($5L(tag=schema.expect_member_name()))
+ self._set_result($6L(tag=schema.expect_member_name()))
def _set_result(self, value: $2T) -> None:
if self._result is not None:
- raise SerializationError("Unions must have exactly one value, but found more than one.")
+ raise ${serializationError:T}("Unions must have exactly one value, but found more than one.")
self._result = value
""",
deserializerSymbol.getName(),
symbol,
schemaSymbol,
+ RuntimeTypes.SCHEMA,
writer.consumer(w -> deserializeMembers()),
unknownSymbol.getName());
}
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/HttpApiKeyAuth.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/HttpApiKeyAuth.java
index 2c58c7ed2..a3d796d9c 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/HttpApiKeyAuth.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/HttpApiKeyAuth.java
@@ -6,7 +6,6 @@
import java.util.List;
import java.util.Locale;
-import java.util.Set;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.ShapeId;
@@ -16,6 +15,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.writer.PythonWriter;
import software.amazon.smithy.utils.SmithyInternalApi;
@@ -61,12 +61,11 @@ public List getClientPlugins(GenerationContext context) {
.build())
.build())
.initialize(writer -> {
- writer.addImport("smithy_http.aio.identity.apikey", "APIKeyIdentityResolver");
writer.write("""
self.api_key_identity_resolver = (
- api_key_identity_resolver or APIKeyIdentityResolver()
+ api_key_identity_resolver or $T()
)
- """);
+ """, RuntimeTypes.API_KEY_IDENTITY_RESOLVER);
})
.build())
.authScheme(new ApiKeyAuthScheme())
@@ -86,20 +85,21 @@ public void customize(GenerationContext context) {
context.writerDelegator().useFileWriter(resolver.getDefinitionFile(), resolver.getNamespace(), writer -> {
writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
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.addStdlibImport("typing", "Any");
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
)
""",
OPTION_GENERATOR_NAME,
+ RuntimeTypes.AUTH_PARAMS,
+ RuntimeTypes.AUTH_OPTION_INTERFACE,
+ RuntimeTypes.AUTH_OPTION,
+ RuntimeTypes.SHAPE_ID,
HttpApiKeyAuthTrait.ID.toString());
writer.popState();
});
@@ -152,11 +152,10 @@ public void initializeScheme(GenerationContext context, PythonWriter writer, Ser
var trait = service.expectTrait(HttpApiKeyAuthTrait.class);
writer.pushState();
writer.putContext("scheme", trait.getScheme().orElse(null));
- writer.addImport("smithy_core.traits", "APIKeyLocation");
writer.write("""
- $T(
- name=$S,
- location=APIKeyLocation.$L,
+ $1T(
+ name=$2S,
+ location=$4T.$3L,
${?scheme}
scheme=${scheme:S},
${/scheme}
@@ -164,7 +163,8 @@ public void initializeScheme(GenerationContext context, PythonWriter writer, Ser
""",
getAuthSchemeSymbol(context),
trait.getName(),
- trait.getIn().name().toUpperCase(Locale.ENGLISH));
+ trait.getIn().name().toUpperCase(Locale.ENGLISH),
+ RuntimeTypes.API_KEY_LOCATION);
writer.popState();
}
}
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/RestJsonProtocolGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/RestJsonProtocolGenerator.java
index 09f49690b..1a31050e7 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/RestJsonProtocolGenerator.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/RestJsonProtocolGenerator.java
@@ -13,6 +13,7 @@
import software.amazon.smithy.python.codegen.ApplicationProtocol;
import software.amazon.smithy.python.codegen.GenerationContext;
import software.amazon.smithy.python.codegen.HttpProtocolTestGenerator;
+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.generators.ProtocolGenerator;
@@ -84,10 +85,9 @@ public ApplicationProtocol getApplicationProtocol(GenerationContext context) {
@Override
public void initializeProtocol(GenerationContext context, PythonWriter writer) {
writer.addDependency(SmithyPythonDependency.SMITHY_AWS_CORE.withOptionalDependencies("json"));
- writer.addImport("smithy_aws_core.aio.protocols", "RestJsonClientProtocol");
var serviceSymbol = context.symbolProvider().toSymbol(context.settings().service(context.model()));
var serviceSchema = serviceSymbol.expectProperty(SymbolProperties.SCHEMA);
- writer.write("RestJsonClientProtocol($T)", serviceSchema);
+ writer.write("$1T($2T)", RuntimeTypes.REST_JSON_CLIENT_PROTOCOL, serviceSchema);
}
// This is here rather than in HttpBindingProtocolGenerator because eventually
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/writer/ImportDeclarations.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/writer/ImportDeclarations.java
index 13df49e9d..efb45dfa0 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/writer/ImportDeclarations.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/writer/ImportDeclarations.java
@@ -104,6 +104,54 @@ private ImportDeclarations addImportToMap(
return this;
}
+ /**
+ * Retroactively aliases an existing import to avoid name collisions.
+ *
+ *
This is called by {@link PythonWriter} at {@code toString()} time when a collision
+ * is detected. The import statement is rewritten from {@code from namespace import name}
+ * to {@code from namespace import name as alias}.
+ *
+ *
The lookup mirrors the dispatch logic in {@link #addImport} so that aliasing works
+ * for all three import maps:
+ *
+ *
{@code stdlibImports} — keyed by absolute namespace.
+ *
{@code externalImports} — keyed by absolute namespace.
+ *
{@code localImports} — keyed by relativized namespace for same-package imports.
+ *
+ *
+ * @param namespace The module namespace of the import (absolute).
+ * @param name The original imported name.
+ * @param alias The alias to use instead.
+ */
+ void aliasImport(String namespace, String name, String alias) {
+ // stdlib imports are stored with absolute namespaces.
+ aliasImportInMap(namespace, name, alias, stdlibImports);
+
+ // external imports are stored with absolute namespaces.
+ aliasImportInMap(namespace, name, alias, externalImports);
+
+ // local imports may be stored with relativized namespaces (mirroring addImport).
+ if (namespace.startsWith(settings.moduleName())) {
+ var isTestModule = this.localNamespace.startsWith("tests");
+ var ns = isTestModule ? namespace : relativize(namespace);
+ aliasImportInMap(ns, name, alias, localImports);
+ }
+ }
+
+ private void aliasImportInMap(
+ String namespace,
+ String name,
+ String alias,
+ Map> importMap
+ ) {
+ var namespaceImports = importMap.get(namespace);
+ if (namespaceImports != null
+ && namespaceImports.containsKey(name)
+ && namespaceImports.get(name).equals(name)) {
+ namespaceImports.put(name, alias);
+ }
+ }
+
@Override
public String toString() {
if (externalImports.isEmpty() && stdlibImports.isEmpty() && localImports.isEmpty()) {
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/writer/PythonWriter.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/writer/PythonWriter.java
index 435b8463a..fa369d54e 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/writer/PythonWriter.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/writer/PythonWriter.java
@@ -6,6 +6,8 @@
import static software.amazon.smithy.python.codegen.SymbolProperties.IMPORTABLE;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -31,7 +33,11 @@
/**
* Specialized code writer for managing Python dependencies.
*
- *
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> symbolTable = new HashMap<>();
+
+ /**
+ * Names that are defined locally in this file (e.g., class names, top-level variables).
+ * These take priority over imported names during collision resolution.
+ */
+ private final Set locallyDefinedNames = new HashSet<>();
+
/**
* Constructs a PythonWriter.
*
@@ -316,18 +338,124 @@ public PythonWriter maybeWrite(boolean shouldWrite, Object content, Object... ar
return this;
}
+ /**
+ * Records a symbol as locally defined in this file (e.g., a class or top-level variable).
+ *
+ *
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 resolvedNames = resolveCollisions();
+
+ // Replace all placeholder tokens in the body with resolved names.
+ for (Map.Entry entry : resolvedNames.entrySet()) {
+ var placeholder = PLACEHOLDER_PREFIX + entry.getKey() + PLACEHOLDER_SUFFIX;
+ mainContent = mainContent.replace(placeholder, entry.getValue());
+ }
+
+ String imports = getImportContainer().toString();
+
return header + imports + logger + mainContent;
}
+ /**
+ * Detects collisions between symbols and resolves them by aliasing.
+ *
+ *
Collision cases handled:
+ *
+ *
Imported symbol vs locally-defined name — the import is aliased as {@code _Name}.
+ *
Two imported symbols with the same simple name from different modules — each gets
+ * a unique alias derived from its module namespace.
+ *
Both cases combined — locally-defined wins, all imports are aliased.
+ *
+ *
+ * @return A map from placeholder key (namespace.name) to resolved name.
+ */
+ private Map resolveCollisions() {
+ Map resolvedNames = new HashMap<>();
+
+ for (Map.Entry> entry : symbolTable.entrySet()) {
+ String simpleName = entry.getKey();
+ Set symbols = entry.getValue();
+
+ boolean collidesWithLocal = locallyDefinedNames.contains(simpleName);
+ boolean hasImportCollision = symbols.size() > 1;
+
+ if (!collidesWithLocal && !hasImportCollision) {
+ // No collision — use the plain name.
+ for (Symbol symbol : symbols) {
+ String placeholderKey = symbol.getNamespace() + "." + symbol.getName();
+ resolvedNames.put(placeholderKey, simpleName);
+ }
+ continue;
+ }
+
+ if (collidesWithLocal && !hasImportCollision) {
+ // Single import collides with a locally-defined name — alias it as _Name.
+ for (Symbol symbol : symbols) {
+ String placeholderKey = symbol.getNamespace() + "." + symbol.getName();
+ String aliasedName = "_" + simpleName;
+ getImportContainer().aliasImport(symbol.getNamespace(), simpleName, aliasedName);
+ resolvedNames.put(placeholderKey, aliasedName);
+ }
+ } else {
+ // Multiple imports with the same simple name (possibly also colliding with local).
+ // Each gets a unique alias derived from its module namespace.
+ for (Symbol symbol : symbols) {
+ String placeholderKey = symbol.getNamespace() + "." + symbol.getName();
+ String aliasedName = buildModuleAlias(symbol);
+ getImportContainer().aliasImport(symbol.getNamespace(), simpleName, aliasedName);
+ resolvedNames.put(placeholderKey, aliasedName);
+ }
+ }
+ }
+
+ return resolvedNames;
+ }
+
+ /**
+ * Builds a unique alias for a symbol based on its module namespace.
+ *
+ *
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