Skip to content

Pure python request pipeline #429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2cacaf3
Implement the request pipeline in pure python
JordonPhillips Mar 12, 2025
a07082b
Replace code generated request pipeline
JordonPhillips May 26, 2025
6df2d87
Set default namespace for json codec
JordonPhillips May 30, 2025
f9d6bdb
Use TypeForm for events
JordonPhillips Jul 9, 2025
1c65e4b
Check for enum response bodies
JordonPhillips Jul 9, 2025
e477381
Headers are never null
JordonPhillips Jul 9, 2025
754ed2d
Serialize float headers correctly
JordonPhillips Jul 9, 2025
618d663
Handle medaitype in headers
JordonPhillips Jun 2, 2025
58e2192
Call read_data_stream on data streams
JordonPhillips Jul 9, 2025
92a3056
Don't serialize content-length without content-type
JordonPhillips Jul 9, 2025
7e34bde
Update protocol test skip list
JordonPhillips Jul 9, 2025
fdf7ee2
Merge signer/identity properties
JordonPhillips Jul 9, 2025
2dce401
Log endpoint params
JordonPhillips Jul 9, 2025
038ae87
Update changelogs with unreleased changes
JordonPhillips Jul 11, 2025
ac9c6dd
Fix AuthOptions generator
nateprewitt Jul 26, 2025
63b71a7
Don't use result of update
JordonPhillips Jul 29, 2025
0a24ac4
Add AWS static config
JordonPhillips Jul 29, 2025
924e169
Avoid logging schemas
JordonPhillips Jul 29, 2025
b011eb8
Wrap HTTP payloads in async readables
JordonPhillips Jul 29, 2025
e409916
Expose underlying seek on AsyncBytesReader
JordonPhillips Jul 29, 2025
423dc9e
Check seekable in aws signers
JordonPhillips Jul 29, 2025
d375432
Make ConditionallySeekable a Protocol
nateprewitt Jul 29, 2025
e831bdf
Match test cases to new wrapping behavior in smithy-http
nateprewitt Jul 29, 2025
c14a0dd
Check unknown error content type before read
JordonPhillips Jul 30, 2025
16f4b32
Don't pass on None payloads
JordonPhillips Jul 30, 2025
3a2dff9
Fix path and query serialization
JordonPhillips Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,13 @@

## Unreleased

* Description of change. (Issue Number)
### Breaking Changes

* Removed the `http_client` config option in favor of the generic `transport`.

### Features

