Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion codegen/smithy-python-codegen-test/model/main.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@

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;

/**
* Represents the resolves {@link Symbol}s and references for an
* 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.
*
Expand All @@ -40,18 +45,28 @@ 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()
.symbol(createHttpSymbol("HTTPRequest"))
.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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ConfigProperty> 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()
Expand Down Expand Up @@ -137,6 +127,64 @@ final class ConfigGenerator implements Runnable {
this.settings = settings;
}

private static List<ConfigProperty> getHttpProperties(GenerationContext context) {
var properties = new ArrayList<ConfigProperty>(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<ConfigProperty> getHttpAuthProperties(GenerationContext context) {
return List.of(
ConfigProperty.builder()
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public ProtocolGenerator protocolGenerator() {
*/
public ApplicationProtocol applicationProtocol() {
return protocolGenerator != null
? protocolGenerator.getApplicationProtocol()
? protocolGenerator.getApplicationProtocol(this)
: ApplicationProtocol.createDefaultHttpApplicationProtocol();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public abstract class HttpBindingProtocolGenerator implements ProtocolGenerator
private final Set<Shape> deserializingDocumentShapes = new TreeSet<>();

@Override
public ApplicationProtocol getApplicationProtocol() {
public ApplicationProtocol getApplicationProtocol(GenerationContext context) {
return ApplicationProtocol.createDefaultHttpApplicationProtocol();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ default String getName() {
*
* @return Returns the created application protocol.
*/
ApplicationProtocol getApplicationProtocol();
ApplicationProtocol getApplicationProtocol(GenerationContext context);


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Loading