Skip to content

Implement endpoint bdd resolution - #885

Open
arandito wants to merge 1 commit into
developfrom
endpoints-bdd
Open

arandito wants to merge 1 commit into
developfrom
endpoints-bdd

Conversation

@arandito

@arandito arandito commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Description

This PR replaces the regional endpoint resolver that generated AWS clients us today with endpoint resolution from each service's endpoint rules. See designs/endpoints.md for the full design.

  • Code generation lowers the smithy.rules#endpointBdd trait into a BDD constant of plain data in the _private/endpoints.py, and configs now default endpoint_resolver to AsyncBddEndpointResolver for async clients.
  • smithy-core adds one rules engine and standard library that evaluate this data for every Smithy-based service. Endpoint parameters are bound from the config, the operation input, and the contextParam, staticContextParams, and operationContextParams traits.
  • smithy-aws-core adds AWS_RULES_EXTENSION with the aws.* rules functions, the AWS built-ins, and partition data, and removes AsyncStandardRegionalEndpointsResolver.
  • Generated configs get use_fips_endpoint, use_dualstack_endpoint, account_id_endpoint_mode, and a field for each clientContextParams entry, when the service's rules declare them.
  • The request pipeline now resolves the auth scheme and identity before the endpoint, so the rules engine can read the account ID.
  • Code generation turns each model's endpointTests cases into tests/test_endpoints.py in the client package. Every case is resolved with its parameters through the client's resolver and cases with operation inputs run through the client as well. This ensures parameter binding works correctly.

Sync/Async Split

  • BddEndpointResolver defines the resolution logic as sync code and AsyncBddEndpointResolver wraps it for async clients. The async endpoint resolver protocol follows the SRA's recommendation that endpoint resolution be async, so it doesn't assume how an endpoint is resolved and keeps our interfaces flexible. For example, users may need to define a custom endpoint resolver that makes network calls. There are also AWS services (Timestream) that define a DescribeEndpoints API for the endpoint discovery feature.

Note

Follow-up PRs will stack on top of this one for:

  1. Applying the endpoint's auth schemes when signing.
  2. Host prefixes and host labels from the endpoint trait.
  3. Resolving the endpoint built-in config fields from environment variables and profile keys.

Testing

  • Added unit tests for the BDD lowering, the rules engine and standard library, parameter binding, the resolver, the AWS rules functions, and the endpoint test checks.
  • Ran make test-unit - 3376 passed, 7 skipped.
  • Generated the 25 existing aws-sdk-python clients plus S3 and EC2 and ran their endpoint tests: 2,257 passed, 38 skipped, 5 xfailed. The skipped client cases set AWS::S3::UseGlobalEndpoint or AWS::STS::UseGlobalEndpoint, which have no config field (legacy config options we will not support). The expected failures are S3 WriteGetObjectResponse cases that need host labels.
  • Broke the engine, the AWS rules functions, parameter binding, and the request pipeline on purpose and confirmed the generated tests fail on both the resolver and client paths.

Performance

An uncached resolution is 4-13x faster than boto3 and a cache hit takes about 1 µs. Mean time per uncached call in µs, on m7i.xlarge with CPython 3.14:

Case smithy-python boto3
S3 virtual addressing 10.3 131
S3 access point ARN 18.1 130
S3 Outposts 22.6 121
Lambda, us-east-1 4.6 18.4

Full standardized benchmark results are in the appendix in designs/endpoints.md.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@arandito
arandito marked this pull request as ready for review November 3, 2026 15:06
@arandito
arandito requested a review from a team as a code owner November 3, 2026 15:06

