Skip to content
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

Enable shapes-only codegen #373

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
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
153 changes: 150 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ This repository does *not* contain any generated clients, such as for S3 or othe
AWS services. Rather, these are the tools that facilitate the generation of those
clients and non-AWS Smithy clients.

### How do I use this?
### How do I use this to create a client?

The first step is to create a Smithy pacakge. If this is your first time working
with Smithy, follow [this quickstart guide](https://smithy.io/2.0/quickstart.html)
Expand Down Expand Up @@ -82,8 +82,8 @@ this file, see the
"sources": ["model"],
"maven": {
"dependencies": [
"software.amazon.smithy:smithy-model:[1.34.0,2.0)",
"software.amazon.smithy:smithy-aws-traits:[1.34.0,2.0)",
"software.amazon.smithy:smithy-model:[1.54.0,2.0)",
"software.amazon.smithy:smithy-aws-traits:[1.54.0,2.0)",
"software.amazon.smithy.python:smithy-python-codegen:0.1.0"
]
},
Expand Down Expand Up @@ -145,6 +145,153 @@ Only for now. Once the generator has been published, the Smithy CLI will be able
to run it without a separate Java installation. Similarly, once the python
helper libraries have been published you won't need to install them manually.

### How do I generate types for shapes without a client?

If all you want are concrete Python classes for the shapes in your Smithy model,
all you need to do is replace `python-client-codegen` with
`python-shape-codegen` when following the steps above. Your `smithy-build.json`
would now look like:

```json
{
"version": "1.0",
"sources": ["model"],
"maven": {
"dependencies": [
"software.amazon.smithy:smithy-model:[1.54.0,2.0)",
"software.amazon.smithy.python:smithy-python-codegen:0.1.0"
]
},
"projections": {
"shapes": {
"plugins": {
"python-shape-codegen": {
"service": "com.example#EchoService",
"module": "echo",
"moduleVersion": "0.0.1"
}
}
}
}
}
```

The module with the generated shape classes can be found in
`build/smithy/client/python-shape-codegen` after you run `smithy-build`.

Unlike when generating a client, a service shape is not required for shape
generation. If a service is not provided then every shape found in the model
will be generated. Any naming conflicts may be resolved by using the
[`renameShapes` transform](https://smithy.io/2.0/guides/smithy-build-json.html#renameshapes)
(or renaming the shapes in the model of course).

The set of shapes generated can also be constrained by using the
[`includeShapesBySelector` transform](https://smithy.io/2.0/guides/smithy-build-json.html#includeshapesbyselector).
For example, to generate only shapes within the `com.example` namespace:

```json
{
"version": "1.0",
"sources": ["model"],
"maven": {
"dependencies": [
"software.amazon.smithy:smithy-model:[1.54.0,2.0)",
"software.amazon.smithy.python:smithy-python-codegen:0.1.0"
]
},
"projections": {
"shapes": {
"transforms": [
{
"name": "includeShapesBySelector",
"args": {
"selector": "[id|namespace = 'com.example']"
}
}
],
"plugins": {
"python-shape-codegen": {
"module": "echo",
"moduleVersion": "0.0.1"
}
}
}
}
}
```

Input and output shapes (shapes with the `@input` or `@output` traits and
operation inputs / outputs created as part of an operation definition) are not
generated by default. To generate these shapes anyway, remove the traits with
the
[`excludeTraits` transform](https://smithy.io/2.0/guides/smithy-build-json.html#excludetraits):

```json
{
"version": "1.0",
"sources": ["model"],
"maven": {
"dependencies": [
"software.amazon.smithy:smithy-model:[1.54.0,2.0)",
"software.amazon.smithy.python:smithy-python-codegen:0.1.0"
]
},
"projections": {
"shapes": {
"transforms": [
{
"name": "excludeTraits",
"args": {
"traits": ["input", "output"]
}
}
],
"plugins": {
"python-shape-codegen": {
"module": "echo",
"moduleVersion": "0.0.1"
}
}
}
}
}
```

You can also generate both a client package and a shape package in one build,
but they won't depend on each other. To do this, just add both plugins in the
projection, or create a projection for each plugin. Below is an example showing
both plugins in one projection:

```json
{
"version": "1.0",
"sources": ["model"],
"maven": {
"dependencies": [
"software.amazon.smithy:smithy-model:[1.54.0,2.0)",
"software.amazon.smithy:smithy-aws-traits:[1.54.0,2.0)",
"software.amazon.smithy.python:smithy-python-codegen:0.1.0"
]
},
"projections": {
"client": {
"plugins": {
"python-client-codegen": {
"service": "com.example#EchoService",
"module": "echo",
"moduleVersion": "0.0.1"
},
"python-shape-codegen": {
"service": "com.example#EchoService",
"module": "echo",
"moduleVersion": "0.0.1"
}
}
}
}
}
```

### Core Modules and Interfaces

* `smithy-core` provides transport-agnostic core modules and interfaces
Expand Down
5 changes: 5 additions & 0 deletions codegen/smithy-python-codegen-test/smithy-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
"service": "example.weather#Weather",
"module": "weather",
"moduleVersion": "0.0.1"
},
"python-shape-codegen": {
Copy link
Contributor

Choose a reason for hiding this comment

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

To be consistent with smithy-java, let's call it python-type-codegen?

"service": "example.weather#Weather",
"module": "weather",
"moduleVersion": "0.0.1"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.InputTrait;
import software.amazon.smithy.model.traits.OutputTrait;
import software.amazon.smithy.python.codegen.generators.ListGenerator;
import software.amazon.smithy.python.codegen.generators.MapGenerator;
import software.amazon.smithy.python.codegen.integration.ProtocolGenerator;
Expand Down Expand Up @@ -112,10 +114,13 @@ private ProtocolGenerator resolveProtocolGenerator(
@Override
public void customizeBeforeShapeGeneration(CustomizeDirective<GenerationContext, PythonSettings> directive) {
generateServiceErrors(directive.settings(), directive.context().writerDelegator());
new ConfigGenerator(directive.settings(), directive.context()).run();

generateSchemas(directive.context(), directive.connectedShapes().values());

if (directive.settings().artifactType().equals(PythonSettings.ArtifactType.SHAPES)) {
return;
}

new ConfigGenerator(directive.settings(), directive.context()).run();
var serviceIndex = ServiceIndex.of(directive.model());
if (directive.context().applicationProtocol().isHttpProtocol()
&& !serviceIndex.getAuthSchemes(directive.service()).isEmpty()) {
Expand All @@ -130,12 +135,18 @@ private void generateSchemas(GenerationContext context, Collection<Shape> shapes
.filter(shapes::contains)
.filter(shape -> !shape.isOperationShape() && !shape.isResourceShape()
&& !shape.isServiceShape() && !shape.isMemberShape() && !Prelude.isPreludeShape(shape))
// If we're only generating data shapes, there's no need to generate input or output shapes.
.filter(shape -> shape.isStructureShape() && !shouldGenerateStructure(context.settings(), shape))
.forEach(schemaGenerator);
schemaGenerator.finalizeRecursiveShapes();
}

@Override
public void generateService(GenerateServiceDirective<GenerationContext, PythonSettings> directive) {
if (directive.settings().artifactType().equals(PythonSettings.ArtifactType.SHAPES)) {
return;
}

new ClientGenerator(directive.context(), directive.service()).run();

var protocolGenerator = directive.context().protocolGenerator();
Expand Down Expand Up @@ -197,6 +208,11 @@ public void generateResource(GenerateResourceDirective<GenerationContext, Python

@Override
public void generateStructure(GenerateStructureDirective<GenerationContext, PythonSettings> directive) {
// If we're only generating data shapes, there's no need to generate input or output shapes.
if (!shouldGenerateStructure(directive.settings(), directive.shape())) {
return;
}

directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> {
StructureGenerator generator = new StructureGenerator(
directive.context(),
Expand All @@ -208,6 +224,14 @@ public void generateStructure(GenerateStructureDirective<GenerationContext, Pyth
});
}

private boolean shouldGenerateStructure(PythonSettings settings, Shape shape) {
if (shape.getId().getNamespace().equals("smithy.synthetic")) {
return false;
}
return !(settings.artifactType().equals(PythonSettings.ArtifactType.SHAPES)
&& (shape.hasTrait(InputTrait.class) || shape.hasTrait(OutputTrait.class)));
}

@Override
public void generateError(GenerateErrorDirective<GenerationContext, PythonSettings> directive) {
directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package software.amazon.smithy.python.codegen;

import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.model.Model;
Expand All @@ -35,13 +36,15 @@
* @param moduleName The name of the module to generate.
* @param moduleVersion The version of the module to generate.
* @param moduleDescription The optional module description for the module that will be generated.
* @param artifactType The type of artifact being generated (e.g. client/shapes/server)
*/
@SmithyUnstableApi
public record PythonSettings(
ShapeId service,
String moduleName,
String moduleVersion,
String moduleDescription
String moduleDescription,
ArtifactType artifactType
) implements ToSmithyBuilder<PythonSettings> {

private static final String SERVICE = "service";
Expand All @@ -60,14 +63,15 @@ public record PythonSettings(
}
}

public PythonSettings(Builder builder) {
private PythonSettings(Builder builder) {
this(
builder.service,
builder.moduleName,
builder.moduleVersion,
StringUtils.isBlank(builder.moduleDescription)
? builder.moduleName + " client"
: builder.moduleDescription
? builder.moduleName + " " + builder.artifactType.name().toLowerCase(Locale.ENGLISH)
: builder.moduleDescription,
builder.artifactType
);
}

Expand Down Expand Up @@ -105,13 +109,22 @@ public static PythonSettings fromNode(ObjectNode config) {
return builder.build();
}

/**
* The type of artifact being generated.
*/
public enum ArtifactType {
CLIENT,
SHAPES;
}

@Override
public SmithyBuilder<PythonSettings> toBuilder() {
public Builder toBuilder() {
return builder()
.service(service)
.moduleName(moduleName)
.moduleVersion(moduleVersion)
.moduleDescription(moduleDescription);
.moduleDescription(moduleDescription)
.artifactType(artifactType);
}

public static Builder builder() {
Expand All @@ -124,6 +137,7 @@ public static class Builder implements SmithyBuilder<PythonSettings> {
private String moduleName;
private String moduleVersion;
private String moduleDescription;
private ArtifactType artifactType = ArtifactType.CLIENT;

@Override
public PythonSettings build() {
Expand Down Expand Up @@ -152,5 +166,10 @@ public Builder moduleDescription(String moduleDescription) {
this.moduleDescription = moduleDescription;
return this;
}

public Builder artifactType(ArtifactType artifactType) {
this.artifactType = artifactType;
return this;
}
}
}
Loading
Loading