Skip to content

Commit dc3a7cc

Browse files
committed
Add implementation of AWS StandardRegionalEndpoints
1 parent 3e8fb33 commit dc3a7cc

File tree

9 files changed

+227
-2
lines changed

9 files changed

+227
-2
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.python.aws.codegen;
6+
7+
import software.amazon.smithy.python.codegen.PythonDependency;
8+
import software.amazon.smithy.utils.SmithyUnstableApi;
9+
10+
/**
11+
* AWS Dependencies used in the smithy python generator.
12+
*/
13+
@SmithyUnstableApi
14+
public class AwsPythonDependency {
15+
/**
16+
* The core aws smithy runtime python package.
17+
*
18+
* <p>While in development this will use the develop branch.
19+
*/
20+
public static final PythonDependency SMITHY_AWS_CORE = new PythonDependency(
21+
"smithy_aws_core",
22+
// You'll need to locally install this before we publish
23+
"==0.0.1",
24+
PythonDependency.Type.DEPENDENCY,
25+
false);
26+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package software.amazon.smithy.python.aws.codegen;
2+
3+
import java.util.List;
4+
import software.amazon.smithy.aws.traits.ServiceTrait;
5+
import software.amazon.smithy.codegen.core.Symbol;
6+
import software.amazon.smithy.python.codegen.CodegenUtils;
7+
import software.amazon.smithy.python.codegen.ConfigProperty;
8+
import software.amazon.smithy.python.codegen.GenerationContext;
9+
import software.amazon.smithy.python.codegen.SmithyPythonDependency;
10+
import software.amazon.smithy.python.codegen.integrations.EndpointsGenerator;
11+
import software.amazon.smithy.python.codegen.writer.PythonWriter;
12+
13+
/**
14+
* Generates endpoint config and resolution logic for standard regional endpoints.
15+
*/
16+
public class AwsRegionalEndpointsGenerator implements EndpointsGenerator {
17+
@Override
18+
public List<ConfigProperty> endpointsConfig(GenerationContext context) {
19+
return List.of(ConfigProperty.builder()
20+
.name("endpoint_resolver")
21+
.type(Symbol.builder()
22+
.name("EndpointResolver[RegionalEndpointParameters]")
23+
.addReference(Symbol.builder()
24+
.name("RegionalEndpointParameters")
25+
.namespace(AwsPythonDependency.SMITHY_AWS_CORE.packageName() + ".endpoints.standard_regional", ".")
26+
.addDependency(AwsPythonDependency.SMITHY_AWS_CORE)
27+
.build())
28+
.addReference(Symbol.builder()
29+
.name("EndpointResolver")
30+
.namespace("smithy_http.aio.interfaces", ".")
31+
.addDependency(SmithyPythonDependency.SMITHY_HTTP)
32+
.build())
33+
.build())
34+
.documentation("""
35+
The endpoint resolver used to resolve the final endpoint per-operation based on the \
36+
configuration.""")
37+
.nullable(false)
38+
.initialize(writer -> {
39+
writer.addImport("smithy_aws_core.endpoints.standard_regional", "StandardRegionalEndpointsResolver");
40+
String endpointPrefix = context.settings().service(context.model())
41+
.getTrait(ServiceTrait.class)
42+
.map(ServiceTrait::getEndpointPrefix)
43+
.orElse(context.settings().service().getName());
44+
45+
writer.write(
46+
"self.endpoint_resolver = endpoint_resolver or StandardRegionalEndpointsResolver(endpoint_prefix='$L')",
47+
endpointPrefix);
48+
})
49+
.build(),
50+
ConfigProperty.builder()
51+
.name("endpoint_uri")
52+
.type(Symbol.builder()
53+
.name("str | URI")
54+
.addReference(Symbol.builder()
55+
.name("URI")
56+
.namespace("smithy_core.interfaces", ".")
57+
.addDependency(SmithyPythonDependency.SMITHY_CORE)
58+
.build())
59+
.build())
60+
.documentation("A static URI to route requests to.")
61+
.build(),
62+
ConfigProperty.builder()
63+
.name("region")
64+
.type(Symbol.builder().name("str").build())
65+
.documentation(" The AWS region to connect to. The configured region is used to "
66+
+ "determine the service endpoint.")
67+
.build());
68+
}
69+
70+
@Override
71+
public void generateEndpoints(GenerationContext context, PythonWriter writer) {
72+
var errorSymbol = CodegenUtils.getServiceError(context.settings());
73+
74+
writer.addDependency(AwsPythonDependency.SMITHY_AWS_CORE);
75+
writer.addImport("smithy_aws_core.endpoints.standard_regional", "RegionalEndpointParameters");
76+
writer.addImport("smithy_core", "URI");
77+
writer.write("""
78+
endpoint = await config.endpoint_resolver.resolve_endpoint(
79+
RegionalEndpointParameters(sdk_endpoint=config.endpoint_uri,region=config.region)
80+
)
81+
if not endpoint.uri.path:
82+
path = ""
83+
elif endpoint.uri.path.endswith("/"):
84+
path = endpoint.uri.path[:-1]
85+
else:
86+
path = endpoint.uri.path
87+
if context.transport_request.destination.path:
88+
path += context.transport_request.destination.path
89+
context._transport_request.destination = URI(
90+
scheme=endpoint.uri.scheme,
91+
host=context.transport_request.destination.host + endpoint.uri.host,
92+
path=path,
93+
port=endpoint.uri.port,
94+
query=context.transport_request.destination.query,
95+
)
96+
context._transport_request.fields.extend(endpoint.headers)
97+
98+
""");
99+
}
100+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package software.amazon.smithy.python.aws.codegen;
2+
3+
import java.util.Optional;
4+
import software.amazon.smithy.model.Model;
5+
import software.amazon.smithy.model.shapes.ServiceShape;
6+
import software.amazon.smithy.python.codegen.integrations.EndpointsGenerator;
7+
import software.amazon.smithy.python.codegen.integrations.PythonIntegration;
8+
9+
public class AwsRegionalEndpointsIntegration implements PythonIntegration {
10+
@Override
11+
public Optional<EndpointsGenerator> getEndpointsGenerator(Model model, ServiceShape service) {
12+
return Optional.of(new AwsRegionalEndpointsGenerator());
13+
}
14+
}

codegen/aws/core/src/main/resources/META-INF/services/software.amazon.smithy.python.codegen.integrations.PythonIntegration

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55

66
software.amazon.smithy.python.aws.codegen.AwsAuthIntegration
77
software.amazon.smithy.python.aws.codegen.AwsProtocolsIntegration
8+
software.amazon.smithy.python.aws.codegen.AwsRegionalEndpointsIntegration

codegen/core/src/main/java/software/amazon/smithy/python/codegen/ClientGenerator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ async def _handle_attempt(
432432

433433
writer.pushState(new ResolveEndpointSection());
434434
if (context.applicationProtocol().isHttpProtocol()) {
435+
writer.write("# Step 7f: Invoke endpoint_resolver.resolve_endpoint");
435436
context.endpointsGenerator().generateEndpoints(context, writer);
436437
}
437438
writer.popState();

codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/StaticEndpointsGenerator.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ public void generateEndpoints(GenerationContext context, PythonWriter writer) {
6060
writer.addImport("smithy_http.endpoints", "StaticEndpointParams");
6161
writer.addImport("smithy_core", "URI");
6262
writer.write("""
63-
# Step 7f: Invoke endpoint_resolver.resolve_endpoint
6463
if config.endpoint_uri is None:
6564
raise $1T(
6665
"No endpoint_uri found on the operation config."

codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/EndpointsGenerator.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,32 @@
66
import software.amazon.smithy.python.codegen.writer.PythonWriter;
77

88
/**
9-
*
9+
* Interface for Endpoints Generators.
10+
* Endpoints Generators are responsible for defining the client configuration
11+
* required for resolving endpoints, this includes an appropriately typed
12+
* `endpoint_resolver` and any other configuration properties such as `endpoint_uri`.
13+
* Endpoint generators are also responsible for rendering the logic in the client's
14+
* request execution stack that sets the destination on the transport request.
15+
* Endpoints Generators are only applied to services that use an HTTP transport.
1016
*/
1117
public interface EndpointsGenerator {
1218

19+
/**
20+
* Endpoints Generators are responsible for defining the client configuration
21+
* required for resolving endpoints, this includes an appropriately typed
22+
* `endpoint_resolver` and any other configuration properties such as `endpoint_uri`
23+
*
24+
* @param context generation context.
25+
* @return list of client config required to resolve endpoints.
26+
*/
1327
List<ConfigProperty> endpointsConfig(GenerationContext context);
1428

29+
/**
30+
* Render the logic in the client's request execution stack that sets the
31+
* destination on the transport request using the provided writer.
32+
*
33+
* @param context generation context
34+
* @param writer writer to generate endpoint resolution/setting logic with
35+
*/
1536
void generateEndpoints(GenerationContext context, PythonWriter writer);
1637
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
from dataclasses import dataclass
4+
from urllib.parse import urlparse
5+
6+
import smithy_core
7+
from smithy_core import URI
8+
from smithy_http.aio.interfaces import EndpointResolver
9+
from smithy_http.endpoints import Endpoint
10+
11+
12+
@dataclass(kw_only=True)
13+
class RegionalEndpointParameters:
14+
"""Endpoint parameters for services with standard regional endpoints."""
15+
16+
sdk_endpoint: str | smithy_core.interfaces.URI | None
17+
region: str | None
18+
19+
20+
class StandardRegionalEndpointsResolver(EndpointResolver[RegionalEndpointParameters]):
21+
"""Resolves endpoints for services with standard regional endpoints."""
22+
23+
def __init__(self, endpoint_prefix: str):
24+
self._endpoint_prefix = endpoint_prefix
25+
26+
async def resolve_endpoint(self, params: RegionalEndpointParameters) -> Endpoint:
27+
if params.sdk_endpoint is not None:
28+
# If it's not a string, it's already a parsed URI so just pass it along.
29+
if not isinstance(params.sdk_endpoint, str):
30+
return Endpoint(uri=params.sdk_endpoint)
31+
32+
# Does crt have implementations of these parsing methods? Using the standard
33+
# library is probably fine.
34+
parsed = urlparse(params.sdk_endpoint)
35+
36+
# This will end up getting wrapped in the client.
37+
if parsed.hostname is None:
38+
raise ValueError(
39+
f"Unable to parse hostname from provided URI: {params.sdk_endpoint}"
40+
)
41+
42+
return Endpoint(
43+
uri=URI(
44+
host=parsed.hostname,
45+
path=parsed.path,
46+
scheme=parsed.scheme,
47+
query=parsed.query,
48+
port=parsed.port,
49+
)
50+
)
51+
52+
if params.region is not None:
53+
# TODO: use dns suffix determined from partition metadata
54+
dns_suffix = "amazonaws.com"
55+
hostname = f"{self._endpoint_prefix}.{params.region}.{dns_suffix}"
56+
57+
return Endpoint(uri=URI(host=hostname))
58+
59+
raise ValueError(
60+
"Unable to resolve endpoint - either endpoint_url or region are required."
61+
)

0 commit comments

Comments
 (0)