diff --git a/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsStandardRegionalEndpointsIntegration.java b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsStandardRegionalEndpointsIntegration.java new file mode 100644 index 000000000..7bda7991c --- /dev/null +++ b/codegen/aws/core/src/main/java/software/amazon/smithy/python/aws/codegen/AwsStandardRegionalEndpointsIntegration.java @@ -0,0 +1,127 @@ +/* + * 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.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.integrations.PythonIntegration; +import software.amazon.smithy.python.codegen.integrations.RuntimeClientPlugin; +import software.amazon.smithy.python.codegen.sections.EndpointParametersSection; +import software.amazon.smithy.python.codegen.sections.EndpointResolverSection; +import software.amazon.smithy.python.codegen.sections.InitDefaultEndpointResolverSection; +import software.amazon.smithy.python.codegen.writer.PythonWriter; +import software.amazon.smithy.utils.CodeInterceptor; +import software.amazon.smithy.utils.CodeSection; + +public class AwsStandardRegionalEndpointsIntegration implements PythonIntegration { + @Override + public List getClientPlugins(GenerationContext context) { + if (context.applicationProtocol().isHttpProtocol()) { + var region = 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(); + return List.of( + RuntimeClientPlugin.builder() + .addConfigProperty(region) + .build()); + } else { + return List.of(); + } + } + + @Override + public List> interceptors( + GenerationContext context + ) { + return List.of( + new RegionalEndpointParametersInterceptor(context), + new RegionalEndpointResolverInterceptor(context), + new RegionalInitEndpointResolverInterceptor(context)); + } + + private static final class RegionalEndpointParametersInterceptor + implements CodeInterceptor { + + private final GenerationContext context; + + public RegionalEndpointParametersInterceptor(GenerationContext context) { + this.context = context; + } + + @Override + public Class sectionType() { + return EndpointParametersSection.class; + } + + @Override + public void write(PythonWriter writer, String previousText, EndpointParametersSection section) { + var params = CodegenUtils.getEndpointParametersSymbol(context.settings()); + + writer.write("from smithy_aws_core.endpoints.standard_regional import RegionalEndpointParameters"); + writer.write("$L = RegionalEndpointParameters", params.getName()); + } + } + + private static final class RegionalEndpointResolverInterceptor + implements CodeInterceptor { + + private final GenerationContext context; + + public RegionalEndpointResolverInterceptor(GenerationContext context) { + this.context = context; + } + + @Override + public Class sectionType() { + return EndpointResolverSection.class; + } + + @Override + public void write(PythonWriter writer, String previousText, EndpointResolverSection section) { + var resolver = CodegenUtils.getEndpointResolverSymbol(context.settings()); + + writer.write("from smithy_aws_core.endpoints.standard_regional import StandardRegionalEndpointsResolver"); + writer.write("$L = StandardRegionalEndpointsResolver", resolver.getName()); + } + } + + private static final class RegionalInitEndpointResolverInterceptor + implements CodeInterceptor { + + private final GenerationContext context; + + public RegionalInitEndpointResolverInterceptor(GenerationContext context) { + this.context = context; + } + + @Override + public Class sectionType() { + return InitDefaultEndpointResolverSection.class; + } + + @Override + public void write(PythonWriter writer, String previousText, InitDefaultEndpointResolverSection section) { + var resolver = CodegenUtils.getEndpointResolverSymbol(context.settings()); + + 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 $T(endpoint_prefix=$S)", + resolver, + endpointPrefix); + + } + } +} 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 0375294ba..c9ee36be9 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 @@ -6,3 +6,4 @@ software.amazon.smithy.python.aws.codegen.AwsAuthIntegration software.amazon.smithy.python.aws.codegen.AwsProtocolsIntegration software.amazon.smithy.python.aws.codegen.AwsUserAgentIntegration +software.amazon.smithy.python.aws.codegen.AwsStandardRegionalEndpointsIntegration 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 2cdd09717..35f32b30b 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 @@ -443,16 +443,10 @@ 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 - if config.endpoint_uri is None: - raise $1T( - "No endpoint_uri found on the operation config." - ) - endpoint_resolver_parameters = StaticEndpointParams(uri=config.endpoint_uri) + endpoint_resolver_parameters = $1T.build(config=config) logger.debug("Calling endpoint resolver with parameters: %s", endpoint_resolver_parameters) endpoint = await config.endpoint_resolver.resolve_endpoint( endpoint_resolver_parameters @@ -475,7 +469,8 @@ async def _handle_attempt( ) context._transport_request.fields.extend(endpoint.headers) - """, errorSymbol); + """, + CodegenUtils.getEndpointParametersSymbol(context.settings())); } writer.popState(); diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/CodegenUtils.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/CodegenUtils.java index 39379536f..6660e1851 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/CodegenUtils.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/CodegenUtils.java @@ -140,10 +140,38 @@ public static Symbol getUnknownApiError(PythonSettings settings) { } /** - * Gets the symbol for the http auth parameters object. + * Gets the symbol for the endpoint parameters object. * * @param settings The client settings, used to account for module configuration. - * @return Returns the symbol for http auth params. + * @return Returns the symbol for endpoint parameters. + */ + public static Symbol getEndpointParametersSymbol(PythonSettings settings) { + return Symbol.builder() + .name("EndpointParameters") + .namespace(String.format("%s.endpoints", settings.moduleName()), ".") + .definitionFile(String.format("./%s/endpoints.py", settings.moduleName())) + .build(); + } + + /** + * Gets the symbol for the endpoint resolver object. + * + * @param settings The client settings, used to account for module configuration. + * @return Returns the symbol for endpoint resolver. + */ + public static Symbol getEndpointResolverSymbol(PythonSettings settings) { + return Symbol.builder() + .name("EndpointResolver") + .namespace(String.format("%s.endpoints", settings.moduleName()), ".") + .definitionFile(String.format("./%s/endpoints.py", settings.moduleName())) + .build(); + } + + /** + * Gets the symbol for the http auth params. + * + * @param settings The client settings, used to account for module configuration. + * @return Returns the http auth params symbol. */ public static Symbol getHttpAuthParamsSymbol(PythonSettings settings) { return Symbol.builder() diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonClientCodegen.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonClientCodegen.java index c1c737cc1..51f1d189d 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonClientCodegen.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonClientCodegen.java @@ -29,6 +29,7 @@ import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.python.codegen.generators.ConfigGenerator; +import software.amazon.smithy.python.codegen.generators.EndpointsGenerator; import software.amazon.smithy.python.codegen.generators.EnumGenerator; import software.amazon.smithy.python.codegen.generators.InitGenerator; import software.amazon.smithy.python.codegen.generators.IntEnumGenerator; @@ -110,6 +111,7 @@ public void customizeBeforeShapeGeneration(CustomizeDirective { - 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() @@ -124,7 +101,7 @@ public ConfigGenerator(PythonSettings settings, GenerationContext context) { } private static List getHttpProperties(GenerationContext context) { - var properties = new ArrayList(HTTP_PROPERTIES.size() + 1); + var properties = new ArrayList(HTTP_PROPERTIES.size() + 2); var clientBuilder = ConfigProperty.builder() .name("http_client") .type(Symbol.builder() @@ -155,6 +132,25 @@ private static List getHttpProperties(GenerationContext context) } properties.add(clientBuilder.build()); + var endpointResolver = CodegenUtils.getEndpointResolverSymbol(context.settings()); + properties.add(ConfigProperty.builder() + .name("endpoint_resolver") + .type(Symbol.builder() + .name("_EndpointResolver[EndpointParameters]") + .addReference(CodegenUtils.getEndpointParametersSymbol(context.settings())) + .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.interfaces", "EndpointResolver", "_EndpointResolver"); + writer.pushState(new InitDefaultEndpointResolverSection()); + writer.write("self.endpoint_resolver = endpoint_resolver or $T()", endpointResolver); + writer.popState(); + }) + .build()); + properties.addAll(HTTP_PROPERTIES); return List.copyOf(properties); } diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/EndpointsGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/EndpointsGenerator.java new file mode 100644 index 000000000..a913ec800 --- /dev/null +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/EndpointsGenerator.java @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.python.codegen.generators; + +import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.python.codegen.CodegenUtils; +import software.amazon.smithy.python.codegen.GenerationContext; +import software.amazon.smithy.python.codegen.PythonSettings; +import software.amazon.smithy.python.codegen.SmithyPythonDependency; +import software.amazon.smithy.python.codegen.sections.EndpointParametersSection; +import software.amazon.smithy.python.codegen.sections.EndpointResolverSection; +import software.amazon.smithy.python.codegen.writer.PythonWriter; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * This class is responsible for generating the endpoint resolver and its parameters. + */ +@SmithyInternalApi +public final class EndpointsGenerator implements Runnable { + + private final PythonSettings settings; + private final GenerationContext context; + + public EndpointsGenerator(GenerationContext context, PythonSettings settings) { + this.context = context; + this.settings = settings; + } + + @Override + public void run() { + var params = CodegenUtils.getEndpointParametersSymbol(settings); + context.writerDelegator().useFileWriter(params.getDefinitionFile(), params.getNamespace(), writer -> { + generateEndpointParameters(writer, params); + }); + + var resolver = CodegenUtils.getEndpointResolverSymbol(settings); + context.writerDelegator().useFileWriter(resolver.getDefinitionFile(), resolver.getNamespace(), writer -> { + generateEndpointResolver(writer, resolver); + }); + } + + private void generateEndpointParameters(PythonWriter writer, Symbol params) { + writer.pushState(new EndpointParametersSection()); + writer.addDependency(SmithyPythonDependency.SMITHY_HTTP); + writer.write("from smithy_http.endpoints import StaticEndpointParams"); + writer.write("$L = StaticEndpointParams", params.getName()); + writer.popState(); + } + + private void generateEndpointResolver(PythonWriter writer, Symbol resolver) { + writer.pushState(new EndpointResolverSection()); + writer.addDependency(SmithyPythonDependency.SMITHY_HTTP); + writer.write("from smithy_http.aio.endpoints import StaticEndpointResolver"); + writer.write("$L = StaticEndpointResolver", resolver.getName()); + writer.popState(); + } +} diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/EndpointParametersSection.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/EndpointParametersSection.java new file mode 100644 index 000000000..b70a0b111 --- /dev/null +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/EndpointParametersSection.java @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.python.codegen.sections; + +import software.amazon.smithy.utils.CodeSection; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * A section that controls generating the EndpointParameters class. + */ +@SmithyInternalApi +public record EndpointParametersSection() implements CodeSection {} diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/EndpointResolverSection.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/EndpointResolverSection.java new file mode 100644 index 000000000..77b8472ac --- /dev/null +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/EndpointResolverSection.java @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.python.codegen.sections; + +import software.amazon.smithy.utils.CodeSection; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * A section that controls generating the EndpointResolver class. + */ +@SmithyInternalApi +public record EndpointResolverSection() implements CodeSection {} diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/InitDefaultEndpointResolverSection.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/InitDefaultEndpointResolverSection.java new file mode 100644 index 000000000..5614c7212 --- /dev/null +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/sections/InitDefaultEndpointResolverSection.java @@ -0,0 +1,14 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package software.amazon.smithy.python.codegen.sections; + +import software.amazon.smithy.utils.CodeSection; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * A section that controls generating the EndpointParameters class. + */ +@SmithyInternalApi +public record InitDefaultEndpointResolverSection() implements CodeSection {} 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..c6b37df2d --- /dev/null +++ b/packages/smithy-aws-core/src/smithy_aws_core/endpoints/standard_regional.py @@ -0,0 +1,73 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +from dataclasses import dataclass +from typing import Protocol, Self +from urllib.parse import urlparse + +import smithy_core +from smithy_core import URI +from smithy_http.aio.interfaces import ( + EndpointResolver, + EndpointParameters, +) +from smithy_http.endpoints import Endpoint +from smithy_http.exceptions import EndpointResolutionError + + +class _RegionUriConfig(Protocol): + endpoint_uri: str | smithy_core.interfaces.URI | None + region: str | None + + +@dataclass(kw_only=True) +class RegionalEndpointParameters(EndpointParameters[_RegionUriConfig]): + """Endpoint parameters for services with standard regional endpoints.""" + + sdk_endpoint: str | smithy_core.interfaces.URI | None + region: str | None + + @classmethod + def build(cls, config: _RegionUriConfig) -> Self: + return cls(sdk_endpoint=config.endpoint_uri, region=config.region) + + +class StandardRegionalEndpointsResolver(EndpointResolver[RegionalEndpointParameters]): + """Resolves endpoints for services with standard regional endpoints.""" + + def __init__(self, endpoint_prefix: str = "bedrock-runtime"): + 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..0ae20ac3b --- /dev/null +++ b/packages/smithy-aws-core/tests/unit/endpoints/test_standard_regional.py @@ -0,0 +1,74 @@ +# 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 + +import pytest + +from smithy_http.exceptions import EndpointResolutionError + + +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..47ebbbae2 100644 --- a/packages/smithy-http/src/smithy_http/aio/endpoints.py +++ b/packages/smithy-http/src/smithy_http/aio/endpoints.py @@ -3,20 +3,24 @@ from urllib.parse import urlparse from smithy_core import URI +from .interfaces import EndpointResolver from .. import interfaces as http_interfaces from ..endpoints import Endpoint, StaticEndpointParams -from . import interfaces as http_aio_interfaces +from ..exceptions import EndpointResolutionError -class StaticEndpointResolver( - http_aio_interfaces.EndpointResolver[StaticEndpointParams] -): +class StaticEndpointResolver(EndpointResolver[StaticEndpointParams]): """A basic endpoint resolver that forwards a static URI.""" 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 +31,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/aio/interfaces/__init__.py b/packages/smithy-http/src/smithy_http/aio/interfaces/__init__.py index fd112ef38..699fcf373 100644 --- a/packages/smithy-http/src/smithy_http/aio/interfaces/__init__.py +++ b/packages/smithy-http/src/smithy_http/aio/interfaces/__init__.py @@ -1,6 +1,6 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -from typing import Protocol, TypeVar +from typing import Protocol, Self from smithy_core.aio.interfaces import Request, Response from smithy_core.aio.utils import read_streaming_blob, read_streaming_blob_async @@ -12,17 +12,17 @@ HTTPRequestConfiguration, ) -# EndpointParams are defined in the generated client, so we use a TypeVar here. -# More specific EndpointParams implementations are subtypes of less specific ones. But -# consumers of less specific EndpointParams implementations are subtypes of consumers -# of more specific ones. -EndpointParams = TypeVar("EndpointParams", contravariant=True) + +class EndpointParameters[C](Protocol): + @classmethod + def build(cls, config: C) -> Self: + raise NotImplementedError() -class EndpointResolver(Protocol[EndpointParams]): +class EndpointResolver[T](Protocol): """Resolves an operation's endpoint based given parameters.""" - async def resolve_endpoint(self, params: EndpointParams) -> Endpoint: + async def resolve_endpoint(self, params: T) -> Endpoint: raise NotImplementedError() diff --git a/packages/smithy-http/src/smithy_http/endpoints.py b/packages/smithy-http/src/smithy_http/endpoints.py index 3e3f99dc8..8f2945744 100644 --- a/packages/smithy-http/src/smithy_http/endpoints.py +++ b/packages/smithy-http/src/smithy_http/endpoints.py @@ -1,10 +1,12 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 from dataclasses import dataclass, field +from typing import Protocol, Self from smithy_core.interfaces import URI from . import Fields, interfaces +from .aio.interfaces import EndpointParameters @dataclass @@ -13,11 +15,19 @@ class Endpoint(interfaces.Endpoint): headers: interfaces.Fields = field(default_factory=Fields) +class _UriConfig(Protocol): + endpoint_uri: str | URI | None + + @dataclass -class StaticEndpointParams: +class StaticEndpointParams(EndpointParameters[_UriConfig]): """Static endpoint params. :param uri: A static URI to route requests to. """ - uri: str | URI + uri: str | URI | None + + @classmethod + def build(cls, config: _UriConfig) -> Self: + return cls(uri=config.endpoint_uri) diff --git a/packages/smithy-http/src/smithy_http/exceptions.py b/packages/smithy-http/src/smithy_http/exceptions.py index 08ced3c39..2b27c2942 100644 --- a/packages/smithy-http/src/smithy_http/exceptions.py +++ b/packages/smithy-http/src/smithy_http/exceptions.py @@ -5,3 +5,7 @@ class SmithyHTTPException(SmithyException): """Base exception type for all exceptions raised in HTTP clients.""" + + +class EndpointResolutionError(SmithyException): + """Exception type for all exceptions raised by endpoint resolution."""