Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.python.aws.codegen;

import software.amazon.smithy.python.codegen.PythonDependency;
import software.amazon.smithy.utils.SmithyUnstableApi;

/**
* AWS Dependencies used in the smithy python generator.
*/
@SmithyUnstableApi
public class AwsPythonDependency {
/**
* The core aws smithy runtime python package.
*
* <p>While in development this will use the develop branch.
*/
public static final PythonDependency SMITHY_AWS_CORE = new PythonDependency(
"smithy_aws_core",
// You'll need to locally install this before we publish
"==0.0.1",
PythonDependency.Type.DEPENDENCY,
false);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package software.amazon.smithy.python.aws.codegen;

import java.util.List;
import software.amazon.smithy.aws.traits.ServiceTrait;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.python.codegen.CodegenUtils;
import software.amazon.smithy.python.codegen.ConfigProperty;
import software.amazon.smithy.python.codegen.GenerationContext;
import software.amazon.smithy.python.codegen.SmithyPythonDependency;
import software.amazon.smithy.python.codegen.integrations.EndpointsGenerator;
import software.amazon.smithy.python.codegen.writer.PythonWriter;

/**
* Generates endpoint config and resolution logic for standard regional endpoints.
*/
public class AwsRegionalEndpointsGenerator implements EndpointsGenerator {
@Override
public List<ConfigProperty> endpointsConfig(GenerationContext context) {
return List.of(ConfigProperty.builder()
.name("endpoint_resolver")
.type(Symbol.builder()
.name("EndpointResolver[RegionalEndpointParameters]")
.addReference(Symbol.builder()
.name("RegionalEndpointParameters")
.namespace(AwsPythonDependency.SMITHY_AWS_CORE.packageName() + ".endpoints.standard_regional", ".")
.addDependency(AwsPythonDependency.SMITHY_AWS_CORE)
.build())
.addReference(Symbol.builder()
.name("EndpointResolver")
.namespace("smithy_http.aio.interfaces", ".")
.addDependency(SmithyPythonDependency.SMITHY_HTTP)
.build())
.build())
.documentation("""
The endpoint resolver used to resolve the final endpoint per-operation based on the \
configuration.""")
.nullable(false)
.initialize(writer -> {
writer.addImport("smithy_aws_core.endpoints.standard_regional", "StandardRegionalEndpointsResolver");
String endpointPrefix = context.settings().service(context.model())
.getTrait(ServiceTrait.class)
.map(ServiceTrait::getEndpointPrefix)
.orElse(context.settings().service().getName());

writer.write(
"self.endpoint_resolver = endpoint_resolver or StandardRegionalEndpointsResolver(endpoint_prefix='$L')",
endpointPrefix);
})
.build(),
ConfigProperty.builder()
.name("endpoint_uri")
.type(Symbol.builder()
.name("str | URI")
.addReference(Symbol.builder()
.name("URI")
.namespace("smithy_core.interfaces", ".")
.addDependency(SmithyPythonDependency.SMITHY_CORE)
.build())
.build())
.documentation("A static URI to route requests to.")
.build(),
ConfigProperty.builder()
.name("region")
.type(Symbol.builder().name("str").build())
.documentation(" The AWS region to connect to. The configured region is used to "
+ "determine the service endpoint.")
.build());
}

@Override
public void renderEndpointParameterConstruction(GenerationContext context, PythonWriter writer) {

writer.addDependency(AwsPythonDependency.SMITHY_AWS_CORE);
writer.addImport("smithy_aws_core.endpoints.standard_regional", "RegionalEndpointParameters");
writer.write("""
endpoint_parameters = RegionalEndpointParameters(
sdk_endpoint=config.endpoint_uri,
region=config.region
)
""");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package software.amazon.smithy.python.aws.codegen;

import java.util.Optional;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.python.codegen.integrations.EndpointsGenerator;
import software.amazon.smithy.python.codegen.integrations.PythonIntegration;

public class AwsRegionalEndpointsIntegration implements PythonIntegration {
@Override
public Optional<EndpointsGenerator> getEndpointsGenerator(Model model, ServiceShape service) {
return Optional.of(new AwsRegionalEndpointsGenerator());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@

software.amazon.smithy.python.aws.codegen.AwsAuthIntegration
software.amazon.smithy.python.aws.codegen.AwsProtocolsIntegration
software.amazon.smithy.python.aws.codegen.AwsRegionalEndpointsIntegration
Original file line number Diff line number Diff line change
Expand Up @@ -442,20 +442,14 @@ async def _handle_attempt(
if (context.applicationProtocol().isHttpProtocol()) {
writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
writer.addDependency(SmithyPythonDependency.SMITHY_HTTP);
// TODO: implement the endpoints 2.0 spec and remove the hard-coded handling of static params.
writer.addImport("smithy_http.endpoints", "StaticEndpointParams");
writer.addImport("smithy_core", "URI");
writer.write("# Step 7f: Invoke endpoint_resolver.resolve_endpoint");

context.endpointsGenerator().renderEndpointParameterConstruction(context, writer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a prime use case for a CodeSection. You would create a section like this called EndpointParamSection or something like that with the necessary context, and then a PythonIntegration would return a CodeInterceptor in its interceptors method.

Then here you'd have something like:

writer.pushState(new EndpointParamsSection(context));
// Default implementation. Interceptors can be blow it away.
writer.write("""
    endpoint_parameters = StaticEndpointParams(uri=config.endpoint_uri)
""");
writer.popState();

You can then get rid of the EndpointsGenerator entirely.

smithy-python doesn't use sections or interceptors much currently, but it should.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For an implementation of an approach using CodeSections and CodeInterceptor see: #407


writer.write("""
# Step 7f: Invoke endpoint_resolver.resolve_endpoint
if config.endpoint_uri is None:
raise $1T(
"No endpoint_uri found on the operation config."
)
endpoint_resolver_parameters = StaticEndpointParams(uri=config.endpoint_uri)
logger.debug("Calling endpoint resolver with parameters: %s", endpoint_resolver_parameters)
endpoint = await config.endpoint_resolver.resolve_endpoint(
endpoint_resolver_parameters
)
logger.debug("Calling endpoint resolver with parameters: %s", endpoint_parameters)
endpoint = await config.endpoint_resolver.resolve_endpoint(endpoint_parameters)
logger.debug("Endpoint resolver result: %s", endpoint)
if not endpoint.uri.path:
path = ""
Expand All @@ -474,7 +468,7 @@ async def _handle_attempt(
)
context._transport_request.fields.extend(endpoint.headers)

""", errorSymbol);
""");
}
writer.popState();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@
import software.amazon.smithy.python.codegen.generators.ProtocolGenerator;
import software.amazon.smithy.python.codegen.generators.SchemaGenerator;
import software.amazon.smithy.python.codegen.generators.SetupGenerator;
import software.amazon.smithy.python.codegen.generators.StaticEndpointsGenerator;
import software.amazon.smithy.python.codegen.generators.StructureGenerator;
import software.amazon.smithy.python.codegen.generators.UnionGenerator;
import software.amazon.smithy.python.codegen.integrations.EndpointsGenerator;
import software.amazon.smithy.python.codegen.integrations.PythonIntegration;
import software.amazon.smithy.python.codegen.writer.PythonDelegator;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
Expand Down Expand Up @@ -81,9 +83,20 @@ public GenerationContext createContext(CreateContextDirective<PythonSettings, Py
directive.integrations(),
directive.model(),
directive.service()))
.endpointsGenerator(resolveEndpointsGenerator(directive))
.build();
}

private EndpointsGenerator resolveEndpointsGenerator(CreateContextDirective<PythonSettings, PythonIntegration> directive) {
for (PythonIntegration integration : directive.integrations()) {
Optional<EndpointsGenerator> generator = integration.getEndpointsGenerator(directive.model(), directive.service());
if (generator.isPresent()) {
return generator.get();
}
}
return new StaticEndpointsGenerator();
}

private ProtocolGenerator resolveProtocolGenerator(
Collection<PythonIntegration> integrations,
Model model,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import software.amazon.smithy.codegen.core.WriterDelegator;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.python.codegen.generators.ProtocolGenerator;
import software.amazon.smithy.python.codegen.integrations.EndpointsGenerator;
import software.amazon.smithy.python.codegen.integrations.PythonIntegration;
import software.amazon.smithy.python.codegen.writer.PythonDelegator;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
Expand All @@ -32,6 +33,7 @@ public final class GenerationContext
private final PythonDelegator delegator;
private final List<PythonIntegration> integrations;
private final ProtocolGenerator protocolGenerator;
private final EndpointsGenerator endpointsGenerator;

private GenerationContext(Builder builder) {
model = builder.model;
Expand All @@ -41,6 +43,7 @@ private GenerationContext(Builder builder) {
delegator = builder.delegator;
integrations = builder.integrations;
protocolGenerator = builder.protocolGenerator;
endpointsGenerator = builder.endpointsGenerator;
}

@Override
Expand Down Expand Up @@ -80,6 +83,13 @@ public ProtocolGenerator protocolGenerator() {
return protocolGenerator;
}

/**
* @return Returns the endpoints generator to use in code generation.
*/
public EndpointsGenerator endpointsGenerator () {
return endpointsGenerator;
}

/**
* Gets the application protocol for the service protocol.
*
Expand All @@ -105,7 +115,8 @@ public SmithyBuilder<GenerationContext> toBuilder() {
.settings(settings)
.symbolProvider(symbolProvider)
.fileManifest(fileManifest)
.writerDelegator(delegator);
.writerDelegator(delegator)
.endpointsGenerator(endpointsGenerator);
}

/**
Expand All @@ -119,6 +130,7 @@ public static final class Builder implements SmithyBuilder<GenerationContext> {
private PythonDelegator delegator;
private List<PythonIntegration> integrations;
private ProtocolGenerator protocolGenerator;
private EndpointsGenerator endpointsGenerator;

@Override
public GenerationContext build() {
Expand Down Expand Up @@ -187,5 +199,14 @@ public Builder protocolGenerator(ProtocolGenerator protocolGenerator) {
this.protocolGenerator = protocolGenerator;
return this;
}

/**
* @param endpointsGenerator The resolved endpoints generator to use.
* @return Returns the builder.
*/
public Builder endpointsGenerator(EndpointsGenerator endpointsGenerator) {
this.endpointsGenerator = endpointsGenerator;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public final class ConfigGenerator implements Runnable {

// This list contains any properties that must be added to any http-based
// service client, except for the http client itself.
private static final List<ConfigProperty> HTTP_PROPERTIES = Arrays.asList(
private static final List<ConfigProperty> HTTP_PROPERTIES = List.of(
ConfigProperty.builder()
.name("http_request_config")
.type(Symbol.builder()
Expand All @@ -77,42 +77,6 @@ public final class ConfigGenerator implements Runnable {
.addDependency(SmithyPythonDependency.SMITHY_HTTP)
.build())
.documentation("Configuration for individual HTTP requests.")
.build(),
ConfigProperty.builder()
.name("endpoint_resolver")
.type(Symbol.builder()
.name("EndpointResolver[Any]")
.addReference(Symbol.builder()
.name("Any")
.namespace("typing", ".")
.putProperty(SymbolProperties.STDLIB, true)
.build())
.addReference(Symbol.builder()
.name("EndpointResolver")
.namespace("smithy_http.aio.interfaces", ".")
.addDependency(SmithyPythonDependency.SMITHY_HTTP)
.build())
.build())
.documentation("""
The endpoint resolver used to resolve the final endpoint per-operation based on the \
configuration.""")
.nullable(false)
.initialize(writer -> {
writer.addImport("smithy_http.aio.endpoints", "StaticEndpointResolver");
writer.write("self.endpoint_resolver = endpoint_resolver or StaticEndpointResolver()");
})
.build(),
ConfigProperty.builder()
.name("endpoint_uri")
.type(Symbol.builder()
.name("str | URI")
.addReference(Symbol.builder()
.name("URI")
.namespace("smithy_core.interfaces", ".")
.addDependency(SmithyPythonDependency.SMITHY_CORE)
.build())
.build())
.documentation("A static URI to route requests to.")
.build());

private final PythonSettings settings;
Expand Down Expand Up @@ -154,6 +118,7 @@ private static List<ConfigProperty> getHttpProperties(GenerationContext context)
properties.add(clientBuilder.build());

properties.addAll(HTTP_PROPERTIES);
properties.addAll(context.endpointsGenerator().endpointsConfig(context));
return List.copyOf(properties);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package software.amazon.smithy.python.codegen.generators;

import java.util.List;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.python.codegen.ConfigProperty;
import software.amazon.smithy.python.codegen.GenerationContext;
import software.amazon.smithy.python.codegen.SmithyPythonDependency;
import software.amazon.smithy.python.codegen.SymbolProperties;
import software.amazon.smithy.python.codegen.integrations.EndpointsGenerator;
import software.amazon.smithy.python.codegen.writer.PythonWriter;

public class StaticEndpointsGenerator implements EndpointsGenerator {
@Override
public List<ConfigProperty> endpointsConfig(GenerationContext context) {
return List.of(ConfigProperty.builder()
.name("endpoint_resolver")
.type(Symbol.builder()
.name("EndpointResolver[Any]")
.addReference(Symbol.builder()
.name("Any")
.namespace("typing", ".")
.putProperty(SymbolProperties.STDLIB, true)
.build())
.addReference(Symbol.builder()
.name("EndpointResolver")
.namespace("smithy_http.aio.interfaces", ".")
.addDependency(SmithyPythonDependency.SMITHY_HTTP)
.build())
.build())
.documentation("""
The endpoint resolver used to resolve the final endpoint per-operation based on the \
configuration.""")
.nullable(false)
.initialize(writer -> {
writer.addImport("smithy_http.aio.endpoints", "StaticEndpointResolver");
writer.write("self.endpoint_resolver = endpoint_resolver or StaticEndpointResolver()");
})
.build(),
ConfigProperty.builder()
.name("endpoint_uri")
.type(Symbol.builder()
.name("str | URI")
.addReference(Symbol.builder()
.name("URI")
.namespace("smithy_core.interfaces", ".")
.addDependency(SmithyPythonDependency.SMITHY_CORE)
.build())
.build())
.documentation("A static URI to route requests to.")
.build());
}

@Override
public void renderEndpointParameterConstruction(GenerationContext context, PythonWriter writer) {
writer.addDependency(SmithyPythonDependency.SMITHY_HTTP);
writer.addImport("smithy_http.endpoints", "StaticEndpointParams");
writer.write("""
endpoint_parameters = StaticEndpointParams(
uri=config.endpoint_uri
)
""");
}
}
Loading
Loading