From f42882df0557af98350825c0594c4647d1a84b9f Mon Sep 17 00:00:00 2001 From: ubaskota <19787410+ubaskota@users.noreply.github.com> Date: Mon, 16 Mar 2026 18:23:22 -0400 Subject: [PATCH 1/3] Refactor and move config resolution codegen logic from generic layer to AWS layer --- .../codegen/AwsConfigPropertyMetadata.java | 61 ++++ .../AwsConfigResolutionIntegration.java | 293 ++++++++++++++++++ .../python/aws/codegen/AwsConfiguration.java | 31 +- .../aws/codegen/AwsUserAgentIntegration.java | 14 +- ...hon.codegen.integrations.PythonIntegration | 1 + .../smithy/python/codegen/ConfigProperty.java | 91 +----- .../codegen/generators/ConfigGenerator.java | 246 ++------------- 7 files changed, 415 insertions(+), 322 deletions(-) create mode 100644 codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigPropertyMetadata.java create mode 100644 codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigPropertyMetadata.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigPropertyMetadata.java new file mode 100644 index 000000000..035d757c5 --- /dev/null +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigPropertyMetadata.java @@ -0,0 +1,61 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.python.aws.codegen; + +import java.util.Optional; +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * AWS-specific config resolution metadata for a config property. + * Holds validators, custom resolvers, and default values. + */ +@SmithyInternalApi +public record AwsConfigPropertyMetadata( + Symbol validator, + Symbol customResolver, + String defaultValue +) { + public Optional validatorOpt() { + return Optional.ofNullable(validator); + } + + public Optional customResolverOpt() { + return Optional.ofNullable(customResolver); + } + + public Optional defaultValueOpt() { + return Optional.ofNullable(defaultValue); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Symbol validator; + private Symbol customResolver; + private String defaultValue; + + public Builder validator(Symbol validator) { + this.validator = validator; + return this; + } + + public Builder customResolver(Symbol customResolver) { + this.customResolver = customResolver; + return this; + } + + public Builder defaultValue(String defaultValue) { + this.defaultValue = defaultValue; + return this; + } + + public AwsConfigPropertyMetadata build() { + return new AwsConfigPropertyMetadata(validator, customResolver, defaultValue); + } + } +} diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java new file mode 100644 index 000000000..194f848d6 --- /dev/null +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java @@ -0,0 +1,293 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.python.aws.codegen; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import software.amazon.smithy.python.codegen.ConfigProperty; +import software.amazon.smithy.python.codegen.GenerationContext; +import software.amazon.smithy.python.codegen.integrations.PythonIntegration; +import software.amazon.smithy.python.codegen.sections.ConfigSection; +import software.amazon.smithy.python.codegen.writer.PythonWriter; +import software.amazon.smithy.utils.CodeInterceptor; +import software.amazon.smithy.utils.CodeSection; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * Intercepts the generated Config class to add AWS-specific descriptor-based + * config resolution, keeping the generic ConfigGenerator unchanged. + */ +@SmithyInternalApi +public class AwsConfigResolutionIntegration implements PythonIntegration { + + // Metadata for properties that use descriptors, keyed by property name. + private static final Map DESCRIPTOR_METADATA = Map.of( + "region", AwsConfiguration.REGION_METADATA, + "retry_strategy", AwsConfiguration.RETRY_STRATEGY_METADATA, + "sdk_ua_app_id", AwsUserAgentIntegration.SDK_UA_APP_ID_METADATA + ); + + @Override + public List> interceptors( + GenerationContext context + ) { + return List.of(new ConfigResolutionInterceptor()); + } + + private static final class ConfigResolutionInterceptor + implements CodeInterceptor { + + @Override + public Class sectionType() { + return ConfigSection.class; + } + + @Override + public void write(PythonWriter writer, String previousText, ConfigSection section) { + // Find properties that have descriptor metadata registered + List descriptorProps = section.properties().stream() + .filter(p -> DESCRIPTOR_METADATA.containsKey(p.name())) + .collect(Collectors.toList()); + + if (descriptorProps.isEmpty()) { + writer.write(previousText); + return; + } + + Set descriptorNames = descriptorProps.stream() + .map(ConfigProperty::name) + .collect(Collectors.toSet()); + + addImports(writer, descriptorProps); + + String transformed = transformConfigClass(previousText, descriptorProps, descriptorNames); + writer.write(transformed); + + appendGetSourceMethod(writer); + } + + private void addImports(PythonWriter writer, List descriptorProps) { + writer.addImport("smithy_aws_core.config.property", "ConfigProperty"); + writer.addImport("smithy_aws_core.config.resolver", "ConfigResolver"); + writer.addImport("smithy_aws_core.config.sources", "EnvironmentSource"); + writer.addImport("smithy_aws_core.config.source_info", "SourceInfo"); + + for (ConfigProperty prop : descriptorProps) { + AwsConfigPropertyMetadata meta = DESCRIPTOR_METADATA.get(prop.name()); + if (meta == null) { + continue; + } + meta.validatorOpt().ifPresent(sym -> + writer.addImport(sym.getNamespace(), sym.getName())); + meta.customResolverOpt().ifPresent(sym -> + writer.addImport(sym.getNamespace(), sym.getName())); + meta.defaultValueOpt().ifPresent(val -> { + if (val.contains("RetryStrategyOptions")) { + writer.addImport("smithy_core.retries", "RetryStrategyOptions"); + } + }); + } + } + + private void appendGetSourceMethod(PythonWriter writer) { + writer.write(""" + + def get_source(self, key: str) -> SourceInfo | None: + \"""Get the source that provided a configuration value. + + Args: + key: The configuration key (e.g., 'region', 'retry_strategy') + + Returns: + The source info (SimpleSource or ComplexSource), + or None if the key hasn't been resolved yet. + \""" + cached = self.__dict__.get(f'_cache_{key}') + return cached[1] if cached else None + """); + } + + private String transformConfigClass( + String previousText, + List descriptorProps, + Set descriptorNames + ) { + String[] lines = previousText.split("\n", -1); + List result = new ArrayList<>(); + + boolean inClassBody = false; + boolean inInit = false; + boolean initParamsStarted = false; + boolean initBodyStarted = false; + boolean descriptorsInserted = false; + boolean resolverInitInserted = false; + int i = 0; + + while (i < lines.length) { + String line = lines[i]; + String trimmed = line.trim(); + + if (trimmed.startsWith("class Config")) { + inClassBody = true; + result.add(line); + i++; + continue; + } + + // Skip field declarations for descriptor properties + if (inClassBody && !inInit) { + boolean isDescriptorField = false; + for (String name : descriptorNames) { + if (trimmed.startsWith(name + ":") || trimmed.startsWith(name + " :")) { + isDescriptorField = true; + break; + } + } + if (isDescriptorField) { + i++; + // Skip following docstring if present + while (i < lines.length) { + String nextTrimmed = lines[i].trim(); + if (nextTrimmed.startsWith("\"\"\"")) { + if (nextTrimmed.endsWith("\"\"\"") && nextTrimmed.length() > 3) { + i++; + } else { + i++; + while (i < lines.length && !lines[i].trim().endsWith("\"\"\"")) { + i++; + } + i++; + } + break; + } else if (nextTrimmed.isEmpty()) { + i++; + } else { + break; + } + } + continue; + } + } + + // Detect __init__ definition start + if (trimmed.startsWith("def __init__(")) { + inInit = true; + initParamsStarted = true; + + if (!descriptorsInserted) { + insertDescriptorDeclarations(result, descriptorProps); + descriptorsInserted = true; + } + + result.add(line); + i++; + continue; + } + + // Detect end of __init__ params + if (initParamsStarted && !initBodyStarted && trimmed.equals("):")) { + result.add(line); + initBodyStarted = true; + i++; + continue; + } + + // Insert resolver initialization at start of __init__ body + if (initBodyStarted && !resolverInitInserted) { + if (!trimmed.isEmpty()) { + insertResolverInitialization(result, descriptorNames); + resolverInitInserted = true; + } + } + + // Skip self.X = X for descriptor properties in __init__ body + if (initBodyStarted) { + boolean isDescriptorInit = false; + for (String name : descriptorNames) { + if (trimmed.equals("self." + name + " = " + name)) { + isDescriptorInit = true; + break; + } + } + if (isDescriptorInit) { + i++; + continue; + } + } + + result.add(line); + i++; + } + + return String.join("\n", result); + } + + private void insertDescriptorDeclarations(List result, List descriptorProps) { + result.add(" # Config properties using descriptors"); + result.add(" _descriptors = {"); + for (ConfigProperty prop : descriptorProps) { + AwsConfigPropertyMetadata meta = DESCRIPTOR_METADATA.get(prop.name()); + StringBuilder sb = new StringBuilder(); + sb.append(" '").append(prop.name()).append("': ConfigProperty('") + .append(prop.name()).append("'"); + if (meta != null) { + meta.validatorOpt().ifPresent(sym -> + sb.append(", validator=").append(sym.getName())); + meta.customResolverOpt().ifPresent(sym -> + sb.append(", resolver_func=").append(sym.getName())); + meta.defaultValueOpt().ifPresent(val -> + sb.append(", default_value=").append(val)); + } + sb.append("),"); + result.add(sb.toString()); + } + result.add(" }"); + result.add(""); + + // Add class-level descriptor assignments with type hints + for (ConfigProperty prop : descriptorProps) { + String typeHint = prop.type().getName(); + if (prop.isNullable() && !typeHint.endsWith("| None")) { + typeHint = typeHint + " | None"; + } + result.add(" " + prop.name() + ": " + typeHint + + " = _descriptors['" + prop.name() + "'] # type: ignore[assignment]"); + + if (!prop.documentation().isEmpty()) { + String doc = prop.documentation(); + if (doc.contains("\n")) { + result.add(" \"\"\""); + for (String docLine : doc.split("\n")) { + result.add(" " + docLine); + } + result.add(" \"\"\""); + } else { + result.add(" \"\"\"" + doc + "\"\"\""); + } + } + result.add(""); + } + + // Add _resolver field declaration + result.add(" _resolver: ConfigResolver"); + result.add(""); + } + + private void insertResolverInitialization(List result, Set descriptorNames) { + result.add(" # Set instance values for descriptor properties"); + result.add(" self._resolver = ConfigResolver(sources=[EnvironmentSource()])"); + result.add(""); + result.add(" # Only set if provided (not None) to allow resolution from sources"); + result.add(" for key in self.__class__._descriptors.keys():"); + result.add(" value = locals().get(key)"); + result.add(" if value is not None:"); + result.add(" setattr(self, key, value)"); + result.add(""); + } + } +} diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfiguration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfiguration.java index 01529a4ff..e58ece02f 100644 --- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfiguration.java +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfiguration.java @@ -17,22 +17,15 @@ private AwsConfiguration() {} public static final ConfigProperty REGION = ConfigProperty.builder() .name("region") - .type(Symbol.builder().name("str | None").build()) - .documentation(" The AWS region to connect to. The configured region is used to " + .type(Symbol.builder().name("str").build()) + .documentation("The AWS region to connect to. The configured region is used to " + "determine the service endpoint.") - .nullable(false) - .useDescriptor(true) - .validator(Symbol.builder() - .name("validate_region") - .namespace("smithy_aws_core.config.validators", ".") - .addDependency(AwsPythonDependency.SMITHY_AWS_CORE) - .build()) .build(); public static final ConfigProperty RETRY_STRATEGY = ConfigProperty.builder() .name("retry_strategy") .type(Symbol.builder() - .name("RetryStrategy | RetryStrategyOptions | None") + .name("RetryStrategy | RetryStrategyOptions") .addReference(Symbol.builder() .name("RetryStrategy") .namespace("smithy_core.interfaces.retries", ".") @@ -46,8 +39,22 @@ private AwsConfiguration() {} .build()) .documentation( "The retry strategy or options for configuring retry behavior. Can be either a configured RetryStrategy or RetryStrategyOptions to create one.") - .nullable(false) - .useDescriptor(true) + .build(); + + /** + * AWS-specific metadata for descriptor-based config properties. + */ + public static final AwsConfigPropertyMetadata REGION_METADATA = AwsConfigPropertyMetadata.builder() + .validator(Symbol.builder() + .name("validate_region") + .namespace("smithy_aws_core.config.validators", ".") + .addDependency(AwsPythonDependency.SMITHY_AWS_CORE) + .build()) + .build(); + /** + * AWS-specific metadata for descriptor-based config properties. + */ + public static final AwsConfigPropertyMetadata RETRY_STRATEGY_METADATA = AwsConfigPropertyMetadata.builder() .validator(Symbol.builder() .name("validate_retry_strategy") .namespace("smithy_aws_core.config.validators", ".") diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsUserAgentIntegration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsUserAgentIntegration.java index df703730a..ade4a5740 100644 --- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsUserAgentIntegration.java +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsUserAgentIntegration.java @@ -21,6 +21,14 @@ @SmithyInternalApi public class AwsUserAgentIntegration implements PythonIntegration { + public static final AwsConfigPropertyMetadata SDK_UA_APP_ID_METADATA = AwsConfigPropertyMetadata.builder() + .validator(Symbol.builder() + .name("validate_ua_string") + .namespace("smithy_aws_core.config.validators", ".") + .addDependency(AwsPythonDependency.SMITHY_AWS_CORE) + .build()) + .build(); + public static final String USER_AGENT_PLUGIN = """ def aws_user_agent_plugin(config: $1T): config.interceptors.append( @@ -49,12 +57,6 @@ public List getClientPlugins(GenerationContext context) { "A unique and opaque application ID that is appended to the User-Agent header.") .type(Symbol.builder().name("str").build()) .nullable(true) - .useDescriptor(true) - .validator(Symbol.builder() - .name("validate_ua_string") - .namespace("smithy_aws_core.config.validators", ".") - .addDependency(AwsPythonDependency.SMITHY_AWS_CORE) - .build()) .build(); final String user_agent_plugin_file = "user_agent"; diff --git a/codegen/aws/core/src/main/resources/META-INF/services/software.amazon.smithy.python.codegen.integrations.PythonIntegration b/codegen/aws/core/src/main/resources/META-INF/services/software.amazon.smithy.python.codegen.integrations.PythonIntegration index a338df30c..4780e099b 100644 --- a/codegen/aws/core/src/main/resources/META-INF/services/software.amazon.smithy.python.codegen.integrations.PythonIntegration +++ b/codegen/aws/core/src/main/resources/META-INF/services/software.amazon.smithy.python.codegen.integrations.PythonIntegration @@ -8,3 +8,4 @@ software.amazon.smithy.python.aws.codegen.AwsProtocolsIntegration software.amazon.smithy.python.aws.codegen.AwsServiceIdIntegration software.amazon.smithy.python.aws.codegen.AwsUserAgentIntegration software.amazon.smithy.python.aws.codegen.AwsStandardRegionalEndpointsIntegration +software.amazon.smithy.python.aws.codegen.AwsConfigResolutionIntegration diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ConfigProperty.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ConfigProperty.java index bddbfe6d5..7a8b1d674 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ConfigProperty.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/ConfigProperty.java @@ -23,10 +23,6 @@ public final class ConfigProperty implements ToSmithyBuilder { private final boolean nullable; private final String documentation; private final Consumer initialize; - private final Symbol validator; - private final Symbol customResolver; - private final boolean useDescriptor; - private final String defaultValue; /** * Constructor. @@ -37,10 +33,6 @@ private ConfigProperty(Builder builder) { this.nullable = builder.nullable; this.documentation = Objects.requireNonNull(builder.documentation); this.initialize = Objects.requireNonNull(builder.initialize); - this.validator = builder.validator; - this.customResolver = builder.customResolver; - this.useDescriptor = builder.useDescriptor; - this.defaultValue = builder.defaultValue; } /** @@ -71,34 +63,6 @@ public String documentation() { return documentation; } - /** - * @return Returns the validator symbol for this property, if any. - */ - public java.util.Optional validator() { - return java.util.Optional.ofNullable(validator); - } - - /** - * @return Returns the custom resolver symbol for this property, if any. - */ - public java.util.Optional customResolver() { - return java.util.Optional.ofNullable(customResolver); - } - - /** - * @return Returns whether this property uses the ConfigProperty descriptor. - */ - public boolean useDescriptor() { - return useDescriptor; - } - - /** - * @return Returns the default value for this property, if any. - */ - public java.util.Optional defaultValue() { - return java.util.Optional.ofNullable(defaultValue); - } - /** * Initializes the config field on the config object. * @@ -130,11 +94,7 @@ public SmithyBuilder toBuilder() { .type(type) .nullable(nullable) .documentation(documentation) - .initialize(initialize) - .validator(validator) - .customResolver(customResolver) - .useDescriptor(useDescriptor) - .defaultValue(defaultValue); + .initialize(initialize); } /** @@ -147,11 +107,6 @@ public static final class Builder implements SmithyBuilder { private String documentation; private Consumer initialize = writer -> writer.write("self.$1L = $1L", name); - private Symbol validator; - private Symbol customResolver; - private boolean useDescriptor = false; - private String defaultValue; - @Override public ConfigProperty build() { return new ConfigProperty(this); @@ -227,49 +182,5 @@ public Builder initialize(Consumer initialize) { this.initialize = initialize; return this; } - - /** - * Sets the validator symbol for the config property. - * - * @param validator The validator function symbol. - * @return Returns the builder. - */ - public Builder validator(Symbol validator) { - this.validator = validator; - return this; - } - - /** - * Sets the custom resolver symbol for the config property. - * - * @param customResolver The custom resolver function symbol. - * @return Returns the builder. - */ - public Builder customResolver(Symbol customResolver) { - this.customResolver = customResolver; - return this; - } - - /** - * Sets whether the config property uses the ConfigProperty descriptor. - * - * @param useDescriptor Whether to use the descriptor pattern. - * @return Returns the builder. - */ - public Builder useDescriptor(boolean useDescriptor) { - this.useDescriptor = useDescriptor; - return this; - } - - /** - * Sets the default value for the config property. - * - * @param defaultValue The default value as a Python expression string. - * @return Returns the builder. - */ - public Builder defaultValue(String defaultValue) { - this.defaultValue = defaultValue; - return this; - } } } 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 92aa857e5..de03d42d0 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 @@ -52,6 +52,24 @@ public final class ConfigGenerator implements Runnable { .nullable(false) .initialize(writer -> writer.write("self.interceptors = interceptors or []")) .build(), + ConfigProperty.builder() + .name("retry_strategy") + .type(Symbol.builder() + .name("RetryStrategy | RetryStrategyOptions") + .addReference(Symbol.builder() + .name("RetryStrategy") + .namespace("smithy_core.interfaces.retries", ".") + .addDependency(SmithyPythonDependency.SMITHY_CORE) + .build()) + .addReference(Symbol.builder() + .name("RetryStrategyOptions") + .namespace("smithy_core.retries", ".") + .addDependency(SmithyPythonDependency.SMITHY_CORE) + .build()) + .build()) + .documentation( + "The retry strategy or options for configuring retry behavior. Can be either a configured RetryStrategy or RetryStrategyOptions to create one.") + .build(), ConfigProperty.builder() .name("endpoint_uri") .type(Symbol.builder() @@ -80,23 +98,6 @@ public final class ConfigGenerator implements Runnable { writer.write("self.endpoint_resolver = endpoint_resolver or StaticEndpointResolver()"); writer.popState(); }) - .build(), - ConfigProperty.builder() - .name("retry_strategy") - .type(Symbol.builder() - .name("RetryStrategy | RetryStrategyOptions | None") - .addReference(Symbol.builder() - .name("RetryStrategy") - .namespace("smithy_core.interfaces.retries", ".") - .addDependency(SmithyPythonDependency.SMITHY_CORE) - .build()) - .addReference(Symbol.builder() - .name("RetryStrategyOptions") - .namespace("smithy_core.retries", ".") - .addDependency(SmithyPythonDependency.SMITHY_CORE) - .build()) - .build()) - .documentation("The retry strategy or options for configuring retry behavior. Can be either a configured RetryStrategy or RetryStrategyOptions to create one.") .build()); // This list contains any properties that must be added to any http-based @@ -309,21 +310,8 @@ private void writeInterceptorsType(PythonWriter writer) { private void generateConfig(GenerationContext context, PythonWriter writer) { var configSymbol = CodegenUtils.getConfigSymbol(context.settings()); - // Initialize a set of config properties. + // Initialize a set of config properties with our base properties. var properties = new TreeSet<>(Comparator.comparing(ConfigProperty::name)); - - var model = context.model(); - var service = context.settings().service(model); - - // Add plugin properties first so they can override base properties with same name. - for (PythonIntegration integration : context.integrations()) { - for (RuntimeClientPlugin plugin : integration.getClientPlugins(context)) { - if (plugin.matchesService(model, service)) { - properties.addAll(plugin.getConfigProperties()); - } - } - } - properties.addAll(BASE_PROPERTIES); properties.addAll(getProtocolProperties(context)); @@ -334,43 +322,19 @@ private void generateConfig(GenerationContext context, PythonWriter writer) { writer.onSection(new AddAuthHelper()); } - writer.onSection(new AddGetSourceHelper()); - - var finalProperties = List.copyOf(properties); + var model = context.model(); + var service = context.settings().service(model); - // Check if any properties use descriptors - boolean hasDescriptors = finalProperties.stream().anyMatch(ConfigProperty::useDescriptor); - - // Only add config resolution imports if there are descriptor properties - if (hasDescriptors) { - writer.addDependency(SmithyPythonDependency.SMITHY_AWS_CORE); - writer.addImport("smithy_aws_core.config.property", "ConfigProperty"); - writer.addImport("smithy_aws_core.config.resolver", "ConfigResolver"); - writer.addImport("smithy_aws_core.config.sources", "EnvironmentSource"); - - // Add validator and resolver imports for properties that use descriptors - for (ConfigProperty property : finalProperties) { - if (property.useDescriptor()) { - if (property.validator().isPresent()) { - var validatorSymbol = property.validator().get(); - writer.addImport(validatorSymbol.getNamespace(), validatorSymbol.getName()); - } - if (property.customResolver().isPresent()) { - var resolverSymbol = property.customResolver().get(); - writer.addImport(resolverSymbol.getNamespace(), resolverSymbol.getName()); - } - // Add imports for types referenced in default values - if (property.defaultValue().isPresent()) { - var defaultValue = property.defaultValue().get(); - if (defaultValue.contains("RetryStrategyOptions")) { - writer.addDependency(SmithyPythonDependency.SMITHY_CORE); - writer.addImport("smithy_core.retries", "RetryStrategyOptions"); - } - } + // Add any relevant config properties from plugins. + for (PythonIntegration integration : context.integrations()) { + for (RuntimeClientPlugin plugin : integration.getClientPlugins(context)) { + if (plugin.matchesService(model, service)) { + properties.addAll(plugin.getConfigProperties()); } } } + var finalProperties = List.copyOf(properties); final String serviceId = context.settings() .service(context.model()) .getTrait(ServiceTrait.class) @@ -385,8 +349,6 @@ class $L: ${C|} - ${C|} - def __init__( self, *, @@ -397,134 +359,31 @@ def __init__( configSymbol.getName(), serviceId, writer.consumer(w -> writePropertyDeclarations(w, finalProperties)), - writer.consumer(w -> writeDescriptorDeclarations(w, finalProperties, context)), writer.consumer(w -> writeInitParams(w, finalProperties)), writer.consumer(w -> initializeProperties(w, finalProperties))); writer.popState(); } - // Write descriptor declarations for properties using ConfigProperty descriptor - private void writeDescriptorDeclarations(PythonWriter writer, Collection properties, GenerationContext context) { - boolean hasDescriptors = properties.stream().anyMatch(ConfigProperty::useDescriptor); - - if (!hasDescriptors) { - return; - } - - writer.write("# Config properties using descriptors"); - writer.write("_descriptors = {"); - writer.indent(); - - for (ConfigProperty property : properties) { - if (property.useDescriptor()) { - writer.writeInline("'$L': ConfigProperty('$L'", - property.name(), - property.name()); - - if (property.validator().isPresent()) { - writer.writeInline(", validator=$L", property.validator().get().getName()); - } - - if (property.customResolver().isPresent()) { - writer.writeInline(", resolver_func=$L", property.customResolver().get().getName()); - } - - if (property.defaultValue().isPresent()) { - writer.writeInline(", default_value=$L", property.defaultValue().get()); - } - - writer.write("),"); - } - } - - writer.dedent(); - writer.write("}"); - writer.write(""); - - for (ConfigProperty property : properties) { - if (property.useDescriptor()) { - var typeHint = property.isNullable() - ? "$T | None" - : "$T"; - writer.write("$L: " + typeHint + " = _descriptors['$L'] # type: ignore[assignment]", - property.name(), - property.type(), - property.name()); - - if (!property.documentation().isEmpty()) { - writer.writeDocs(property.documentation(), context); - } - writer.write(""); - } - } - writer.write(""); - } - private void writePropertyDeclarations(PythonWriter writer, Collection properties) { for (ConfigProperty property : properties) { - // Skip descriptor properties - they're declared above - if (property.useDescriptor()) { - continue; - } - - String typeName = property.type().getName(); - String formatString; - if (property.isNullable() && !typeName.endsWith("| None")) { - formatString = "$L: $T | None"; - } else { - formatString = "$L: $T"; - } + var formatString = property.isNullable() + ? "$L: $T | None" + : "$L: $T"; writer.write(formatString, property.name(), property.type()); writer.writeDocs(property.documentation(), context); writer.write(""); } - - // Add _resolver declaration only if there are descriptor properties - boolean hasDescriptors = properties.stream().anyMatch(ConfigProperty::useDescriptor); - if (hasDescriptors) { - writer.write("_resolver: ConfigResolver"); - writer.write(""); - } } private void writeInitParams(PythonWriter writer, Collection properties) { for (ConfigProperty property : properties) { - String typeName = property.type().getName(); - if (typeName.endsWith("| None")) { - writer.write("$L: $T = None,", property.name(), property.type()); - } else { - writer.write("$L: $T | None = None,", property.name(), property.type()); - } + writer.write("$L: $T | None = None,", property.name(), property.type()); } } private void initializeProperties(PythonWriter writer, Collection properties) { - var descriptorProperties = properties.stream() - .filter(ConfigProperty::useDescriptor) - .toList(); - - if (!descriptorProperties.isEmpty()) { - writer.write("# Set instance values for descriptor properties"); - writer.write("self._resolver = ConfigResolver(sources=[EnvironmentSource()])"); - writer.write(""); - - writer.write("# Only set if provided (not None) to allow resolution from sources"); - writer.write("for key in self.__class__._descriptors.keys():"); - writer.indent(); - writer.write("value = locals().get(key)"); - writer.write("if value is not None:"); - writer.indent(); - writer.write("setattr(self, key, value)"); - writer.dedent(); - writer.dedent(); - writer.write(""); - } - - // Finally, initialize non-descriptor properties normally for (ConfigProperty property : properties) { - if (!property.useDescriptor()) { - property.initialize(writer); - } + property.initialize(writer); } } @@ -559,45 +418,4 @@ def set_auth_scheme(self, scheme: AuthScheme[Any, Any, Any, Any]) -> None: """); } } - - private static final class AddGetSourceHelper implements CodeInterceptor { - @Override - public Class sectionType() { - return ConfigSection.class; - } - - @Override - public void write(PythonWriter writer, String previousText, ConfigSection section) { - // Check if there are any descriptor properties - boolean hasDescriptors = section.properties() - .stream() - .anyMatch(ConfigProperty::useDescriptor); - - if (!hasDescriptors) { - // No descriptor properties, just write previous text - writer.write(previousText); - return; - } - - writer.write(previousText); - writer.addImport("smithy_aws_core.config.source_info", "SourceInfo"); - - writer.write(""" - - def get_source(self, key: str) -> SourceInfo | None: - \"""Get the source that provided a configuration value. - - Args: - key: The configuration key (e.g., 'region', 'retry_strategy') - - Returns: - The source info (SimpleSource('source_name') or - ComplexSource({"retry_mode": "source1", "max_attempts": "source2"})), - or None if the key hasn't been resolved yet. - \""" - cached = self.__dict__.get(f'_cache_{key}') - return cached[1] if cached else None - """); - } - } } From 3abda5e5e12c1515d67332f40c9d36316e9f0384 Mon Sep 17 00:00:00 2001 From: ubaskota <19787410+ubaskota@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:56:18 -0400 Subject: [PATCH 2/3] Address reviews --- .../AwsConfigResolutionIntegration.java | 356 ++++++++---------- .../codegen/generators/ConfigGenerator.java | 13 + .../codegen/sections/ConfigSection.java | 12 +- 3 files changed, 185 insertions(+), 196 deletions(-) diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java index 194f848d6..5000418fb 100644 --- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java @@ -4,10 +4,8 @@ */ package software.amazon.smithy.python.aws.codegen; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import software.amazon.smithy.python.codegen.ConfigProperty; import software.amazon.smithy.python.codegen.GenerationContext; @@ -36,39 +34,129 @@ public class AwsConfigResolutionIntegration implements PythonIntegration { public List> interceptors( GenerationContext context ) { - return List.of(new ConfigResolutionInterceptor()); + return List.of( + new PropertyDeclarationInterceptor(), + new PropertyInitInterceptor(), + new PreDeclarationsInterceptor(), + new PreInitInterceptor(), + new ConfigTailInterceptor() + ); } - private static final class ConfigResolutionInterceptor - implements CodeInterceptor { + // Replaces plain field declarations with descriptor assignments for properties + // that have AWS metadata registered. + private static final class PropertyDeclarationInterceptor + implements CodeInterceptor { @Override - public Class sectionType() { - return ConfigSection.class; + public Class sectionType() { + return ConfigSection.PropertyDeclarationSection.class; } @Override - public void write(PythonWriter writer, String previousText, ConfigSection section) { - // Find properties that have descriptor metadata registered + public void write( + PythonWriter writer, + String previousText, + ConfigSection.PropertyDeclarationSection section + ) { + ConfigProperty prop = section.property(); + AwsConfigPropertyMetadata meta = DESCRIPTOR_METADATA.get(prop.name()); + + if (meta == null) { + writer.write(previousText); + return; + } + + String typeHint = prop.type().getName(); + if (prop.isNullable() && !typeHint.endsWith("| None")) { + typeHint = typeHint + " | None"; + } + writer.write("$L: $L = _descriptors['$L'] # type: ignore[assignment]", + prop.name(), typeHint, prop.name()); + if (!prop.documentation().isEmpty()) { + String doc = prop.documentation(); + if (doc.contains("\n")) { + writer.write("\"\"\""); + for (String docLine : doc.split("\n")) { + writer.write(docLine); + } + writer.write("\"\"\""); + } else { + writer.write("\"\"\"$L\"\"\"", doc); + } + } + } + } + + // Skips `self.X = X` initialization for descriptor properties since the + // descriptor handles resolution. + private static final class PropertyInitInterceptor + implements CodeInterceptor { + + @Override + public Class sectionType() { + return ConfigProperty.InitializeConfigPropertySection.class; + } + + @Override + public void write( + PythonWriter writer, + String previousText, + ConfigProperty.InitializeConfigPropertySection section + ) { + if (DESCRIPTOR_METADATA.containsKey(section.property().name())) { + return; + } + writer.write(previousText); + } + } + + // Injects _descriptors dict and _resolver field before property declarations. + private static final class PreDeclarationsInterceptor + implements CodeInterceptor { + + @Override + public Class sectionType() { + return ConfigSection.PrePropertyDeclarationsSection.class; + } + + @Override + public void write( + PythonWriter writer, + String previousText, + ConfigSection.PrePropertyDeclarationsSection section + ) { List descriptorProps = section.properties().stream() .filter(p -> DESCRIPTOR_METADATA.containsKey(p.name())) .collect(Collectors.toList()); if (descriptorProps.isEmpty()) { - writer.write(previousText); return; } - Set descriptorNames = descriptorProps.stream() - .map(ConfigProperty::name) - .collect(Collectors.toSet()); - addImports(writer, descriptorProps); - String transformed = transformConfigClass(previousText, descriptorProps, descriptorNames); - writer.write(transformed); - - appendGetSourceMethod(writer); + writer.write("# Config properties using descriptors"); + writer.openBlock("_descriptors = {"); + for (ConfigProperty prop : descriptorProps) { + AwsConfigPropertyMetadata meta = DESCRIPTOR_METADATA.get(prop.name()); + StringBuilder sb = new StringBuilder(); + sb.append("'").append(prop.name()).append("': ConfigProperty('") + .append(prop.name()).append("'"); + if (meta != null) { + meta.validatorOpt().ifPresent(sym -> + sb.append(", validator=").append(sym.getName())); + meta.customResolverOpt().ifPresent(sym -> + sb.append(", resolver_func=").append(sym.getName())); + meta.defaultValueOpt().ifPresent(val -> + sb.append(", default_value=").append(val)); + } + sb.append("),"); + writer.write(sb.toString()); + } + writer.closeBlock("}"); + writer.write(""); + writer.write("_resolver: ConfigResolver"); } private void addImports(PythonWriter writer, List descriptorProps) { @@ -93,201 +181,79 @@ private void addImports(PythonWriter writer, List descriptorProp }); } } + } - private void appendGetSourceMethod(PythonWriter writer) { - writer.write(""" - - def get_source(self, key: str) -> SourceInfo | None: - \"""Get the source that provided a configuration value. - - Args: - key: The configuration key (e.g., 'region', 'retry_strategy') + // Injects resolver initialization at the start of __init__. + private static final class PreInitInterceptor + implements CodeInterceptor { - Returns: - The source info (SimpleSource or ComplexSource), - or None if the key hasn't been resolved yet. - \""" - cached = self.__dict__.get(f'_cache_{key}') - return cached[1] if cached else None - """); + @Override + public Class sectionType() { + return ConfigSection.PreInitializePropertiesSection.class; } - private String transformConfigClass( + @Override + public void write( + PythonWriter writer, String previousText, - List descriptorProps, - Set descriptorNames + ConfigSection.PreInitializePropertiesSection section ) { - String[] lines = previousText.split("\n", -1); - List result = new ArrayList<>(); - - boolean inClassBody = false; - boolean inInit = false; - boolean initParamsStarted = false; - boolean initBodyStarted = false; - boolean descriptorsInserted = false; - boolean resolverInitInserted = false; - int i = 0; - - while (i < lines.length) { - String line = lines[i]; - String trimmed = line.trim(); - - if (trimmed.startsWith("class Config")) { - inClassBody = true; - result.add(line); - i++; - continue; - } + boolean hasDescriptors = section.properties().stream() + .anyMatch(p -> DESCRIPTOR_METADATA.containsKey(p.name())); - // Skip field declarations for descriptor properties - if (inClassBody && !inInit) { - boolean isDescriptorField = false; - for (String name : descriptorNames) { - if (trimmed.startsWith(name + ":") || trimmed.startsWith(name + " :")) { - isDescriptorField = true; - break; - } - } - if (isDescriptorField) { - i++; - // Skip following docstring if present - while (i < lines.length) { - String nextTrimmed = lines[i].trim(); - if (nextTrimmed.startsWith("\"\"\"")) { - if (nextTrimmed.endsWith("\"\"\"") && nextTrimmed.length() > 3) { - i++; - } else { - i++; - while (i < lines.length && !lines[i].trim().endsWith("\"\"\"")) { - i++; - } - i++; - } - break; - } else if (nextTrimmed.isEmpty()) { - i++; - } else { - break; - } - } - continue; - } - } - - // Detect __init__ definition start - if (trimmed.startsWith("def __init__(")) { - inInit = true; - initParamsStarted = true; + if (!hasDescriptors) { + return; + } - if (!descriptorsInserted) { - insertDescriptorDeclarations(result, descriptorProps); - descriptorsInserted = true; - } + writer.write("self._resolver = ConfigResolver(sources=[EnvironmentSource()])"); + writer.write(""); + writer.write("# Only set if provided (not None) to allow resolution from sources"); + writer.write("for key in self.__class__._descriptors.keys():"); + writer.indent(); + writer.write("value = locals().get(key)"); + writer.write("if value is not None:"); + writer.indent(); + writer.write("setattr(self, key, value)"); + writer.dedent(); + writer.dedent(); + } + } - result.add(line); - i++; - continue; - } + // Appends get_source() method to the Config class. + private static final class ConfigTailInterceptor + implements CodeInterceptor { - // Detect end of __init__ params - if (initParamsStarted && !initBodyStarted && trimmed.equals("):")) { - result.add(line); - initBodyStarted = true; - i++; - continue; - } + @Override + public Class sectionType() { + return ConfigSection.class; + } - // Insert resolver initialization at start of __init__ body - if (initBodyStarted && !resolverInitInserted) { - if (!trimmed.isEmpty()) { - insertResolverInitialization(result, descriptorNames); - resolverInitInserted = true; - } - } + @Override + public void write(PythonWriter writer, String previousText, ConfigSection section) { + boolean hasDescriptors = section.properties().stream() + .anyMatch(p -> DESCRIPTOR_METADATA.containsKey(p.name())); - // Skip self.X = X for descriptor properties in __init__ body - if (initBodyStarted) { - boolean isDescriptorInit = false; - for (String name : descriptorNames) { - if (trimmed.equals("self." + name + " = " + name)) { - isDescriptorInit = true; - break; - } - } - if (isDescriptorInit) { - i++; - continue; - } - } + writer.write(previousText); - result.add(line); - i++; + if (!hasDescriptors) { + return; } - return String.join("\n", result); - } - - private void insertDescriptorDeclarations(List result, List descriptorProps) { - result.add(" # Config properties using descriptors"); - result.add(" _descriptors = {"); - for (ConfigProperty prop : descriptorProps) { - AwsConfigPropertyMetadata meta = DESCRIPTOR_METADATA.get(prop.name()); - StringBuilder sb = new StringBuilder(); - sb.append(" '").append(prop.name()).append("': ConfigProperty('") - .append(prop.name()).append("'"); - if (meta != null) { - meta.validatorOpt().ifPresent(sym -> - sb.append(", validator=").append(sym.getName())); - meta.customResolverOpt().ifPresent(sym -> - sb.append(", resolver_func=").append(sym.getName())); - meta.defaultValueOpt().ifPresent(val -> - sb.append(", default_value=").append(val)); - } - sb.append("),"); - result.add(sb.toString()); - } - result.add(" }"); - result.add(""); + writer.write(""" - // Add class-level descriptor assignments with type hints - for (ConfigProperty prop : descriptorProps) { - String typeHint = prop.type().getName(); - if (prop.isNullable() && !typeHint.endsWith("| None")) { - typeHint = typeHint + " | None"; - } - result.add(" " + prop.name() + ": " + typeHint + - " = _descriptors['" + prop.name() + "'] # type: ignore[assignment]"); - - if (!prop.documentation().isEmpty()) { - String doc = prop.documentation(); - if (doc.contains("\n")) { - result.add(" \"\"\""); - for (String docLine : doc.split("\n")) { - result.add(" " + docLine); - } - result.add(" \"\"\""); - } else { - result.add(" \"\"\"" + doc + "\"\"\""); - } - } - result.add(""); - } + def get_source(self, key: str) -> SourceInfo | None: + \"""Get the source that provided a configuration value. - // Add _resolver field declaration - result.add(" _resolver: ConfigResolver"); - result.add(""); - } + Args: + key: The configuration key (e.g., 'region', 'retry_strategy') - private void insertResolverInitialization(List result, Set descriptorNames) { - result.add(" # Set instance values for descriptor properties"); - result.add(" self._resolver = ConfigResolver(sources=[EnvironmentSource()])"); - result.add(""); - result.add(" # Only set if provided (not None) to allow resolution from sources"); - result.add(" for key in self.__class__._descriptors.keys():"); - result.add(" value = locals().get(key)"); - result.add(" if value is not None:"); - result.add(" setattr(self, key, value)"); - result.add(""); + Returns: + The source info (SimpleSource or ComplexSource), + or None if the key hasn't been resolved yet. + \""" + cached = self.__dict__.get(f'_cache_{key}') + return cached[1] if cached else None + """); } } } 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..dbcbdd88a 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 @@ -349,28 +349,41 @@ class $L: ${C|} + ${C|} + def __init__( self, *, ${C|} ): ${C|} + ${C|} """, configSymbol.getName(), serviceId, + writer.consumer(w -> { + w.pushState(new ConfigSection.PrePropertyDeclarationsSection(finalProperties)); + w.popState(); + }), writer.consumer(w -> writePropertyDeclarations(w, finalProperties)), writer.consumer(w -> writeInitParams(w, finalProperties)), + writer.consumer(w -> { + w.pushState(new ConfigSection.PreInitializePropertiesSection(finalProperties)); + w.popState(); + }), writer.consumer(w -> initializeProperties(w, finalProperties))); writer.popState(); } private void writePropertyDeclarations(PythonWriter writer, Collection properties) { for (ConfigProperty property : properties) { + writer.pushState(new ConfigSection.PropertyDeclarationSection(property)); var formatString = property.isNullable() ? "$L: $T | None" : "$L: $T"; writer.write(formatString, property.name(), property.type()); writer.writeDocs(property.documentation(), context); + writer.popState(); writer.write(""); } } diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/ConfigSection.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/ConfigSection.java index 13ae6110d..ccf9dc80a 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/ConfigSection.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/ConfigSection.java @@ -15,4 +15,14 @@ * @param properties The list of properties that need to be present on the config. */ @SmithyInternalApi -public record ConfigSection(List properties) implements CodeSection {} +public record ConfigSection(List properties) implements CodeSection { + + /** Section for a single config property's class-level field declaration. */ + public record PropertyDeclarationSection(ConfigProperty property) implements CodeSection {} + + /** Section before property declarations, for injecting class-level fields. */ + public record PrePropertyDeclarationsSection(List properties) implements CodeSection {} + + /** Section before property initializations in __init__, for injecting setup code. */ + public record PreInitializePropertiesSection(List properties) implements CodeSection {} +} From 4e34cb65b20d4dec52f1e250d8ff168fddf189b8 Mon Sep 17 00:00:00 2001 From: ubaskota <19787410+ubaskota@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:23:20 -0400 Subject: [PATCH 3/3] Address reviews --- .../AwsConfigResolutionIntegration.java | 27 ++++++++----------- .../python/aws/codegen/AwsConfiguration.java | 1 + .../src/smithy_aws_core/config/resolver.py | 3 +-- .../src/smithy_aws_core}/interfaces/config.py | 0 4 files changed, 13 insertions(+), 18 deletions(-) rename packages/{smithy-core/src/smithy_core => smithy-aws-core/src/smithy_aws_core}/interfaces/config.py (100%) diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java index 5000418fb..45161c410 100644 --- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfigResolutionIntegration.java @@ -35,7 +35,7 @@ public List> inte GenerationContext context ) { return List.of( - new PropertyDeclarationInterceptor(), + new PropertyDeclarationInterceptor(context), new PropertyInitInterceptor(), new PreDeclarationsInterceptor(), new PreInitInterceptor(), @@ -48,6 +48,12 @@ public List> inte private static final class PropertyDeclarationInterceptor implements CodeInterceptor { + private final GenerationContext context; + + PropertyDeclarationInterceptor(GenerationContext context) { + this.context = context; + } + @Override public Class sectionType() { return ConfigSection.PropertyDeclarationSection.class; @@ -63,7 +69,7 @@ public void write( AwsConfigPropertyMetadata meta = DESCRIPTOR_METADATA.get(prop.name()); if (meta == null) { - writer.write(previousText); + writer.write(previousText.stripTrailing()); return; } @@ -73,18 +79,7 @@ public void write( } writer.write("$L: $L = _descriptors['$L'] # type: ignore[assignment]", prop.name(), typeHint, prop.name()); - if (!prop.documentation().isEmpty()) { - String doc = prop.documentation(); - if (doc.contains("\n")) { - writer.write("\"\"\""); - for (String docLine : doc.split("\n")) { - writer.write(docLine); - } - writer.write("\"\"\""); - } else { - writer.write("\"\"\"$L\"\"\"", doc); - } - } + writer.writeDocs(prop.documentation(), context); } } @@ -107,7 +102,7 @@ public void write( if (DESCRIPTOR_METADATA.containsKey(section.property().name())) { return; } - writer.write(previousText); + writer.write(previousText.stripTrailing()); } } @@ -233,7 +228,7 @@ public void write(PythonWriter writer, String previousText, ConfigSection sectio boolean hasDescriptors = section.properties().stream() .anyMatch(p -> DESCRIPTOR_METADATA.containsKey(p.name())); - writer.write(previousText); + writer.write(previousText.stripTrailing()); if (!hasDescriptors) { return; diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfiguration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfiguration.java index e58ece02f..361a23301 100644 --- a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfiguration.java +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsConfiguration.java @@ -20,6 +20,7 @@ private AwsConfiguration() {} .type(Symbol.builder().name("str").build()) .documentation("The AWS region to connect to. The configured region is used to " + "determine the service endpoint.") + .nullable(false) .build(); public static final ConfigProperty RETRY_STRATEGY = ConfigProperty.builder() diff --git a/packages/smithy-aws-core/src/smithy_aws_core/config/resolver.py b/packages/smithy-aws-core/src/smithy_aws_core/config/resolver.py index a17a36b93..3444e987c 100644 --- a/packages/smithy-aws-core/src/smithy_aws_core/config/resolver.py +++ b/packages/smithy-aws-core/src/smithy_aws_core/config/resolver.py @@ -3,9 +3,8 @@ from collections.abc import Sequence from typing import Any -from smithy_core.interfaces.config import ConfigSource - from smithy_aws_core.config.source_info import SimpleSource +from smithy_aws_core.interfaces.config import ConfigSource class ConfigResolver: diff --git a/packages/smithy-core/src/smithy_core/interfaces/config.py b/packages/smithy-aws-core/src/smithy_aws_core/interfaces/config.py similarity index 100% rename from packages/smithy-core/src/smithy_core/interfaces/config.py rename to packages/smithy-aws-core/src/smithy_aws_core/interfaces/config.py