* Removed code-generated protocol implementations in favor of hand-written
implementations based on schemas.
* Moved documentation for structure members into doc strings after the member's
dataclass field declaration.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public class AwsAuthIntegration implements PythonIntegration {

@Override
public List<RuntimeClientPlugin> getClientPlugins(GenerationContext context) {
if (!hasSigV4Auth(context)) {
return List.of();
}
return List.of(
RuntimeClientPlugin.builder()
.servicePredicate((model, service) -> service.hasTrait(SigV4Trait.class))
Expand Down Expand Up @@ -61,6 +64,24 @@ public List<RuntimeClientPlugin> getClientPlugins(GenerationContext context) {
.nullable(true)
.build())
.addConfigProperty(REGION)
.addConfigProperty(ConfigProperty.builder()
.name("aws_access_key_id")
.type(Symbol.builder().name("str").build())
.documentation("The identifier for a secret access key.")
.nullable(true)
.build())
.addConfigProperty(ConfigProperty.builder()
.name("aws_secret_access_key")
.type(Symbol.builder().name("str").build())
.nullable(true)
.documentation("A secret access key that can be used to sign requests.")
.build())
.addConfigProperty(ConfigProperty.builder()
.name("aws_session_token")
.type(Symbol.builder().name("str").build())
.documentation("An access key ID that identifies temporary security credentials.")
.nullable(true)
.build())
.authScheme(new Sigv4AuthScheme())
.build());
}
Expand All @@ -70,7 +91,6 @@ public void customize(GenerationContext context) {
if (!hasSigV4Auth(context)) {
return;
}
var trait = context.settings().service(context.model()).expectTrait(SigV4Trait.class);
var resolver = CodegenUtils.getHttpAuthSchemeResolverSymbol(context.settings());

// Add a function that generates the http auth option for api key auth.
Expand All @@ -91,8 +111,7 @@ public void customize(GenerationContext context) {
)
""",
SIGV4_OPTION_GENERATOR_NAME,
SigV4Trait.ID.toString(),
trait.getName());
SigV4Trait.ID.toString());
writer.popState();
});
}
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,6 @@ public void generateService(GenerateServiceDirective<GenerationContext, PythonSe
if (protocolGenerator == null) {
return;
}

protocolGenerator.generateSharedSerializerComponents(directive.context());
protocolGenerator.generateRequestSerializers(directive.context());

protocolGenerator.generateSharedDeserializerComponents(directive.context());
protocolGenerator.generateResponseDeserializers(directive.context());

protocolGenerator.generateProtocolTests(directive.context());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ private void generateRequestTest(OperationShape operation, HttpRequestTestCase t
writer.write("""
config = $T(
endpoint_uri="https://$L/$L",
http_client = $T(),
transport = $T(),
retry_strategy=SimpleRetryStrategy(max_attempts=1),
)
""",
Expand Down Expand Up @@ -432,7 +432,7 @@ private void generateResponseTest(OperationShape operation, HttpResponseTestCase
writer.write("""
config = $T(
endpoint_uri="https://example.com",
http_client = $T(
transport = $T(
status=$L,
headers=$J,
body=b$S,
Expand Down Expand Up @@ -485,7 +485,7 @@ private void generateErrorResponseTest(
writer.write("""
config = $T(
endpoint_uri="https://example.com",
http_client = $T(
transport = $T(
status=$L,
headers=$J,
body=b$S,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ public final class SmithyPythonDependency {
Type.DEPENDENCY,
false);

/**
* Smithy core functionality for AWS.
*/
public static final PythonDependency SMITHY_AWS_CORE = new PythonDependency(
"smithy_aws_core",
"<0.1.0",
Type.DEPENDENCY,
false);

/**
* testing framework used in generated functional tests.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,38 +117,58 @@ public ConfigGenerator(PythonSettings settings, GenerationContext context) {
this.settings = settings;
}

private static List<ConfigProperty> getHttpProperties(GenerationContext context) {
var properties = new ArrayList<ConfigProperty>(HTTP_PROPERTIES.size() + 2);
var clientBuilder = ConfigProperty.builder()
.name("http_client")
private static List<ConfigProperty> getProtocolProperties(GenerationContext context) {
var properties = new ArrayList<ConfigProperty>();
var protocolBuilder = ConfigProperty.builder()
.name("protocol")
.type(Symbol.builder()
.name("HTTPClient")
.namespace("smithy_http.aio.interfaces", ".")
.addDependency(SmithyPythonDependency.SMITHY_HTTP)
.name("ClientProtocol[Any, Any]")
.addReference(Symbol.builder()
.name("ClientProtocol")
.namespace("smithy_core.aio.interfaces", ".")
.build())
.build())
.documentation("The protocol to serialize and deserialize requests with.")
.initialize(w -> {
w.write("self.protocol = protocol or ${C|}",
w.consumer(writer -> context.protocolGenerator().initializeProtocol(context, writer)));
});

var transportBuilder = ConfigProperty.builder()
.name("transport")
.type(Symbol.builder()
.name("ClientTransport[Any, Any]")
.addReference(Symbol.builder()
.name("ClientTransport")
.namespace("smithy_core.aio.interfaces", ".")
.build())
.build())
.documentation("The HTTP client used to make requests.")
.nullable(false);
.documentation("The transport to use to send requests (e.g. an HTTP client).");

if (usesHttp2(context)) {
clientBuilder
.initialize(writer -> {
writer.addDependency(SmithyPythonDependency.SMITHY_HTTP.withOptionalDependencies("awscrt"));
writer.addImport("smithy_http.aio.crt", "AWSCRTHTTPClient");
writer.write("self.http_client = http_client or AWSCRTHTTPClient()");
});
if (context.applicationProtocol().isHttpProtocol()) {
properties.addAll(HTTP_PROPERTIES);
if (usesHttp2(context)) {
transportBuilder
.initialize(writer -> {
writer.addDependency(SmithyPythonDependency.SMITHY_HTTP.withOptionalDependencies("awscrt"));
writer.addImport("smithy_http.aio.crt", "AWSCRTHTTPClient");
writer.write("self.transport = transport or AWSCRTHTTPClient()");
});

} else {
clientBuilder
.initialize(writer -> {
writer.addDependency(SmithyPythonDependency.SMITHY_HTTP.withOptionalDependencies("aiohttp"));
writer.addImport("smithy_http.aio.aiohttp", "AIOHTTPClient");
writer.write("self.http_client = http_client or AIOHTTPClient()");
});
} else {
transportBuilder
.initialize(writer -> {
writer.addDependency(
SmithyPythonDependency.SMITHY_HTTP.withOptionalDependencies("aiohttp"));
writer.addImport("smithy_http.aio.aiohttp", "AIOHTTPClient");
writer.write("self.transport = transport or AIOHTTPClient()");
});
}
}
properties.add(clientBuilder.build());

properties.addAll(HTTP_PROPERTIES);
return List.copyOf(properties);
properties.add(protocolBuilder.build());
properties.add(transportBuilder.build());
return properties;
}

private static boolean usesHttp2(GenerationContext context) {
Expand Down Expand Up @@ -289,6 +309,7 @@ private void generateConfig(GenerationContext context, PythonWriter writer) {
// Initialize a set of config properties with our base properties.
var properties = new TreeSet<>(Comparator.comparing(ConfigProperty::name));
properties.addAll(BASE_PROPERTIES);
properties.addAll(getProtocolProperties(context));

// Add in auth configuration if the service supports auth.
var serviceIndex = ServiceIndex.of(context.model());
Expand All @@ -297,13 +318,6 @@ private void generateConfig(GenerationContext context, PythonWriter writer) {
writer.onSection(new AddAuthHelper());
}

// Smithy is transport agnostic, so we don't add http-related properties by default.
// Nevertheless, HTTP is the most common use case so we standardize those settings
// and add them in if the protocol is going to need them.
if (context.applicationProtocol().isHttpProtocol()) {
properties.addAll(getHttpProperties(context));
}

var model = context.model();
var service = context.settings().service(model);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.TimestampShape;
import software.amazon.smithy.model.shapes.UnionShape;
import software.amazon.smithy.model.traits.StreamingTrait;
import software.amazon.smithy.python.codegen.GenerationContext;
import software.amazon.smithy.python.codegen.SymbolProperties;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
Expand Down Expand Up @@ -100,7 +101,11 @@ private void writeSchema() {

@Override
public Void blobShape(BlobShape shape) {
writeDeserializer(shape);
if (shape.hasTrait(StreamingTrait.class)) {
writeDeserializer("data_stream");
} else {
writeDeserializer(shape);
}
Comment on lines +104 to +108
Copy link
Contributor

Choose a reason for hiding this comment

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

What's unique with the data_stream here that shape isn't accounting for? Just that it's an unknown length/shape?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

read_blob expects bytes, read_data_stream expects a stream. As for writeDeserializer, when given a shape it uses the name of the shape type as the read method to dispatch to. When given a string, it just does read_{name}. We wouldn't want it to check streaming automatically since it should only apply to blobs.

return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.TimestampShape;
import software.amazon.smithy.model.shapes.UnionShape;
import software.amazon.smithy.model.traits.StreamingTrait;
import software.amazon.smithy.python.codegen.GenerationContext;
import software.amazon.smithy.python.codegen.SymbolProperties;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
Expand Down Expand Up @@ -107,7 +108,11 @@ private void writeSchema() {

@Override
public Void blobShape(BlobShape shape) {
writeSerializer(shape);
if (shape.hasTrait(StreamingTrait.class)) {
writeSerializer("data_stream");
} else {
writeSerializer(shape);
}
return null;
}

Expand Down
Loading
Loading