diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsPythonDependency.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsPythonDependency.java
new file mode 100644
index 000000000..d6d3563db
--- /dev/null
+++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsPythonDependency.java
@@ -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.
+ *
+ *
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);
+}
\ No newline at end of file
diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsRegionalEndpointsGenerator.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsRegionalEndpointsGenerator.java
new file mode 100644
index 000000000..47a49fa09
--- /dev/null
+++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsRegionalEndpointsGenerator.java
@@ -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 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
+ )
+ """);
+ }
+}
diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsRegionalEndpointsIntegration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsRegionalEndpointsIntegration.java
new file mode 100644
index 000000000..298f88acc
--- /dev/null
+++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsRegionalEndpointsIntegration.java
@@ -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 getEndpointsGenerator(Model model, ServiceShape service) {
+ return Optional.of(new AwsRegionalEndpointsGenerator());
+ }
+}
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 5155ed74a..b4ae333c5 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
@@ -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
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 5aa42d257..b2df848c5 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
@@ -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);
+
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 = ""
@@ -474,7 +468,7 @@ async def _handle_attempt(
)
context._transport_request.fields.extend(endpoint.headers)
- """, errorSymbol);
+ """);
}
writer.popState();
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java
index a52cb082e..98270804e 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java
@@ -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;
@@ -81,9 +83,20 @@ public GenerationContext createContext(CreateContextDirective directive) {
+ for (PythonIntegration integration : directive.integrations()) {
+ Optional generator = integration.getEndpointsGenerator(directive.model(), directive.service());
+ if (generator.isPresent()) {
+ return generator.get();
+ }
+ }
+ return new StaticEndpointsGenerator();
+ }
+
private ProtocolGenerator resolveProtocolGenerator(
Collection integrations,
Model model,
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/GenerationContext.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/GenerationContext.java
index 2f112e388..18fd2cd5e 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/GenerationContext.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/GenerationContext.java
@@ -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;
@@ -32,6 +33,7 @@ public final class GenerationContext
private final PythonDelegator delegator;
private final List integrations;
private final ProtocolGenerator protocolGenerator;
+ private final EndpointsGenerator endpointsGenerator;
private GenerationContext(Builder builder) {
model = builder.model;
@@ -41,6 +43,7 @@ private GenerationContext(Builder builder) {
delegator = builder.delegator;
integrations = builder.integrations;
protocolGenerator = builder.protocolGenerator;
+ endpointsGenerator = builder.endpointsGenerator;
}
@Override
@@ -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.
*
@@ -105,7 +115,8 @@ public SmithyBuilder toBuilder() {
.settings(settings)
.symbolProvider(symbolProvider)
.fileManifest(fileManifest)
- .writerDelegator(delegator);
+ .writerDelegator(delegator)
+ .endpointsGenerator(endpointsGenerator);
}
/**
@@ -119,6 +130,7 @@ public static final class Builder implements SmithyBuilder {
private PythonDelegator delegator;
private List integrations;
private ProtocolGenerator protocolGenerator;
+ private EndpointsGenerator endpointsGenerator;
@Override
public GenerationContext build() {
@@ -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;
+ }
}
}
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 fb01926d8..0a435bcd1 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
@@ -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 HTTP_PROPERTIES = Arrays.asList(
+ private static final List HTTP_PROPERTIES = List.of(
ConfigProperty.builder()
.name("http_request_config")
.type(Symbol.builder()
@@ -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;
@@ -154,6 +118,7 @@ private static List getHttpProperties(GenerationContext context)
properties.add(clientBuilder.build());
properties.addAll(HTTP_PROPERTIES);
+ properties.addAll(context.endpointsGenerator().endpointsConfig(context));
return List.copyOf(properties);
}
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/StaticEndpointsGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/StaticEndpointsGenerator.java
new file mode 100644
index 000000000..a4ef6c757
--- /dev/null
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/StaticEndpointsGenerator.java
@@ -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 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
+ )
+ """);
+ }
+}
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/EndpointsGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/EndpointsGenerator.java
new file mode 100644
index 000000000..dfb349bd9
--- /dev/null
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/EndpointsGenerator.java
@@ -0,0 +1,38 @@
+package software.amazon.smithy.python.codegen.integrations;
+
+import java.util.List;
+import software.amazon.smithy.python.codegen.ConfigProperty;
+import software.amazon.smithy.python.codegen.GenerationContext;
+import software.amazon.smithy.python.codegen.writer.PythonWriter;
+
+/**
+ * Interface for Endpoints Generators.
+ * Endpoints Generators are responsible for defining the client configuration
+ * required for resolving endpoints, this includes an appropriately typed
+ * `endpoint_resolver` and any other configuration properties such as `endpoint_uri`.
+ * Endpoint generators are also responsible for rendering the logic in the client's
+ * request execution stack that sets the destination on the transport request.
+ * Endpoints Generators are only applied to services that use an HTTP transport.
+ */
+public interface EndpointsGenerator {
+
+ /**
+ * Endpoints Generators are responsible for defining the client configuration
+ * required for resolving endpoints, this must include an appropriately typed
+ * `endpoint_resolver` and any other configuration properties such as `endpoint_uri`
+ *
+ * @param context generation context.
+ * @return list of client config required to resolve endpoints.
+ */
+ List endpointsConfig(GenerationContext context);
+
+ /**
+ * Render the logic in the client's request execution stack that constructs
+ * `endpoint_parameters`. Implementations must add required dependencies and import statements
+ * and must ensure the `endpoint_parameters` variable is set.
+ *
+ * @param context generation context
+ * @param writer writer to write out logic to construct `endpoint_parameters`.
+ */
+ void renderEndpointParameterConstruction(GenerationContext context, PythonWriter writer);
+}
diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/PythonIntegration.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/PythonIntegration.java
index a23cf237a..40b33e252 100644
--- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/PythonIntegration.java
+++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/integrations/PythonIntegration.java
@@ -6,7 +6,10 @@
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import software.amazon.smithy.codegen.core.SmithyIntegration;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.python.codegen.GenerationContext;
import software.amazon.smithy.python.codegen.PythonSettings;
import software.amazon.smithy.python.codegen.generators.ProtocolGenerator;
@@ -38,4 +41,13 @@ default List getProtocolGenerators() {
default List getClientPlugins() {
return Collections.emptyList();
}
+
+ /**
+ * Get an EndpointsGenerator that will be used to generate endpoints related config and resolution logic.
+ *
+ * @return optional EndpointsGenerator.
+ */
+ default Optional getEndpointsGenerator(Model model, ServiceShape service) {
+ return Optional.empty();
+ }
}
diff --git a/packages/smithy-aws-core/src/smithy_aws_core/endpoints/__init__.py b/packages/smithy-aws-core/src/smithy_aws_core/endpoints/__init__.py
new file mode 100644
index 000000000..33cbe867a
--- /dev/null
+++ b/packages/smithy-aws-core/src/smithy_aws_core/endpoints/__init__.py
@@ -0,0 +1,2 @@
+# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+# SPDX-License-Identifier: Apache-2.0
diff --git a/packages/smithy-aws-core/src/smithy_aws_core/endpoints/standard_regional.py b/packages/smithy-aws-core/src/smithy_aws_core/endpoints/standard_regional.py
new file mode 100644
index 000000000..6064d6315
--- /dev/null
+++ b/packages/smithy-aws-core/src/smithy_aws_core/endpoints/standard_regional.py
@@ -0,0 +1,59 @@
+# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+# SPDX-License-Identifier: Apache-2.0
+from dataclasses import dataclass
+from urllib.parse import urlparse
+
+import smithy_core
+from smithy_core import URI
+from smithy_http.aio.interfaces import EndpointResolver
+from smithy_http.endpoints import Endpoint, EndpointResolutionError
+
+
+@dataclass(kw_only=True)
+class RegionalEndpointParameters:
+ """Endpoint parameters for services with standard regional endpoints."""
+
+ sdk_endpoint: str | smithy_core.interfaces.URI | None
+ region: str | None
+
+
+class StandardRegionalEndpointsResolver(EndpointResolver[RegionalEndpointParameters]):
+ """Resolves endpoints for services with standard regional endpoints."""
+
+ def __init__(self, endpoint_prefix: str):
+ self._endpoint_prefix = endpoint_prefix
+
+ async def resolve_endpoint(self, params: RegionalEndpointParameters) -> Endpoint:
+ if params.sdk_endpoint is not None:
+ # If it's not a string, it's already a parsed URI so just pass it along.
+ if not isinstance(params.sdk_endpoint, str):
+ return Endpoint(uri=params.sdk_endpoint)
+
+ parsed = urlparse(params.sdk_endpoint)
+
+ # This will end up getting wrapped in the client.
+ if parsed.hostname is None:
+ raise EndpointResolutionError(
+ f"Unable to parse hostname from provided URI: {params.sdk_endpoint}"
+ )
+
+ return Endpoint(
+ uri=URI(
+ host=parsed.hostname,
+ path=parsed.path,
+ scheme=parsed.scheme,
+ query=parsed.query,
+ port=parsed.port,
+ )
+ )
+
+ if params.region is not None:
+ # TODO: use dns suffix determined from partition metadata
+ dns_suffix = "amazonaws.com"
+ hostname = f"{self._endpoint_prefix}.{params.region}.{dns_suffix}"
+
+ return Endpoint(uri=URI(host=hostname))
+
+ raise EndpointResolutionError(
+ "Unable to resolve endpoint - either endpoint_url or region are required."
+ )
diff --git a/packages/smithy-aws-core/tests/unit/endpoints/test_standard_regional.py b/packages/smithy-aws-core/tests/unit/endpoints/test_standard_regional.py
new file mode 100644
index 000000000..332d8969d
--- /dev/null
+++ b/packages/smithy-aws-core/tests/unit/endpoints/test_standard_regional.py
@@ -0,0 +1,73 @@
+# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+# SPDX-License-Identifier: Apache-2.0
+from smithy_aws_core.endpoints.standard_regional import (
+ StandardRegionalEndpointsResolver,
+ RegionalEndpointParameters,
+)
+
+from smithy_core import URI
+from smithy_http.endpoints import EndpointResolutionError
+
+import pytest
+
+
+async def test_resolve_endpoint_with_valid_sdk_endpoint_string():
+ resolver = StandardRegionalEndpointsResolver(endpoint_prefix="service")
+ params = RegionalEndpointParameters(
+ sdk_endpoint="https://example.com/path?query=123", region=None
+ )
+
+ endpoint = await resolver.resolve_endpoint(params)
+
+ assert endpoint.uri.host == "example.com"
+ assert endpoint.uri.path == "/path"
+ assert endpoint.uri.scheme == "https"
+ assert endpoint.uri.query == "query=123"
+
+
+async def test_resolve_endpoint_with_sdk_endpoint_uri():
+ resolver = StandardRegionalEndpointsResolver(endpoint_prefix="service")
+ parsed_uri = URI(
+ host="example.com", path="/path", scheme="https", query="query=123", port=443
+ )
+ params = RegionalEndpointParameters(sdk_endpoint=parsed_uri, region=None)
+
+ endpoint = await resolver.resolve_endpoint(params)
+
+ assert endpoint.uri == parsed_uri
+
+
+async def test_resolve_endpoint_with_invalid_sdk_endpoint():
+ resolver = StandardRegionalEndpointsResolver(endpoint_prefix="service")
+ params = RegionalEndpointParameters(sdk_endpoint="invalid-uri", region=None)
+
+ with pytest.raises(EndpointResolutionError):
+ await resolver.resolve_endpoint(params)
+
+
+async def test_resolve_endpoint_with_region():
+ resolver = StandardRegionalEndpointsResolver(endpoint_prefix="service")
+ params = RegionalEndpointParameters(sdk_endpoint=None, region="us-west-2")
+
+ endpoint = await resolver.resolve_endpoint(params)
+
+ assert endpoint.uri.host == "service.us-west-2.amazonaws.com"
+
+
+async def test_resolve_endpoint_with_no_sdk_endpoint_or_region():
+ resolver = StandardRegionalEndpointsResolver(endpoint_prefix="service")
+ params = RegionalEndpointParameters(sdk_endpoint=None, region=None)
+
+ with pytest.raises(EndpointResolutionError):
+ await resolver.resolve_endpoint(params)
+
+
+async def test_resolve_endpoint_with_sdk_endpoint_and_region():
+ resolver = StandardRegionalEndpointsResolver(endpoint_prefix="service")
+ params = RegionalEndpointParameters(
+ sdk_endpoint="https://example.com", region="us-west-2"
+ )
+
+ endpoint = await resolver.resolve_endpoint(params)
+
+ assert endpoint.uri.host == "example.com"
diff --git a/packages/smithy-http/src/smithy_http/aio/endpoints.py b/packages/smithy-http/src/smithy_http/aio/endpoints.py
index f0c703240..9ce4ae34d 100644
--- a/packages/smithy-http/src/smithy_http/aio/endpoints.py
+++ b/packages/smithy-http/src/smithy_http/aio/endpoints.py
@@ -5,7 +5,7 @@
from smithy_core import URI
from .. import interfaces as http_interfaces
-from ..endpoints import Endpoint, StaticEndpointParams
+from ..endpoints import Endpoint, StaticEndpointParams, EndpointResolutionError
from . import interfaces as http_aio_interfaces
@@ -17,6 +17,11 @@ class StaticEndpointResolver(
async def resolve_endpoint(
self, params: StaticEndpointParams
) -> http_interfaces.Endpoint:
+ if params.uri is None:
+ raise EndpointResolutionError(
+ "Unable to resolve endpoint: endpoint_uri is required"
+ )
+
# If it's not a string, it's already a parsed URI so just pass it along.
if not isinstance(params.uri, str):
return Endpoint(uri=params.uri)
@@ -27,7 +32,7 @@ async def resolve_endpoint(
# This will end up getting wrapped in the client.
if parsed.hostname is None:
- raise ValueError(
+ raise EndpointResolutionError(
f"Unable to parse hostname from provided URI: {params.uri}"
)
diff --git a/packages/smithy-http/src/smithy_http/endpoints.py b/packages/smithy-http/src/smithy_http/endpoints.py
index 3e3f99dc8..a1337734b 100644
--- a/packages/smithy-http/src/smithy_http/endpoints.py
+++ b/packages/smithy-http/src/smithy_http/endpoints.py
@@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
from dataclasses import dataclass, field
+from smithy_core import SmithyException
from smithy_core.interfaces import URI
from . import Fields, interfaces
@@ -13,6 +14,10 @@ class Endpoint(interfaces.Endpoint):
headers: interfaces.Fields = field(default_factory=Fields)
+class EndpointResolutionError(SmithyException):
+ """Exception type for all exceptions raised by endpoint resolution."""
+
+
@dataclass
class StaticEndpointParams:
"""Static endpoint params.
@@ -20,4 +25,4 @@ class StaticEndpointParams:
:param uri: A static URI to route requests to.
"""
- uri: str | URI
+ uri: str | URI | None