if signing is not None:
option, scheme, identity = signing
signer_properties = scheme.signer_properties(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we’re missing the resolved endpoint’s auth properties here. It looks like signingRegion, signingName, and disableDoubleEncoding never get merged into the signer properties

for (OperationShape operation : TopDownIndex.of(model).getContainedOperations(service)) {
var http = operation.getTrait(HttpTrait.class);
if (http.isPresent() && placesBucket(model, operation, http.get())) {
var uri = http.get().getUri().toString().substring(BUCKET_LABEL.length());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could we avoid removing /{Bucket} from the operation model unconditionally? Ideally static/custom resolvers retain the modeled path

@jonathan343 jonathan343 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @arandito! I mostly have questions for now. Still processing all these changes

Comment on lines +175 to +179
use_dualstack_endpoint: bool | None = None
"""Whether to use dual-stack."""

use_fips_endpoint: bool | None = None
"""Whether to use FIPS-compliant regional endpoint."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit - I think our descriptions can be improved a bit here to be more helpful

),
"root": 2,
"nodeCount": 14,
"nodes": "/////wAAAAH/////AAAAAAAAAA0AAAADAAAAAQAAAAQF9eEMAAAAAgAAAAUF9eEMAAAAAwAAAAgAAAAGAAAABAAAAAcF9eELAAAABQX14QkF9eEKAAAABAAAAAsAAAAJAAAABgAAAAoF9eEIAAAABwX14QYF9eEHAAAABQAAAAwF9eEFAAAABgX14QQF9eEFAAAAAwX14QEAAAAOAAAABAX14QIF9eED",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Investigating myself, but asking incase I forget

Why isn't this reflected in the endpoints.py file? Do we do some conversion into a different format?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The trait stores nodes as a base64 string (spec) and EndpointBddGenerator decodes them to plain ints in endpoints.py to avoid doing it at runtime. For example, /////wAAAAH///// decodes to -1, 1, -1.

]

dependencies = [
"smithy_aws_core~=0.12.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why did this change here and not in the other places?

Edit: Oh is it because the old endpoint resolver was the only thing it was pulling in? And the others still pull in this dependency for other things like protocols?

new BuiltInField("use_dualstack_endpoint", null),
// The SDKs and Tools Reference Guide default.
"AWS::Auth::AccountIdEndpointMode",
new BuiltInField("account_id_endpoint_mode", "preferred"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this value validated anywhere. We should only be allowing preferred, disabled, or required. We also need to treat it as disabled when the request isn't signed.

Comment on lines +100 to +102
ContextParamTrait.ID, // endpoint parameter plans read these at runtime
StaticContextParamsTrait.ID,
OperationContextParamsTrait.ID,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't see these being pulled in in the protocol test clients we have generated in this repo. Was this tested E2E with a service that does use them?

Comment thread designs/endpoints.md
@@ -0,0 +1,476 @@
# Endpoint Resolution

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you format this w/ npx prettier or something? Main thing for readability is the long lines for me

@@ -0,0 +1,4 @@
{
"type": "breaking",
"description": "Removed `AsyncStandardRegionalEndpointsResolver` and its `smithy_aws_core.aio.endpoints` package, along with `smithy_aws_core.endpoints.RegionalEndpointConfig` and `REGIONAL_ENDPOINT_CONFIG`. Generated AWS clients now resolve endpoints from the service's endpoint rules with `smithy_core.aio.endpoints.AsyncBddEndpointResolver` and `smithy_aws_core.endpoints.AWS_RULES_EXTENSION`."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

and its smithy_aws_core.aio.endpoints package

Do you mean module?

auth_schemes=MappingProxyType(
{
"sigv4": SigV4Trait.id,
"sigv4a": ShapeID("aws.auth#sigv4a"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What happens when we see sigv4a and don't support it?

@@ -0,0 +1,4 @@
{
"type": "breaking",
"description": "Added the required `auth_schemes` attribute to the `smithy_core.interfaces.Endpoint` protocol. Custom endpoint types that don't subclass `smithy_core.endpoints.Endpoint` must add it."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is this required now?


__version__ = "0.1.0"
__all__ = ("ConformanceError", "deep_equal", "verify_models", "xml_equal")
__all__ = (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do you know what caused this formatting change?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants