diff --git a/codegen/smithy-python-codegen-test/model/main.smithy b/codegen/smithy-python-codegen-test/model/main.smithy index 39ee49502..d79556b83 100644 --- a/codegen/smithy-python-codegen-test/model/main.smithy +++ b/codegen/smithy-python-codegen-test/model/main.smithy @@ -8,7 +8,10 @@ use smithy.test#httpResponseTests use smithy.waiters#waitable /// Provides weather forecasts. -@restJson1 +@restJson1( + http: ["h2", "http/1.1"] + eventStreamHttp: ["h2"] +) @fakeProtocol @paginated(inputToken: "nextToken", outputToken: "nextToken", pageSize: "pageSize") @httpApiKeyAuth(name: "weather-auth", in: "header") diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/ApplicationProtocol.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/ApplicationProtocol.java index a1c7ae8d2..42a321d3f 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/ApplicationProtocol.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/ApplicationProtocol.java @@ -17,6 +17,7 @@ import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolReference; +import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.utils.SmithyUnstableApi; /** @@ -24,8 +25,12 @@ * application protocol (e.g., "http", "mqtt", etc). */ @SmithyUnstableApi -public record ApplicationProtocol(String name, SymbolReference requestType, SymbolReference responseType) { - +public record ApplicationProtocol( + String name, + SymbolReference requestType, + SymbolReference responseType, + ObjectNode configuration +) { /** * Checks if the protocol is an HTTP based protocol. * @@ -40,7 +45,7 @@ public boolean isHttpProtocol() { * * @return Returns the created application protocol. */ - public static ApplicationProtocol createDefaultHttpApplicationProtocol() { + public static ApplicationProtocol createDefaultHttpApplicationProtocol(ObjectNode config) { return new ApplicationProtocol( "http", SymbolReference.builder() @@ -48,10 +53,20 @@ public static ApplicationProtocol createDefaultHttpApplicationProtocol() { .build(), SymbolReference.builder() .symbol(createHttpSymbol("HTTPResponse")) - .build() + .build(), + config ); } + /** + * Creates a default HTTP application protocol. + * + * @return Returns the created application protocol. + */ + public static ApplicationProtocol createDefaultHttpApplicationProtocol() { + return createDefaultHttpApplicationProtocol(ObjectNode.objectNode()); + } + private static Symbol createHttpSymbol(String symbolName) { PythonDependency dependency = SmithyPythonDependency.SMITHY_HTTP; return Symbol.builder() diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/ConfigGenerator.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/ConfigGenerator.java index fb54c2757..8f847e1c7 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/ConfigGenerator.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/ConfigGenerator.java @@ -20,9 +20,14 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import software.amazon.smithy.codegen.core.Symbol; +import software.amazon.smithy.model.knowledge.EventStreamIndex; import software.amazon.smithy.model.knowledge.ServiceIndex; import software.amazon.smithy.model.knowledge.TopDownIndex; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.StringNode; +import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.python.codegen.integration.PythonIntegration; import software.amazon.smithy.python.codegen.integration.RuntimeClientPlugin; import software.amazon.smithy.python.codegen.sections.ConfigSection; @@ -65,23 +70,8 @@ final class ConfigGenerator implements Runnable { ); // This list contains any properties that must be added to any http-based - // service client. + // service client, except for the http client itself. private static final List HTTP_PROPERTIES = Arrays.asList( - ConfigProperty.builder() - .name("http_client") - .type(Symbol.builder() - .name("HTTPClient") - .namespace("smithy_http.aio.interfaces", ".") - .addDependency(SmithyPythonDependency.SMITHY_HTTP) - .build()) - .documentation("The HTTP client used to make requests.") - .nullable(false) - .initialize(writer -> { - writer.addDependency(SmithyPythonDependency.SMITHY_HTTP); - writer.addImport("smithy_http.aio.aiohttp", "AIOHTTPClient"); - writer.write("self.http_client = http_client or AIOHTTPClient()"); - }) - .build(), ConfigProperty.builder() .name("http_request_config") .type(Symbol.builder() @@ -137,6 +127,64 @@ final class ConfigGenerator implements Runnable { this.settings = settings; } + private static List getHttpProperties(GenerationContext context) { + var properties = new ArrayList(HTTP_PROPERTIES.size() + 1); + var clientBuilder = ConfigProperty.builder() + .name("http_client") + .type(Symbol.builder() + .name("HTTPClient") + .namespace("smithy_http.aio.interfaces", ".") + .addDependency(SmithyPythonDependency.SMITHY_HTTP) + .build()) + .documentation("The HTTP client used to make requests.") + .nullable(false); + + 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()"); + }); + + } 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()"); + }); + } + properties.add(clientBuilder.build()); + + properties.addAll(HTTP_PROPERTIES); + return List.copyOf(properties); + } + + private static boolean usesHttp2(GenerationContext context) { + var configuration = context.applicationProtocol().configuration(); + var httpVersions = configuration.getArrayMember("http") + .orElse(ArrayNode.arrayNode()) + .getElementsAs(StringNode.class) + .stream().map(node -> node.getValue().toLowerCase(Locale.ENGLISH)).toList(); + + // An explicit http2 configuration + if (httpVersions.contains("h2")) { + return true; + } + + // Bidirectional streaming REQUIRES h2 inherently + var eventIndex = EventStreamIndex.of(context.model()); + var topDownIndex = TopDownIndex.of(context.model()); + for (OperationShape operation : topDownIndex.getContainedOperations(context.settings().service())) { + if (eventIndex.getInputInfo(operation).isPresent() && eventIndex.getOutputInfo(operation).isPresent()) { + return true; + } + } + + return false; + } + private static List getHttpAuthProperties(GenerationContext context) { return List.of( ConfigProperty.builder() @@ -254,7 +302,7 @@ private void generateConfig(GenerationContext context, PythonWriter writer) { // and add them in if the protocol is going to need them. var serviceIndex = ServiceIndex.of(context.model()); if (context.applicationProtocol().isHttpProtocol()) { - properties.addAll(HTTP_PROPERTIES); + properties.addAll(getHttpProperties(context)); if (!serviceIndex.getAuthSchemes(settings.service()).isEmpty()) { properties.addAll(getHttpAuthProperties(context)); writer.onSection(new AddAuthHelper()); diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/GenerationContext.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/GenerationContext.java index feecfb72b..0315e056b 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/GenerationContext.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/GenerationContext.java @@ -96,7 +96,7 @@ public ProtocolGenerator protocolGenerator() { */ public ApplicationProtocol applicationProtocol() { return protocolGenerator != null - ? protocolGenerator.getApplicationProtocol() + ? protocolGenerator.getApplicationProtocol(this) : ApplicationProtocol.createDefaultHttpApplicationProtocol(); } diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonDependency.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonDependency.java index b62bd2a94..2e0fb9fa3 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonDependency.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonDependency.java @@ -56,6 +56,10 @@ public SymbolDependency getDependency() { .build(); } + public PythonDependency withOptionalDependencies(String... optionalDependencies) { + return new PythonDependency(packageName, version, type, isLink, List.of(optionalDependencies)); + } + /** * An enum of valid dependency types. */ diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/SmithyPythonDependency.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/SmithyPythonDependency.java index 954c207d0..26a34ca17 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/SmithyPythonDependency.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/SmithyPythonDependency.java @@ -49,9 +49,7 @@ public final class SmithyPythonDependency { // You'll need to locally install this before we publish "==0.0.1", Type.DEPENDENCY, - false, - // TODO: make this configurable - List.of("aiohttp") + false ); /** diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/HttpBindingProtocolGenerator.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/HttpBindingProtocolGenerator.java index e552eee7e..b34d45a68 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/HttpBindingProtocolGenerator.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/HttpBindingProtocolGenerator.java @@ -92,7 +92,7 @@ public abstract class HttpBindingProtocolGenerator implements ProtocolGenerator private final Set deserializingDocumentShapes = new TreeSet<>(); @Override - public ApplicationProtocol getApplicationProtocol() { + public ApplicationProtocol getApplicationProtocol(GenerationContext context) { return ApplicationProtocol.createDefaultHttpApplicationProtocol(); } diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/ProtocolGenerator.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/ProtocolGenerator.java index 78257ec2e..e6ab93e37 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/ProtocolGenerator.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/ProtocolGenerator.java @@ -55,7 +55,7 @@ default String getName() { * * @return Returns the created application protocol. */ - ApplicationProtocol getApplicationProtocol(); + ApplicationProtocol getApplicationProtocol(GenerationContext context); /** diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/RestJsonProtocolGenerator.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/RestJsonProtocolGenerator.java index 39484cd84..5246a031b 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/RestJsonProtocolGenerator.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/RestJsonProtocolGenerator.java @@ -19,6 +19,8 @@ import java.util.Set; import software.amazon.smithy.aws.traits.protocols.RestJson1Trait; import software.amazon.smithy.model.knowledge.HttpBinding; +import software.amazon.smithy.model.node.ArrayNode; +import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.Shape; @@ -27,6 +29,7 @@ import software.amazon.smithy.model.traits.StreamingTrait; import software.amazon.smithy.model.traits.TimestampFormatTrait.Format; import software.amazon.smithy.protocoltests.traits.HttpMessageTestCase; +import software.amazon.smithy.python.codegen.ApplicationProtocol; import software.amazon.smithy.python.codegen.CodegenUtils; import software.amazon.smithy.python.codegen.GenerationContext; import software.amazon.smithy.python.codegen.HttpProtocolTestGenerator; @@ -90,6 +93,17 @@ public ShapeId getProtocol() { return RestJson1Trait.ID; } + @Override + public ApplicationProtocol getApplicationProtocol(GenerationContext context) { + var service = context.settings().service(context.model()); + var trait = service.expectTrait(RestJson1Trait.class); + var config = ObjectNode.builder() + .withMember("http", ArrayNode.fromStrings(trait.getHttp())) + .withMember("eventStreamHttp", ArrayNode.fromStrings(trait.getEventStreamHttp())) + .build(); + return ApplicationProtocol.createDefaultHttpApplicationProtocol(config); + } + @Override protected Format getDocumentTimestampFormat() { return Format.EPOCH_SECONDS;