From 3b9f6f6a2a43a2c062e8eb755519b6e0be7ce06e Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Wed, 29 Jan 2025 15:42:06 +0100 Subject: [PATCH 1/2] Enable shapes-only codegen This adds an alternative generation mode that only generates "data shapes". --- README.md | 107 +++++++++++++++++- .../smithy-build.json | 5 + .../python/codegen/DirectedPythonCodegen.java | 11 +- .../smithy/python/codegen/PythonSettings.java | 29 ++++- .../codegen/PythonShapeCodegenPlugin.java | 37 ++++++ .../codegen/integration/HttpApiKeyAuth.java | 3 +- ...ware.amazon.smithy.build.SmithyBuildPlugin | 1 + 7 files changed, 182 insertions(+), 11 deletions(-) create mode 100644 codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeCodegenPlugin.java diff --git a/README.md b/README.md index 40f93d75..14905931 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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" ] }, @@ -145,6 +145,107 @@ 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`. + +Note that a service shape is still required. In this case, it's used for the +purposes of namespacing since all shapes within a service's closure must have a +unique name. The service shape also has the `rename` property to resolve any +conflicts you might encounter. + +The one downside to this is that at time of writing there is no way to add +shapes to a service that aren't connected to it via an operation or error. In +the future, the service shape will have a `shapes` property or some other way of +doing this. For now, it is recommended to just create a dummy operation to add +any shapes needed, like below: + +```smithy +$version: "2.0" + +namespace com.example + +service ShapeNamespaceService { + version: "2006-03-01" + operations: [ShapeContainer] +} + +operation ShapeContainer { + input := { + example: ExampleShape + } +} + +structure ExampleShape { + intMember: Integer +} +``` + +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 diff --git a/codegen/smithy-python-codegen-test/smithy-build.json b/codegen/smithy-python-codegen-test/smithy-build.json index 6249eaf5..2d4ac37d 100644 --- a/codegen/smithy-python-codegen-test/smithy-build.json +++ b/codegen/smithy-python-codegen-test/smithy-build.json @@ -5,6 +5,11 @@ "service": "example.weather#Weather", "module": "weather", "moduleVersion": "0.0.1" + }, + "python-shape-codegen": { + "service": "example.weather#Weather", + "module": "weather", + "moduleVersion": "0.0.1" } } } diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java index fb8a30c4..dfe850f4 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java @@ -112,10 +112,13 @@ private ProtocolGenerator resolveProtocolGenerator( @Override public void customizeBeforeShapeGeneration(CustomizeDirective 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()) { @@ -136,6 +139,10 @@ private void generateSchemas(GenerationContext context, Collection shapes @Override public void generateService(GenerateServiceDirective directive) { + if (directive.settings().artifactType().equals(PythonSettings.ArtifactType.SHAPES)) { + return; + } + new ClientGenerator(directive.context(), directive.service()).run(); var protocolGenerator = directive.context().protocolGenerator(); diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java index ec7645fb..fa85d6d4 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java @@ -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; @@ -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 { private static final String SERVICE = "service"; @@ -66,8 +69,9 @@ public PythonSettings(Builder builder) { 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 ); } @@ -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 toBuilder() { + public Builder toBuilder() { return builder() .service(service) .moduleName(moduleName) .moduleVersion(moduleVersion) - .moduleDescription(moduleDescription); + .moduleDescription(moduleDescription) + .artifactType(artifactType); } public static Builder builder() { @@ -124,6 +137,7 @@ public static class Builder implements SmithyBuilder { private String moduleName; private String moduleVersion; private String moduleDescription; + private ArtifactType artifactType = ArtifactType.CLIENT; @Override public PythonSettings build() { @@ -152,5 +166,10 @@ public Builder moduleDescription(String moduleDescription) { this.moduleDescription = moduleDescription; return this; } + + public Builder artifactType(ArtifactType artifactType) { + this.artifactType = artifactType; + return this; + } } } diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeCodegenPlugin.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeCodegenPlugin.java new file mode 100644 index 00000000..e39368d4 --- /dev/null +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeCodegenPlugin.java @@ -0,0 +1,37 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.python.codegen; + +import software.amazon.smithy.build.PluginContext; +import software.amazon.smithy.build.SmithyBuildPlugin; +import software.amazon.smithy.codegen.core.directed.CodegenDirector; +import software.amazon.smithy.python.codegen.integration.PythonIntegration; + +public final class PythonShapeCodegenPlugin implements SmithyBuildPlugin { + @Override + public String getName() { + return "python-shape-codegen"; + } + + @Override + public void execute(PluginContext context) { + CodegenDirector runner + = new CodegenDirector<>(); + + PythonSettings settings = PythonSettings.fromNode(context.getSettings()).toBuilder() + .artifactType(PythonSettings.ArtifactType.SHAPES) + .build(); + + runner.settings(settings); + runner.directedCodegen(new DirectedPythonCodegen()); + runner.fileManifest(context.getFileManifest()); + runner.service(settings.service()); + runner.model(context.getModel()); + runner.integrationClass(PythonIntegration.class); + runner.performDefaultCodegenTransforms(); + runner.run(); + } +} diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/HttpApiKeyAuth.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/HttpApiKeyAuth.java index 2ccd59e0..ff50d7ba 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/HttpApiKeyAuth.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/integration/HttpApiKeyAuth.java @@ -13,6 +13,7 @@ import software.amazon.smithy.python.codegen.CodegenUtils; import software.amazon.smithy.python.codegen.ConfigProperty; import software.amazon.smithy.python.codegen.GenerationContext; +import software.amazon.smithy.python.codegen.PythonSettings; import software.amazon.smithy.python.codegen.SmithyPythonDependency; /** @@ -57,7 +58,7 @@ public List getClientPlugins() { @Override public void customize(GenerationContext context) { - if (!hasApiKeyAuth(context)) { + if (!hasApiKeyAuth(context) || !context.settings().artifactType().equals(PythonSettings.ArtifactType.CLIENT)) { return; } var trait = context.settings().service(context.model()).expectTrait(HttpApiKeyAuthTrait.class); diff --git a/codegen/smithy-python-codegen/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin b/codegen/smithy-python-codegen/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin index 9df65151..051f373e 100644 --- a/codegen/smithy-python-codegen/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin +++ b/codegen/smithy-python-codegen/src/main/resources/META-INF/services/software.amazon.smithy.build.SmithyBuildPlugin @@ -4,3 +4,4 @@ # software.amazon.smithy.python.codegen.PythonClientCodegenPlugin +software.amazon.smithy.python.codegen.PythonShapeCodegenPlugin From 46b6ba3f2ca840868972752a4c823dcad138338e Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Mon, 3 Feb 2025 17:45:04 +0100 Subject: [PATCH 2/2] Allow generating shapes without a service --- README.md | 94 +++++++++---- .../python/codegen/DirectedPythonCodegen.java | 17 +++ .../smithy/python/codegen/PythonSettings.java | 2 +- .../codegen/PythonShapeCodegenPlugin.java | 72 +++++++++- .../python/codegen/PythonShapeSettings.java | 127 ++++++++++++++++++ 5 files changed, 281 insertions(+), 31 deletions(-) create mode 100644 codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeSettings.java diff --git a/README.md b/README.md index 14905931..34d8aed5 100644 --- a/README.md +++ b/README.md @@ -179,35 +179,81 @@ would now look like: The module with the generated shape classes can be found in `build/smithy/client/python-shape-codegen` after you run `smithy-build`. -Note that a service shape is still required. In this case, it's used for the -purposes of namespacing since all shapes within a service's closure must have a -unique name. The service shape also has the `rename` property to resolve any -conflicts you might encounter. +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 one downside to this is that at time of writing there is no way to add -shapes to a service that aren't connected to it via an operation or error. In -the future, the service shape will have a `shapes` property or some other way of -doing this. For now, it is recommended to just create a dummy operation to add -any shapes needed, like below: +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: -```smithy -$version: "2.0" - -namespace com.example - -service ShapeNamespaceService { - version: "2006-03-01" - operations: [ShapeContainer] -} - -operation ShapeContainer { - input := { - example: ExampleShape +```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" + } + } + } } } +``` -structure ExampleShape { - intMember: Integer +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" + } + } + } + } } ``` diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java index dfe850f4..5e7dfab6 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/DirectedPythonCodegen.java @@ -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; @@ -133,6 +135,8 @@ private void generateSchemas(GenerationContext context, Collection 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(); } @@ -204,6 +208,11 @@ public void generateResource(GenerateResourceDirective 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(), @@ -215,6 +224,14 @@ public void generateStructure(GenerateStructureDirective directive) { directive.context().writerDelegator().useShapeWriter(directive.shape(), writer -> { diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java index fa85d6d4..ad57522b 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java @@ -63,7 +63,7 @@ public record PythonSettings( } } - public PythonSettings(Builder builder) { + private PythonSettings(Builder builder) { this( builder.service, builder.moduleName, diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeCodegenPlugin.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeCodegenPlugin.java index e39368d4..ca1a7462 100644 --- a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeCodegenPlugin.java +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeCodegenPlugin.java @@ -5,12 +5,32 @@ package software.amazon.smithy.python.codegen; +import java.util.Set; import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.build.SmithyBuildPlugin; import software.amazon.smithy.codegen.core.directed.CodegenDirector; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.OperationShape; +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.shapes.ShapeType; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.ErrorTrait; +import software.amazon.smithy.model.traits.InputTrait; +import software.amazon.smithy.model.traits.MixinTrait; +import software.amazon.smithy.model.traits.OutputTrait; +import software.amazon.smithy.model.transform.ModelTransformer; import software.amazon.smithy.python.codegen.integration.PythonIntegration; public final class PythonShapeCodegenPlugin implements SmithyBuildPlugin { + private static final String SYNTHETIC_NAMESPACE = "smithy.synthetic"; + private static final ShapeId SYNTHETIC_SERVICE_ID = ShapeId.fromParts(SYNTHETIC_NAMESPACE, "TypesGenService"); + private static final ShapeId SYNTHETIC_OPERATION_ID = ShapeId.fromParts(SYNTHETIC_NAMESPACE, "TypesGenOperation"); + private static final ShapeId SYNTHETIC_INPUT_ID = ShapeId.fromParts(SYNTHETIC_NAMESPACE, "TypesGenOperationInput"); + private static final Set GENERATED_TYPES = Set.of( + ShapeType.STRUCTURE, ShapeType.UNION, ShapeType.ENUM, ShapeType.INT_ENUM); + @Override public String getName() { return "python-shape-codegen"; @@ -21,17 +41,57 @@ public void execute(PluginContext context) { CodegenDirector runner = new CodegenDirector<>(); - PythonSettings settings = PythonSettings.fromNode(context.getSettings()).toBuilder() - .artifactType(PythonSettings.ArtifactType.SHAPES) - .build(); + var shapeSettings = PythonShapeSettings.fromNode(context.getSettings()); + var service = shapeSettings.service().orElse(SYNTHETIC_SERVICE_ID); + var pythonSettings = shapeSettings.toPythonSettings(service); + + var model = context.getModel(); + if (shapeSettings.service().isEmpty()) { + model = addSyntheticService(model); + } - runner.settings(settings); + runner.settings(pythonSettings); runner.directedCodegen(new DirectedPythonCodegen()); runner.fileManifest(context.getFileManifest()); - runner.service(settings.service()); - runner.model(context.getModel()); + runner.service(pythonSettings.service()); + runner.model(model); runner.integrationClass(PythonIntegration.class); runner.performDefaultCodegenTransforms(); runner.run(); } + + private Model addSyntheticService(Model model) { + StructureShape.Builder inputBuilder = StructureShape.builder() + .id(SYNTHETIC_INPUT_ID) + .addTrait(new InputTrait()); + + OperationShape.Builder operationBuilder = OperationShape.builder() + .id(SYNTHETIC_OPERATION_ID) + .input(SYNTHETIC_INPUT_ID); + + var index = 0; + for (Shape shape : model.toSet()) { + if (!GENERATED_TYPES.contains(shape.getType()) + || shape.hasTrait(InputTrait.class) + || shape.hasTrait(OutputTrait.class) + || shape.hasTrait(MixinTrait.class)) { + continue; + } + + if (shape.hasTrait(ErrorTrait.class)) { + operationBuilder.addError(shape.getId()); + } else { + inputBuilder.addMember(String.valueOf(index), shape.getId()); + index++; + } + } + + ServiceShape service = ServiceShape.builder() + .id(SYNTHETIC_SERVICE_ID) + .addOperation(SYNTHETIC_OPERATION_ID) + .build(); + + ModelTransformer transformer = ModelTransformer.create(); + return transformer.replaceShapes(model, Set.of(inputBuilder.build(), operationBuilder.build(), service)); + } } diff --git a/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeSettings.java b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeSettings.java new file mode 100644 index 00000000..7831c40f --- /dev/null +++ b/codegen/smithy-python-codegen/src/main/java/software/amazon/smithy/python/codegen/PythonShapeSettings.java @@ -0,0 +1,127 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.python.codegen; + +import java.util.Arrays; +import java.util.Optional; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.StringNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.SmithyUnstableApi; +import software.amazon.smithy.utils.ToSmithyBuilder; + +/** + * Settings used by {@link PythonClientCodegenPlugin}. + * + * @param service The id of the service that is being generated. + * @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. + */ +@SmithyUnstableApi +public record PythonShapeSettings( + Optional service, + String moduleName, + String moduleVersion, + String moduleDescription + ) implements ToSmithyBuilder { + + private static final String SERVICE = "service"; + private static final String MODULE_NAME = "module"; + private static final String MODULE_DESCRIPTION = "moduleDescription"; + private static final String MODULE_VERSION = "moduleVersion"; + + private PythonShapeSettings(Builder builder) { + this( + Optional.ofNullable(builder.service), + builder.moduleName, + builder.moduleVersion, + builder.moduleDescription + ); + } + + @Override + public Builder toBuilder() { + Builder builder = builder() + .moduleName(moduleName) + .moduleVersion(moduleVersion) + .moduleDescription(moduleDescription); + service.ifPresent(builder::service); + return builder; + } + + public PythonSettings toPythonSettings(ShapeId service) { + return PythonSettings.builder() + .service(service) + .moduleName(moduleName) + .moduleVersion(moduleVersion) + .moduleDescription(moduleDescription) + .artifactType(PythonSettings.ArtifactType.SHAPES) + .build(); + } + + public PythonSettings toPythonSettings() { + return toPythonSettings(service.get()); + } + + /** + * Create a settings object from a configuration object node. + * + * @param config Config object to load. + * @return Returns the extracted settings. + */ + public static PythonShapeSettings fromNode(ObjectNode config) { + config.warnIfAdditionalProperties(Arrays.asList(SERVICE, MODULE_NAME, MODULE_DESCRIPTION, MODULE_VERSION)); + + String moduleName = config.expectStringMember(MODULE_NAME).getValue(); + Builder builder = builder() + .moduleName(moduleName) + .moduleVersion(config.expectStringMember(MODULE_VERSION).getValue()); + config.getStringMember(SERVICE).map(StringNode::expectShapeId).ifPresent(builder::service); + config.getStringMember(MODULE_DESCRIPTION).map(StringNode::getValue).ifPresent(builder::moduleDescription); + return builder.build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder implements SmithyBuilder { + + private ShapeId service; + private String moduleName; + private String moduleVersion; + private String moduleDescription; + + @Override + public PythonShapeSettings build() { + SmithyBuilder.requiredState("moduleName", moduleName); + SmithyBuilder.requiredState("moduleVersion", moduleVersion); + return new PythonShapeSettings(this); + } + + public Builder service(ShapeId service) { + this.service = service; + return this; + } + + public Builder moduleName(String moduleName) { + this.moduleName = moduleName; + return this; + } + + public Builder moduleVersion(String moduleVersion) { + this.moduleVersion = moduleVersion; + return this; + } + + public Builder moduleDescription(String moduleDescription) { + this.moduleDescription = moduleDescription; + return this; + } + } +}