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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public List<RuntimeClientPlugin> getClientPlugins(GenerationContext context) {
moduleName + ".",
writer -> {
writer.write(USER_AGENT_PLUGIN,
CodegenUtils.getConfigSymbol(c.settings()),
CodegenUtils.getConfigSymbol(c.settings(), c.model()),
userAgentInterceptor,
versionSymbol,
serviceId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public void run() {

private void generateService(PythonWriter writer) {
var serviceSymbol = symbolProvider.toSymbol(service);
var configSymbol = CodegenUtils.getConfigSymbol(context.settings());
var pluginSymbol = CodegenUtils.getPluginSymbol(context.settings());
var configSymbol = CodegenUtils.getConfigSymbol(context.settings(), context.model());
var pluginSymbol = CodegenUtils.getPluginSymbol(context.settings(), context.model());
writer.addLogger();

writer.openBlock("class $L:", "", serviceSymbol.getName(), () -> {
Expand Down Expand Up @@ -134,7 +134,7 @@ private void writeConstructorDocs(PythonWriter writer, String clientName) {
private void generateOperation(PythonWriter writer, OperationShape operation) {
var operationSymbol = symbolProvider.toSymbol(operation);
var operationMethodSymbol = operationSymbol.expectProperty(OPERATION_METHOD);
var pluginSymbol = CodegenUtils.getPluginSymbol(context.settings());
var pluginSymbol = CodegenUtils.getPluginSymbol(context.settings(), context.model());

var input = model.expectShape(operation.getInputShape());
var inputSymbol = symbolProvider.toSymbol(input);
Expand Down Expand Up @@ -219,7 +219,7 @@ private void writeSharedOperationInit(
writer.addStdlibImport("copy", "deepcopy");

writer.write("""
operation_plugins: list[Plugin] = [
operation_plugins: list[${plugin:T}] = [
$C
]
if plugins:
Expand Down Expand Up @@ -259,7 +259,7 @@ private void generateEventStreamOperation(PythonWriter writer, OperationShape op
writer.putContext("operation", operationSymbol);
var operationMethodSymbol = operationSymbol.expectProperty(OPERATION_METHOD);
writer.putContext("operationName", operationMethodSymbol.getName());
var pluginSymbol = CodegenUtils.getPluginSymbol(context.settings());
var pluginSymbol = CodegenUtils.getPluginSymbol(context.settings(), context.model());
writer.putContext("plugin", pluginSymbol);

var input = model.expectShape(operation.getInputShape());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.logging.Logger;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.aws.traits.ServiceTrait;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.NullableIndex;
import software.amazon.smithy.model.node.Node;
Expand Down Expand Up @@ -63,24 +64,46 @@ public final class CodegenUtils {
private CodegenUtils() {}

/**
* Gets the configuration object symbol for the service.
*
* <p>For AWS services with a {@code ServiceTrait}, this derives the name from the SDK ID
* (e.g., "Bedrock Runtime" becomes "BedrockRuntimeConfig"). For services without a
* {@code ServiceTrait}, falls back to the generic "Config" name.
*
* @param settings The client settings, used to account for module configuration.
* @param model The model containing the service shape.
* @return Returns the client's configuration object symbol.
*/
public static Symbol getConfigSymbol(PythonSettings settings) {
public static Symbol getConfigSymbol(PythonSettings settings, Model model) {
var service = settings.service(model);
var name = service.getTrait(ServiceTrait.class)
.map(trait -> StringUtils.capitalize(trait.getSdkId()).replace(" ", "") + "Config")
.orElse("Config");
return Symbol.builder()
.name("Config")
.name(name)
.namespace(String.format("%s.config", settings.moduleName()), ".")
.definitionFile(String.format("./src/%s/config.py", settings.moduleName()))
.build();
}

/**
* Gets the plugin type hint symbol for the service.
*
* <p>For AWS services with a {@code ServiceTrait}, this derives the name from the SDK ID
* (e.g., "Bedrock Runtime" becomes "BedrockRuntimePlugin"). For services without a
* {@code ServiceTrait}, falls back to the generic "Plugin" name.
*
* @param settings The client settings, used to account for module configuration.
* @param model The model containing the service shape.
* @return Returns the client's plugin type hint symbol.
*/
public static Symbol getPluginSymbol(PythonSettings settings) {
public static Symbol getPluginSymbol(PythonSettings settings, Model model) {
var service = settings.service(model);
var name = service.getTrait(ServiceTrait.class)
.map(trait -> StringUtils.capitalize(trait.getSdkId()).replace(" ", "") + "Plugin")
.orElse("Plugin");
return Symbol.builder()
.name("Plugin")
.name(name)
.namespace(String.format("%s.config", settings.moduleName()), ".")
.definitionFile(String.format("./src/%s/config.py", settings.moduleName()))
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ private void generateRequestTest(OperationShape operation, HttpRequestTestCase t
${C|}
)
""",
CodegenUtils.getConfigSymbol(context.settings()),
CodegenUtils.getConfigSymbol(context.settings(), context.model()),
host,
path,
REQUEST_TEST_ASYNC_HTTP_CLIENT_SYMBOL,
Expand Down Expand Up @@ -453,7 +453,7 @@ private void generateResponseTest(OperationShape operation, HttpResponseTestCase
${C|}
)
""",
CodegenUtils.getConfigSymbol(context.settings()),
CodegenUtils.getConfigSymbol(context.settings(), context.model()),
RESPONSE_TEST_ASYNC_HTTP_CLIENT_SYMBOL,
testCase.getCode(),
CodegenUtils.toTuples(testCase.getHeaders()),
Expand Down Expand Up @@ -508,7 +508,7 @@ private void generateErrorResponseTest(
${C|}
)
""",
CodegenUtils.getConfigSymbol(context.settings()),
CodegenUtils.getConfigSymbol(context.settings(), context.model()),
RESPONSE_TEST_ASYNC_HTTP_CLIENT_SYMBOL,
testCase.getCode(),
CodegenUtils.toTuples(testCase.getHeaders()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,21 +261,57 @@ private static void writeDefaultAuthSchemes(GenerationContext context, PythonWri

@Override
public void run() {
var config = CodegenUtils.getConfigSymbol(context.settings());
var model = context.model();
var config = CodegenUtils.getConfigSymbol(context.settings(), model);
var genericConfigName = "Config";
context.writerDelegator().useFileWriter(config.getDefinitionFile(), config.getNamespace(), writer -> {
writeInterceptorsType(writer);
generateConfig(context, writer);

// Generate deprecated Config alias if name differs from the generic name
if (!config.getName().equals(genericConfigName)) {
writer.addStdlibImport("warnings", "warn");
writer.addStdlibImport("typing", "Any");
writer.write("""


class $1L($2L):
\"""Deprecated: Use :class:`$2L` instead.\"""

def __init__(self, *args: Any, **kwargs: Any):
warn(
"Importing '$1L' is deprecated. Use '$2L' instead.",
DeprecationWarning,
stacklevel=2,
)
super().__init__(*args, **kwargs)
""",
genericConfigName,
config.getName());
}
});

// Generate the plugin symbol. This is just a callable. We could do something
// like have a class to implement, but that seems unnecessarily burdensome for
// a single function.
var plugin = CodegenUtils.getPluginSymbol(context.settings());
var plugin = CodegenUtils.getPluginSymbol(context.settings(), model);
var genericPluginName = "Plugin";
context.writerDelegator().useFileWriter(plugin.getDefinitionFile(), plugin.getNamespace(), writer -> {
writer.addStdlibImport("typing", "Callable");
writer.addStdlibImport("typing", "TypeAlias");
writer.write("$L: TypeAlias = Callable[[$T], None]", plugin.getName(), config);
writer.writeDocs("A callable that allows customizing the config object on each request.", context);

// Generate deprecated Plugin alias if name differs from the generic name
if (!plugin.getName().equals(genericPluginName)) {
writer.write("""

$1L: TypeAlias = $2L
\"""Deprecated: Use :data:`$2L` instead.\"""
""",
genericPluginName,
plugin.getName());
}
});
}

Expand Down Expand Up @@ -308,7 +344,7 @@ private void writeInterceptorsType(PythonWriter writer) {
}

private void generateConfig(GenerationContext context, PythonWriter writer) {
var configSymbol = CodegenUtils.getConfigSymbol(context.settings());
var configSymbol = CodegenUtils.getConfigSymbol(context.settings(), context.model());

// Initialize a set of config properties with our base properties.
var properties = new TreeSet<>(Comparator.comparing(ConfigProperty::name));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "breaking",
"description": "Renamed generated Config to <ServiceName>Config and Plugin to <ServiceName>Plugin. Deprecated aliases are provided."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "breaking",
"description": "Renamed generated Config to <ServiceName>Config and Plugin to <ServiceName>Plugin. Deprecated aliases are provided."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "breaking",
"description": "Renamed generated Config to <ServiceName>Config and Plugin to <ServiceName>Plugin. Deprecated aliases are provided."
}
Loading