From e44dd3eaa24181cc868ded11710ba1bf92d5a7f5 Mon Sep 17 00:00:00 2001 From: jonathan343 Date: Wed, 11 Nov 2026 19:01:43 -0500 Subject: [PATCH 1/3] Use service titles in client package descriptions Default client descriptions to the service title, with the service name as a fallback. Share the resolved description between package metadata and the README while preserving explicit overrides and types defaults. --- codegen-output/aws-json-1-0/README.md | 2 +- codegen-output/aws-json-1-0/pyproject.toml | 2 +- codegen-output/aws-json-1-1/README.md | 2 +- codegen-output/aws-json-1-1/pyproject.toml | 2 +- codegen-output/aws-query/README.md | 2 +- codegen-output/aws-query/pyproject.toml | 2 +- codegen-output/ec2-query/README.md | 2 +- codegen-output/ec2-query/pyproject.toml | 2 +- codegen-output/my-handwritten-model/README.md | 2 +- .../my-handwritten-model/pyproject.toml | 2 +- .../non-query-compatible-rpcv2-cbor/README.md | 2 +- .../pyproject.toml | 2 +- .../query-compatible-aws-json-1-0/README.md | 2 +- .../pyproject.toml | 2 +- .../query-compatible-rpcv2-cbor/README.md | 2 +- .../pyproject.toml | 2 +- codegen-output/rest-json-1/README.md | 2 +- codegen-output/rest-json-1/pyproject.toml | 2 +- .../rest-xml-with-namespace/README.md | 2 +- .../rest-xml-with-namespace/pyproject.toml | 2 +- codegen-output/rest-xml/README.md | 2 +- codegen-output/rest-xml/pyproject.toml | 2 +- codegen-output/rpcv2-cbor/README.md | 2 +- codegen-output/rpcv2-cbor/pyproject.toml | 2 +- .../smithy/python/codegen/PythonSettings.java | 40 ++++++++- .../codegen/generators/SetupGenerator.java | 15 ++-- .../generators/SetupGeneratorTest.java | 83 +++++++++++++++++++ 27 files changed, 151 insertions(+), 35 deletions(-) diff --git a/codegen-output/aws-json-1-0/README.md b/codegen-output/aws-json-1-0/README.md index 306705d96..19ea5f3c1 100644 --- a/codegen-output/aws-json-1-0/README.md +++ b/codegen-output/aws-json-1-0/README.md @@ -2,4 +2,4 @@ ## Sample Json 1.0 Protocol Service Client -awsjson10 client +Python client for Sample Json 1.0 Protocol Service diff --git a/codegen-output/aws-json-1-0/pyproject.toml b/codegen-output/aws-json-1-0/pyproject.toml index 8b07cfc29..a9b4f5b94 100644 --- a/codegen-output/aws-json-1-0/pyproject.toml +++ b/codegen-output/aws-json-1-0/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "awsjson10" version = "0.0.1" -description = "awsjson10 client" +description = "Python client for Sample Json 1.0 Protocol Service" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "awsjson10"] diff --git a/codegen-output/aws-json-1-1/README.md b/codegen-output/aws-json-1-1/README.md index a811f30ed..984d1fbde 100644 --- a/codegen-output/aws-json-1-1/README.md +++ b/codegen-output/aws-json-1-1/README.md @@ -2,4 +2,4 @@ ## Sample Json 1.1 Protocol Service Client -awsjson11 client +Python client for Sample Json 1.1 Protocol Service diff --git a/codegen-output/aws-json-1-1/pyproject.toml b/codegen-output/aws-json-1-1/pyproject.toml index 921eedbcd..c6f588ad9 100644 --- a/codegen-output/aws-json-1-1/pyproject.toml +++ b/codegen-output/aws-json-1-1/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "awsjson11" version = "0.0.1" -description = "awsjson11 client" +description = "Python client for Sample Json 1.1 Protocol Service" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "awsjson11"] diff --git a/codegen-output/aws-query/README.md b/codegen-output/aws-query/README.md index 9e5d7edaa..fdd59c80f 100644 --- a/codegen-output/aws-query/README.md +++ b/codegen-output/aws-query/README.md @@ -2,7 +2,7 @@ ## Sample Query Protocol Service Client -awsquery client +Python client for Sample Query Protocol Service ### Documentation diff --git a/codegen-output/aws-query/pyproject.toml b/codegen-output/aws-query/pyproject.toml index 289f33685..eade7c2a8 100644 --- a/codegen-output/aws-query/pyproject.toml +++ b/codegen-output/aws-query/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "awsquery" version = "0.0.1" -description = "awsquery client" +description = "Python client for Sample Query Protocol Service" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "awsquery"] diff --git a/codegen-output/ec2-query/README.md b/codegen-output/ec2-query/README.md index 05c376cf8..c24b4b691 100644 --- a/codegen-output/ec2-query/README.md +++ b/codegen-output/ec2-query/README.md @@ -2,7 +2,7 @@ ## Sample Ec2 Protocol Service Client -ec2query client +Python client for Sample Ec2 Protocol Service ### Documentation diff --git a/codegen-output/ec2-query/pyproject.toml b/codegen-output/ec2-query/pyproject.toml index 1fb477a90..4b6ce3a64 100644 --- a/codegen-output/ec2-query/pyproject.toml +++ b/codegen-output/ec2-query/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "ec2query" version = "0.0.1" -description = "ec2query client" +description = "Python client for Sample Ec2 Protocol Service" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "ec2query"] diff --git a/codegen-output/my-handwritten-model/README.md b/codegen-output/my-handwritten-model/README.md index cc10b198b..a7227557a 100644 --- a/codegen-output/my-handwritten-model/README.md +++ b/codegen-output/my-handwritten-model/README.md @@ -2,4 +2,4 @@ ## My Handwritten Model Client -my_handwritten_model client +Python client for My Handwritten Model diff --git a/codegen-output/my-handwritten-model/pyproject.toml b/codegen-output/my-handwritten-model/pyproject.toml index f1c5fd4fe..e58cf27f1 100644 --- a/codegen-output/my-handwritten-model/pyproject.toml +++ b/codegen-output/my-handwritten-model/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "my_handwritten_model" version = "0.0.1" -description = "my_handwritten_model client" +description = "Python client for My Handwritten Model" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "my_handwritten_model"] diff --git a/codegen-output/non-query-compatible-rpcv2-cbor/README.md b/codegen-output/non-query-compatible-rpcv2-cbor/README.md index 93029c3f8..7b955d6a8 100644 --- a/codegen-output/non-query-compatible-rpcv2-cbor/README.md +++ b/codegen-output/non-query-compatible-rpcv2-cbor/README.md @@ -2,4 +2,4 @@ ## Non Query Compatible RpcV2 Protocol Service Client -nonquerycompatiblerpcv2cbor client +Python client for Non Query Compatible RpcV2 Protocol Service diff --git a/codegen-output/non-query-compatible-rpcv2-cbor/pyproject.toml b/codegen-output/non-query-compatible-rpcv2-cbor/pyproject.toml index 1b286f1cd..5937a8056 100644 --- a/codegen-output/non-query-compatible-rpcv2-cbor/pyproject.toml +++ b/codegen-output/non-query-compatible-rpcv2-cbor/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "nonquerycompatiblerpcv2cbor" version = "0.0.1" -description = "nonquerycompatiblerpcv2cbor client" +description = "Python client for Non Query Compatible RpcV2 Protocol Service" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "nonquerycompatiblerpcv2cbor"] diff --git a/codegen-output/query-compatible-aws-json-1-0/README.md b/codegen-output/query-compatible-aws-json-1-0/README.md index 540af86e5..2f8ba8249 100644 --- a/codegen-output/query-compatible-aws-json-1-0/README.md +++ b/codegen-output/query-compatible-aws-json-1-0/README.md @@ -2,4 +2,4 @@ ## Query Compatible Json 1.0 Protocol Service Client -querycompatibleawsjson10 client +Python client for Query Compatible Json 1.0 Protocol Service diff --git a/codegen-output/query-compatible-aws-json-1-0/pyproject.toml b/codegen-output/query-compatible-aws-json-1-0/pyproject.toml index 1d942bfdc..0fdccd779 100644 --- a/codegen-output/query-compatible-aws-json-1-0/pyproject.toml +++ b/codegen-output/query-compatible-aws-json-1-0/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "querycompatibleawsjson10" version = "0.0.1" -description = "querycompatibleawsjson10 client" +description = "Python client for Query Compatible Json 1.0 Protocol Service" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "querycompatibleawsjson10"] diff --git a/codegen-output/query-compatible-rpcv2-cbor/README.md b/codegen-output/query-compatible-rpcv2-cbor/README.md index e35424ce9..5928f1851 100644 --- a/codegen-output/query-compatible-rpcv2-cbor/README.md +++ b/codegen-output/query-compatible-rpcv2-cbor/README.md @@ -2,4 +2,4 @@ ## Query Compatible RpcV2 Protocol Service Client -querycompatiblerpcv2cbor client +Python client for Query Compatible RpcV2 Protocol Service diff --git a/codegen-output/query-compatible-rpcv2-cbor/pyproject.toml b/codegen-output/query-compatible-rpcv2-cbor/pyproject.toml index b26f4075c..8be942985 100644 --- a/codegen-output/query-compatible-rpcv2-cbor/pyproject.toml +++ b/codegen-output/query-compatible-rpcv2-cbor/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "querycompatiblerpcv2cbor" version = "0.0.1" -description = "querycompatiblerpcv2cbor client" +description = "Python client for Query Compatible RpcV2 Protocol Service" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "querycompatiblerpcv2cbor"] diff --git a/codegen-output/rest-json-1/README.md b/codegen-output/rest-json-1/README.md index 6587b4974..239dd0567 100644 --- a/codegen-output/rest-json-1/README.md +++ b/codegen-output/rest-json-1/README.md @@ -2,4 +2,4 @@ ## Sample Rest Json Protocol Service Client -restjson client +Python client for Sample Rest Json Protocol Service diff --git a/codegen-output/rest-json-1/pyproject.toml b/codegen-output/rest-json-1/pyproject.toml index a3c6b76b4..28eae9ce9 100644 --- a/codegen-output/rest-json-1/pyproject.toml +++ b/codegen-output/rest-json-1/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "restjson" version = "0.0.1" -description = "restjson client" +description = "Python client for Sample Rest Json Protocol Service" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "restjson"] diff --git a/codegen-output/rest-xml-with-namespace/README.md b/codegen-output/rest-xml-with-namespace/README.md index 1f9b69a3d..9c005dcfc 100644 --- a/codegen-output/rest-xml-with-namespace/README.md +++ b/codegen-output/rest-xml-with-namespace/README.md @@ -2,7 +2,7 @@ ## Sample Rest Xml Protocol Service With Namespace Client -restxmlwithnamespace client +Python client for Sample Rest Xml Protocol Service With Namespace ### Documentation diff --git a/codegen-output/rest-xml-with-namespace/pyproject.toml b/codegen-output/rest-xml-with-namespace/pyproject.toml index ef8901cdc..2a0359aa8 100644 --- a/codegen-output/rest-xml-with-namespace/pyproject.toml +++ b/codegen-output/rest-xml-with-namespace/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "restxmlwithnamespace" version = "0.0.1" -description = "restxmlwithnamespace client" +description = "Python client for Sample Rest Xml Protocol Service With Namespace" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "restxmlwithnamespace"] diff --git a/codegen-output/rest-xml/README.md b/codegen-output/rest-xml/README.md index e1cb86613..365c8f067 100644 --- a/codegen-output/rest-xml/README.md +++ b/codegen-output/rest-xml/README.md @@ -2,7 +2,7 @@ ## Sample Rest Xml Protocol Service Client -restxml client +Python client for Sample Rest Xml Protocol Service ### Documentation diff --git a/codegen-output/rest-xml/pyproject.toml b/codegen-output/rest-xml/pyproject.toml index e0dc82aac..6d34622cd 100644 --- a/codegen-output/rest-xml/pyproject.toml +++ b/codegen-output/rest-xml/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "restxml" version = "0.0.1" -description = "restxml client" +description = "Python client for Sample Rest Xml Protocol Service" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "restxml"] diff --git a/codegen-output/rpcv2-cbor/README.md b/codegen-output/rpcv2-cbor/README.md index 64b17629a..7beeef19f 100644 --- a/codegen-output/rpcv2-cbor/README.md +++ b/codegen-output/rpcv2-cbor/README.md @@ -2,4 +2,4 @@ ## RpcV2 Protocol Service Client -rpcv2cbor client +Python client for RpcV2 Protocol Service diff --git a/codegen-output/rpcv2-cbor/pyproject.toml b/codegen-output/rpcv2-cbor/pyproject.toml index 91fd95a8d..1c45fc0bd 100644 --- a/codegen-output/rpcv2-cbor/pyproject.toml +++ b/codegen-output/rpcv2-cbor/pyproject.toml @@ -3,7 +3,7 @@ [project] name = "rpcv2cbor" version = "0.0.1" -description = "rpcv2cbor client" +description = "Python client for RpcV2 Protocol Service" readme = "README.md" requires-python = ">=3.12" keywords = ["smithy", "rpcv2cbor"] diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java index 5c9cdbbb0..f86384718 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java @@ -13,6 +13,8 @@ import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.StringTrait; +import software.amazon.smithy.model.traits.TitleTrait; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; import software.amazon.smithy.utils.StringUtils; @@ -25,6 +27,8 @@ * @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. + * Empty for clients without a configured description; use {@link #moduleDescription(Model)} + * to resolve the effective description. * @param artifactType The type of artifact being generated (e.g. client/shapes/server) */ @SmithyUnstableApi @@ -56,12 +60,42 @@ private PythonSettings(Builder builder) { builder.service, builder.moduleName, builder.moduleVersion, - StringUtils.isBlank(builder.moduleDescription) - ? builder.moduleName + " " + builder.artifactType.name().toLowerCase(Locale.ENGLISH) - : builder.moduleDescription, + configuredDescription(builder), builder.artifactType); } + private static String configuredDescription(Builder builder) { + if (!StringUtils.isBlank(builder.moduleDescription)) { + return builder.moduleDescription; + } + // Client defaults depend on the model, so they're resolved by moduleDescription(Model). + if (builder.artifactType == ArtifactType.CLIENT) { + return ""; + } + return builder.moduleName + " " + builder.artifactType.name().toLowerCase(Locale.ENGLISH); + } + + /** + * Resolves the description of the generated package. + * + *

A configured description is returned as-is. Otherwise, the default is derived + * from the service's {@code smithy.api#title} trait, falling back to the service + * shape's name. + * + * @param model Model containing the service shape. + * @return Returns the package description. + */ + public String moduleDescription(Model model) { + if (!StringUtils.isBlank(moduleDescription)) { + return moduleDescription; + } + var service = service(model); + var name = service.getTrait(TitleTrait.class) + .map(StringTrait::getValue) + .orElse(service.getId().getName()); + return "Python client for " + name; + } + /** * Gets the corresponding {@link ServiceShape} from a model. * diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java index 58d49d35e..7e0185164 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java @@ -41,8 +41,9 @@ public static void generateSetup( GenerationContext context ) { var dependencies = gatherDependencies(context.writerDelegator().getDependencies().stream()); - writePyproject(settings, context.writerDelegator(), dependencies); - writeReadme(settings, context); + var description = settings.moduleDescription(context.model()); + writePyproject(settings, description, context.writerDelegator(), dependencies); + writeReadme(settings, description, context); } /** @@ -106,6 +107,7 @@ private static Map> gatherDependencies( */ private static void writePyproject( PythonSettings settings, + String description, WriterDelegator writers, Map> dependencies ) { @@ -139,7 +141,7 @@ private static void writePyproject( "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.15" ] - """, settings.moduleName(), settings.moduleVersion(), settings.moduleDescription()); + """, settings.moduleName(), settings.moduleVersion(), description); Optional.ofNullable(dependencies.get(PythonDependency.Type.DEPENDENCY.getType())).ifPresent(deps -> { writer.openBlock("dependencies = [", "]\n", () -> writeDependencyList(writer, deps.values())); @@ -283,6 +285,7 @@ private static List getOptionalDependencies(SymbolDependency dependency) private static void writeReadme( PythonSettings settings, + String description, GenerationContext context ) { var service = context.model().expectShape(settings.service()); @@ -292,17 +295,13 @@ private static void writeReadme( .map(StringTrait::getValue) .orElse(StringUtils.capitalize(settings.moduleName())); - var description = StringUtils.isBlank(settings.moduleDescription()) - ? "Generated service client for " + title - : StringUtils.wrap(settings.moduleDescription(), 80); - context.writerDelegator().useFileWriter("README.md", writer -> { writer.pushState(new ReadmeSection()); writer.write(""" ## $L Client $L - """, title, description); + """, title, StringUtils.wrap(description, 80)); service.getTrait(DocumentationTrait.class).map(StringTrait::getValue).ifPresent(documentation -> { writer.write(""" diff --git a/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java b/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java index 91c778902..c7465bae5 100644 --- a/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java +++ b/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java @@ -4,19 +4,26 @@ */ package software.amazon.smithy.python.codegen.generators; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import software.amazon.smithy.build.MockManifest; import software.amazon.smithy.codegen.core.SymbolDependency; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.traits.DocumentationTrait; +import software.amazon.smithy.model.traits.TitleTrait; import software.amazon.smithy.python.codegen.GenerationContext; import software.amazon.smithy.python.codegen.PythonSettings; +import software.amazon.smithy.python.codegen.PythonSettings.ArtifactType; import software.amazon.smithy.python.codegen.PythonSymbolProvider; import software.amazon.smithy.python.codegen.SmithyPythonDependency; import software.amazon.smithy.python.codegen.writer.PythonDelegator; @@ -70,4 +77,80 @@ public void readmeKeepsRawTripleQuotesInCodeSpans() { assertTrue(readme.contains("Use `\"\"\"` here."), readme); } + + @Test + public void defaultDescriptionUsesServiceTitle() { + // Without an explicit moduleDescription, the package description should be + // derived from the standard smithy.api#title trait and shared by both files. + assertDescription( + service("smithy.example#DynamoDB", "Amazon DynamoDB"), + null, + ArtifactType.CLIENT, + "Python client for Amazon DynamoDB"); + } + + static Stream packageDescriptionCases() { + var titled = service("smithy.example#Weather", "Example Weather Service"); + var untitled = service("smithy.example#Weather", null); + return Stream.of( + Arguments.of("explicit", titled, "Custom description", ArtifactType.CLIENT, "Custom description"), + Arguments.of("blank", titled, " ", ArtifactType.CLIENT, "Python client for Example Weather Service"), + Arguments.of("titled", titled, null, ArtifactType.CLIENT, "Python client for Example Weather Service"), + Arguments.of("untitled", untitled, null, ArtifactType.CLIENT, "Python client for Weather"), + Arguments.of("types default", titled, null, ArtifactType.TYPES, "test_client types"), + Arguments.of("types explicit", titled, "Custom types", ArtifactType.TYPES, "Custom types")); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("packageDescriptionCases") + public void resolvesPackageDescription( + String name, + ServiceShape service, + String moduleDescription, + ArtifactType artifactType, + String expected + ) { + assertDescription(service, moduleDescription, artifactType, expected); + } + + private static ServiceShape service(String id, String title) { + var builder = ServiceShape.builder().id(id).version("2024-01-01"); + if (title != null) { + builder.addTrait(new TitleTrait(title)); + } + return builder.build(); + } + + private static void assertDescription( + ServiceShape service, + String moduleDescription, + ArtifactType artifactType, + String expected + ) { + var model = Model.builder().addShape(service).build(); + var settings = PythonSettings.builder() + .service(service.getId()) + .moduleName("test_client") + .moduleVersion("0.0.1") + .moduleDescription(moduleDescription) + .artifactType(artifactType) + .build(); + var manifest = new MockManifest(); + var delegator = new PythonDelegator(manifest, new PythonSymbolProvider(model, settings), settings); + var context = GenerationContext.builder() + .model(model) + .settings(settings) + .fileManifest(manifest) + .writerDelegator(delegator) + .build(); + + SetupGenerator.generateSetup(settings, context); + delegator.flushWriters(); + String pyproject = manifest.expectFileString("pyproject.toml"); + String readme = manifest.expectFileString("README.md"); + + assertAll( + () -> assertTrue(pyproject.contains("description = \"" + expected + "\"\n"), pyproject), + () -> assertTrue(readme.contains("\n" + expected + "\n"), readme)); + } } From 9643d78fab9ef126448819f6aa1f94c347cf6085 Mon Sep 17 00:00:00 2001 From: jonathan343 Date: Wed, 11 Nov 2026 19:26:37 -0500 Subject: [PATCH 2/3] Keep README descriptions unchanged Limit title-based descriptions to pyproject.toml metadata. Preserve the existing README output for a separate documentation overhaul. --- codegen-output/aws-json-1-0/README.md | 2 +- codegen-output/aws-json-1-1/README.md | 2 +- codegen-output/aws-query/README.md | 2 +- codegen-output/ec2-query/README.md | 2 +- codegen-output/my-handwritten-model/README.md | 2 +- .../non-query-compatible-rpcv2-cbor/README.md | 2 +- .../query-compatible-aws-json-1-0/README.md | 2 +- .../query-compatible-rpcv2-cbor/README.md | 2 +- codegen-output/rest-json-1/README.md | 2 +- .../rest-xml-with-namespace/README.md | 2 +- codegen-output/rest-xml/README.md | 2 +- codegen-output/rpcv2-cbor/README.md | 2 +- .../smithy/python/codegen/PythonSettings.java | 35 +++++++------ .../codegen/generators/SetupGenerator.java | 9 ++-- .../generators/SetupGeneratorTest.java | 49 +++++++++++++------ 15 files changed, 70 insertions(+), 47 deletions(-) diff --git a/codegen-output/aws-json-1-0/README.md b/codegen-output/aws-json-1-0/README.md index 19ea5f3c1..306705d96 100644 --- a/codegen-output/aws-json-1-0/README.md +++ b/codegen-output/aws-json-1-0/README.md @@ -2,4 +2,4 @@ ## Sample Json 1.0 Protocol Service Client -Python client for Sample Json 1.0 Protocol Service +awsjson10 client diff --git a/codegen-output/aws-json-1-1/README.md b/codegen-output/aws-json-1-1/README.md index 984d1fbde..a811f30ed 100644 --- a/codegen-output/aws-json-1-1/README.md +++ b/codegen-output/aws-json-1-1/README.md @@ -2,4 +2,4 @@ ## Sample Json 1.1 Protocol Service Client -Python client for Sample Json 1.1 Protocol Service +awsjson11 client diff --git a/codegen-output/aws-query/README.md b/codegen-output/aws-query/README.md index fdd59c80f..9e5d7edaa 100644 --- a/codegen-output/aws-query/README.md +++ b/codegen-output/aws-query/README.md @@ -2,7 +2,7 @@ ## Sample Query Protocol Service Client -Python client for Sample Query Protocol Service +awsquery client ### Documentation diff --git a/codegen-output/ec2-query/README.md b/codegen-output/ec2-query/README.md index c24b4b691..05c376cf8 100644 --- a/codegen-output/ec2-query/README.md +++ b/codegen-output/ec2-query/README.md @@ -2,7 +2,7 @@ ## Sample Ec2 Protocol Service Client -Python client for Sample Ec2 Protocol Service +ec2query client ### Documentation diff --git a/codegen-output/my-handwritten-model/README.md b/codegen-output/my-handwritten-model/README.md index a7227557a..cc10b198b 100644 --- a/codegen-output/my-handwritten-model/README.md +++ b/codegen-output/my-handwritten-model/README.md @@ -2,4 +2,4 @@ ## My Handwritten Model Client -Python client for My Handwritten Model +my_handwritten_model client diff --git a/codegen-output/non-query-compatible-rpcv2-cbor/README.md b/codegen-output/non-query-compatible-rpcv2-cbor/README.md index 7b955d6a8..93029c3f8 100644 --- a/codegen-output/non-query-compatible-rpcv2-cbor/README.md +++ b/codegen-output/non-query-compatible-rpcv2-cbor/README.md @@ -2,4 +2,4 @@ ## Non Query Compatible RpcV2 Protocol Service Client -Python client for Non Query Compatible RpcV2 Protocol Service +nonquerycompatiblerpcv2cbor client diff --git a/codegen-output/query-compatible-aws-json-1-0/README.md b/codegen-output/query-compatible-aws-json-1-0/README.md index 2f8ba8249..540af86e5 100644 --- a/codegen-output/query-compatible-aws-json-1-0/README.md +++ b/codegen-output/query-compatible-aws-json-1-0/README.md @@ -2,4 +2,4 @@ ## Query Compatible Json 1.0 Protocol Service Client -Python client for Query Compatible Json 1.0 Protocol Service +querycompatibleawsjson10 client diff --git a/codegen-output/query-compatible-rpcv2-cbor/README.md b/codegen-output/query-compatible-rpcv2-cbor/README.md index 5928f1851..e35424ce9 100644 --- a/codegen-output/query-compatible-rpcv2-cbor/README.md +++ b/codegen-output/query-compatible-rpcv2-cbor/README.md @@ -2,4 +2,4 @@ ## Query Compatible RpcV2 Protocol Service Client -Python client for Query Compatible RpcV2 Protocol Service +querycompatiblerpcv2cbor client diff --git a/codegen-output/rest-json-1/README.md b/codegen-output/rest-json-1/README.md index 239dd0567..6587b4974 100644 --- a/codegen-output/rest-json-1/README.md +++ b/codegen-output/rest-json-1/README.md @@ -2,4 +2,4 @@ ## Sample Rest Json Protocol Service Client -Python client for Sample Rest Json Protocol Service +restjson client diff --git a/codegen-output/rest-xml-with-namespace/README.md b/codegen-output/rest-xml-with-namespace/README.md index 9c005dcfc..1f9b69a3d 100644 --- a/codegen-output/rest-xml-with-namespace/README.md +++ b/codegen-output/rest-xml-with-namespace/README.md @@ -2,7 +2,7 @@ ## Sample Rest Xml Protocol Service With Namespace Client -Python client for Sample Rest Xml Protocol Service With Namespace +restxmlwithnamespace client ### Documentation diff --git a/codegen-output/rest-xml/README.md b/codegen-output/rest-xml/README.md index 365c8f067..e1cb86613 100644 --- a/codegen-output/rest-xml/README.md +++ b/codegen-output/rest-xml/README.md @@ -2,7 +2,7 @@ ## Sample Rest Xml Protocol Service Client -Python client for Sample Rest Xml Protocol Service +restxml client ### Documentation diff --git a/codegen-output/rpcv2-cbor/README.md b/codegen-output/rpcv2-cbor/README.md index 7beeef19f..64b17629a 100644 --- a/codegen-output/rpcv2-cbor/README.md +++ b/codegen-output/rpcv2-cbor/README.md @@ -2,4 +2,4 @@ ## RpcV2 Protocol Service Client -Python client for RpcV2 Protocol Service +rpcv2cbor client diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java index f86384718..a15157619 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java @@ -27,8 +27,6 @@ * @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. - * Empty for clients without a configured description; use {@link #moduleDescription(Model)} - * to resolve the effective description. * @param artifactType The type of artifact being generated (e.g. client/shapes/server) */ @SmithyUnstableApi @@ -60,34 +58,35 @@ private PythonSettings(Builder builder) { builder.service, builder.moduleName, builder.moduleVersion, - configuredDescription(builder), + StringUtils.isBlank(builder.moduleDescription) ? "" : builder.moduleDescription, builder.artifactType); } - private static String configuredDescription(Builder builder) { - if (!StringUtils.isBlank(builder.moduleDescription)) { - return builder.moduleDescription; - } - // Client defaults depend on the model, so they're resolved by moduleDescription(Model). - if (builder.artifactType == ArtifactType.CLIENT) { - return ""; - } - return builder.moduleName + " " + builder.artifactType.name().toLowerCase(Locale.ENGLISH); + /** + * Gets the configured module description, or {@code " "} if none was configured. + * + * @return Returns the module description. + */ + @Override + public String moduleDescription() { + return StringUtils.isBlank(moduleDescription) + ? moduleName + " " + artifactType.name().toLowerCase(Locale.ENGLISH) + : moduleDescription; } /** - * Resolves the description of the generated package. + * Resolves the description used in the generated package metadata. * - *

A configured description is returned as-is. Otherwise, the default is derived - * from the service's {@code smithy.api#title} trait, falling back to the service - * shape's name. + *

A configured description is returned as-is. Otherwise, client descriptions are + * derived from the service's {@code smithy.api#title} trait, falling back to the + * service shape's name. * * @param model Model containing the service shape. * @return Returns the package description. */ public String moduleDescription(Model model) { - if (!StringUtils.isBlank(moduleDescription)) { - return moduleDescription; + if (!StringUtils.isBlank(moduleDescription) || artifactType != ArtifactType.CLIENT) { + return moduleDescription(); } var service = service(model); var name = service.getTrait(TitleTrait.class) diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java index 7e0185164..8d6106a5f 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java @@ -43,7 +43,7 @@ public static void generateSetup( var dependencies = gatherDependencies(context.writerDelegator().getDependencies().stream()); var description = settings.moduleDescription(context.model()); writePyproject(settings, description, context.writerDelegator(), dependencies); - writeReadme(settings, description, context); + writeReadme(settings, context); } /** @@ -285,7 +285,6 @@ private static List getOptionalDependencies(SymbolDependency dependency) private static void writeReadme( PythonSettings settings, - String description, GenerationContext context ) { var service = context.model().expectShape(settings.service()); @@ -295,13 +294,17 @@ private static void writeReadme( .map(StringTrait::getValue) .orElse(StringUtils.capitalize(settings.moduleName())); + var description = StringUtils.isBlank(settings.moduleDescription()) + ? "Generated service client for " + title + : StringUtils.wrap(settings.moduleDescription(), 80); + context.writerDelegator().useFileWriter("README.md", writer -> { writer.pushState(new ReadmeSection()); writer.write(""" ## $L Client $L - """, title, StringUtils.wrap(description, 80)); + """, title, description); service.getTrait(DocumentationTrait.class).map(StringTrait::getValue).ifPresent(documentation -> { writer.write(""" diff --git a/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java b/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java index c7465bae5..837f4971c 100644 --- a/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java +++ b/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java @@ -80,25 +80,41 @@ public void readmeKeepsRawTripleQuotesInCodeSpans() { @Test public void defaultDescriptionUsesServiceTitle() { - // Without an explicit moduleDescription, the package description should be - // derived from the standard smithy.api#title trait and shared by both files. + // Without an explicit moduleDescription, the pyproject.toml description should be + // derived from the standard smithy.api#title trait, while the README keeps its + // legacy " client" description. assertDescription( service("smithy.example#DynamoDB", "Amazon DynamoDB"), null, ArtifactType.CLIENT, - "Python client for Amazon DynamoDB"); + "Python client for Amazon DynamoDB", + "test_client client"); } static Stream packageDescriptionCases() { var titled = service("smithy.example#Weather", "Example Weather Service"); var untitled = service("smithy.example#Weather", null); + var client = ArtifactType.CLIENT; + var types = ArtifactType.TYPES; + var titledDefault = "Python client for Example Weather Service"; return Stream.of( - Arguments.of("explicit", titled, "Custom description", ArtifactType.CLIENT, "Custom description"), - Arguments.of("blank", titled, " ", ArtifactType.CLIENT, "Python client for Example Weather Service"), - Arguments.of("titled", titled, null, ArtifactType.CLIENT, "Python client for Example Weather Service"), - Arguments.of("untitled", untitled, null, ArtifactType.CLIENT, "Python client for Weather"), - Arguments.of("types default", titled, null, ArtifactType.TYPES, "test_client types"), - Arguments.of("types explicit", titled, "Custom types", ArtifactType.TYPES, "Custom types")); + Arguments.of("explicit", + titled, + "Custom description", + client, + "Custom description", + "Custom description"), + Arguments.of("explicit old default", + titled, + "test_client client", + client, + "test_client client", + "test_client client"), + Arguments.of("blank", titled, " ", client, titledDefault, "test_client client"), + Arguments.of("titled", titled, null, client, titledDefault, "test_client client"), + Arguments.of("untitled", untitled, null, client, "Python client for Weather", "test_client client"), + Arguments.of("types default", titled, null, types, "test_client types", "test_client types"), + Arguments.of("types explicit", titled, "Custom types", types, "Custom types", "Custom types")); } @ParameterizedTest(name = "{0}") @@ -108,9 +124,10 @@ public void resolvesPackageDescription( ServiceShape service, String moduleDescription, ArtifactType artifactType, - String expected + String expectedPyproject, + String expectedLegacy ) { - assertDescription(service, moduleDescription, artifactType, expected); + assertDescription(service, moduleDescription, artifactType, expectedPyproject, expectedLegacy); } private static ServiceShape service(String id, String title) { @@ -125,7 +142,8 @@ private static void assertDescription( ServiceShape service, String moduleDescription, ArtifactType artifactType, - String expected + String expectedPyproject, + String expectedLegacy ) { var model = Model.builder().addShape(service).build(); var settings = PythonSettings.builder() @@ -149,8 +167,11 @@ private static void assertDescription( String pyproject = manifest.expectFileString("pyproject.toml"); String readme = manifest.expectFileString("README.md"); + // pyproject.toml uses the model-aware description, while the README and the + // no-argument accessor keep their legacy " " default. assertAll( - () -> assertTrue(pyproject.contains("description = \"" + expected + "\"\n"), pyproject), - () -> assertTrue(readme.contains("\n" + expected + "\n"), readme)); + () -> assertTrue(pyproject.contains("description = \"" + expectedPyproject + "\"\n"), pyproject), + () -> assertTrue(readme.contains("\n" + expectedLegacy + "\n"), readme), + () -> assertEquals(expectedLegacy, settings.moduleDescription())); } } From 1b266753b8f7495019da5f01ab7f35ea305e5f07 Mon Sep 17 00:00:00 2001 From: jonathan343 Date: Wed, 11 Nov 2026 21:47:23 -0500 Subject: [PATCH 3/3] Unify client and types package descriptions Resolve descriptions during setup generation, honoring overrides before service titles and names. Use the module name for service-less types packages, and share the result between metadata and README output. --- codegen-output/aws-json-1-0/README.md | 2 +- codegen-output/aws-json-1-1/README.md | 2 +- codegen-output/aws-query/README.md | 2 +- codegen-output/ec2-query/README.md | 2 +- codegen-output/my-handwritten-model/README.md | 2 +- .../non-query-compatible-rpcv2-cbor/README.md | 2 +- .../query-compatible-aws-json-1-0/README.md | 2 +- .../query-compatible-rpcv2-cbor/README.md | 2 +- codegen-output/rest-json-1/README.md | 2 +- .../rest-xml-with-namespace/README.md | 2 +- codegen-output/rest-xml/README.md | 2 +- codegen-output/rpcv2-cbor/README.md | 2 +- .../smithy/python/codegen/PythonSettings.java | 40 +-------- .../codegen/generators/SetupGenerator.java | 26 ++++-- .../generators/SetupGeneratorTest.java | 86 +++++++++---------- .../codegen/types/PythonTypeCodegenTest.java | 16 +++- .../types/PythonTypeCodegenSettings.java | 9 +- 17 files changed, 95 insertions(+), 106 deletions(-) diff --git a/codegen-output/aws-json-1-0/README.md b/codegen-output/aws-json-1-0/README.md index 306705d96..19ea5f3c1 100644 --- a/codegen-output/aws-json-1-0/README.md +++ b/codegen-output/aws-json-1-0/README.md @@ -2,4 +2,4 @@ ## Sample Json 1.0 Protocol Service Client -awsjson10 client +Python client for Sample Json 1.0 Protocol Service diff --git a/codegen-output/aws-json-1-1/README.md b/codegen-output/aws-json-1-1/README.md index a811f30ed..984d1fbde 100644 --- a/codegen-output/aws-json-1-1/README.md +++ b/codegen-output/aws-json-1-1/README.md @@ -2,4 +2,4 @@ ## Sample Json 1.1 Protocol Service Client -awsjson11 client +Python client for Sample Json 1.1 Protocol Service diff --git a/codegen-output/aws-query/README.md b/codegen-output/aws-query/README.md index 9e5d7edaa..fdd59c80f 100644 --- a/codegen-output/aws-query/README.md +++ b/codegen-output/aws-query/README.md @@ -2,7 +2,7 @@ ## Sample Query Protocol Service Client -awsquery client +Python client for Sample Query Protocol Service ### Documentation diff --git a/codegen-output/ec2-query/README.md b/codegen-output/ec2-query/README.md index 05c376cf8..c24b4b691 100644 --- a/codegen-output/ec2-query/README.md +++ b/codegen-output/ec2-query/README.md @@ -2,7 +2,7 @@ ## Sample Ec2 Protocol Service Client -ec2query client +Python client for Sample Ec2 Protocol Service ### Documentation diff --git a/codegen-output/my-handwritten-model/README.md b/codegen-output/my-handwritten-model/README.md index cc10b198b..a7227557a 100644 --- a/codegen-output/my-handwritten-model/README.md +++ b/codegen-output/my-handwritten-model/README.md @@ -2,4 +2,4 @@ ## My Handwritten Model Client -my_handwritten_model client +Python client for My Handwritten Model diff --git a/codegen-output/non-query-compatible-rpcv2-cbor/README.md b/codegen-output/non-query-compatible-rpcv2-cbor/README.md index 93029c3f8..7b955d6a8 100644 --- a/codegen-output/non-query-compatible-rpcv2-cbor/README.md +++ b/codegen-output/non-query-compatible-rpcv2-cbor/README.md @@ -2,4 +2,4 @@ ## Non Query Compatible RpcV2 Protocol Service Client -nonquerycompatiblerpcv2cbor client +Python client for Non Query Compatible RpcV2 Protocol Service diff --git a/codegen-output/query-compatible-aws-json-1-0/README.md b/codegen-output/query-compatible-aws-json-1-0/README.md index 540af86e5..2f8ba8249 100644 --- a/codegen-output/query-compatible-aws-json-1-0/README.md +++ b/codegen-output/query-compatible-aws-json-1-0/README.md @@ -2,4 +2,4 @@ ## Query Compatible Json 1.0 Protocol Service Client -querycompatibleawsjson10 client +Python client for Query Compatible Json 1.0 Protocol Service diff --git a/codegen-output/query-compatible-rpcv2-cbor/README.md b/codegen-output/query-compatible-rpcv2-cbor/README.md index e35424ce9..5928f1851 100644 --- a/codegen-output/query-compatible-rpcv2-cbor/README.md +++ b/codegen-output/query-compatible-rpcv2-cbor/README.md @@ -2,4 +2,4 @@ ## Query Compatible RpcV2 Protocol Service Client -querycompatiblerpcv2cbor client +Python client for Query Compatible RpcV2 Protocol Service diff --git a/codegen-output/rest-json-1/README.md b/codegen-output/rest-json-1/README.md index 6587b4974..239dd0567 100644 --- a/codegen-output/rest-json-1/README.md +++ b/codegen-output/rest-json-1/README.md @@ -2,4 +2,4 @@ ## Sample Rest Json Protocol Service Client -restjson client +Python client for Sample Rest Json Protocol Service diff --git a/codegen-output/rest-xml-with-namespace/README.md b/codegen-output/rest-xml-with-namespace/README.md index 1f9b69a3d..9c005dcfc 100644 --- a/codegen-output/rest-xml-with-namespace/README.md +++ b/codegen-output/rest-xml-with-namespace/README.md @@ -2,7 +2,7 @@ ## Sample Rest Xml Protocol Service With Namespace Client -restxmlwithnamespace client +Python client for Sample Rest Xml Protocol Service With Namespace ### Documentation diff --git a/codegen-output/rest-xml/README.md b/codegen-output/rest-xml/README.md index e1cb86613..365c8f067 100644 --- a/codegen-output/rest-xml/README.md +++ b/codegen-output/rest-xml/README.md @@ -2,7 +2,7 @@ ## Sample Rest Xml Protocol Service Client -restxml client +Python client for Sample Rest Xml Protocol Service ### Documentation diff --git a/codegen-output/rpcv2-cbor/README.md b/codegen-output/rpcv2-cbor/README.md index 64b17629a..7beeef19f 100644 --- a/codegen-output/rpcv2-cbor/README.md +++ b/codegen-output/rpcv2-cbor/README.md @@ -2,4 +2,4 @@ ## RpcV2 Protocol Service Client -rpcv2cbor client +Python client for RpcV2 Protocol Service diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java index a15157619..0671e3185 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/PythonSettings.java @@ -5,7 +5,6 @@ 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; @@ -13,8 +12,6 @@ import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.traits.StringTrait; -import software.amazon.smithy.model.traits.TitleTrait; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; import software.amazon.smithy.utils.StringUtils; @@ -58,43 +55,11 @@ private PythonSettings(Builder builder) { builder.service, builder.moduleName, builder.moduleVersion, + // Unset descriptions stay empty; SetupGenerator resolves them from the service. StringUtils.isBlank(builder.moduleDescription) ? "" : builder.moduleDescription, builder.artifactType); } - /** - * Gets the configured module description, or {@code " "} if none was configured. - * - * @return Returns the module description. - */ - @Override - public String moduleDescription() { - return StringUtils.isBlank(moduleDescription) - ? moduleName + " " + artifactType.name().toLowerCase(Locale.ENGLISH) - : moduleDescription; - } - - /** - * Resolves the description used in the generated package metadata. - * - *

A configured description is returned as-is. Otherwise, client descriptions are - * derived from the service's {@code smithy.api#title} trait, falling back to the - * service shape's name. - * - * @param model Model containing the service shape. - * @return Returns the package description. - */ - public String moduleDescription(Model model) { - if (!StringUtils.isBlank(moduleDescription) || artifactType != ArtifactType.CLIENT) { - return moduleDescription(); - } - var service = service(model); - var name = service.getTrait(TitleTrait.class) - .map(StringTrait::getValue) - .orElse(service.getId().getName()); - return "Python client for " + name; - } - /** * Gets the corresponding {@link ServiceShape} from a model. * @@ -120,10 +85,9 @@ public ServiceShape service(Model model) { public static PythonSettings fromNode(ObjectNode config) { config.warnIfAdditionalProperties(Arrays.asList(SERVICE, MODULE_NAME, MODULE_DESCRIPTION, MODULE_VERSION)); - String moduleName = config.expectStringMember(MODULE_NAME).getValue(); Builder builder = builder() .service(config.expectStringMember(SERVICE).expectShapeId()) - .moduleName(moduleName) + .moduleName(config.expectStringMember(MODULE_NAME).getValue()) .moduleVersion(config.expectStringMember(MODULE_VERSION).getValue()); config.getStringMember(MODULE_DESCRIPTION).map(StringNode::getValue).ifPresent(builder::moduleDescription); return builder.build(); diff --git a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java index 8d6106a5f..dda9482e2 100644 --- a/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java +++ b/codegen/core/src/main/java/software/amazon/smithy/python/codegen/generators/SetupGenerator.java @@ -41,9 +41,24 @@ public static void generateSetup( GenerationContext context ) { var dependencies = gatherDependencies(context.writerDelegator().getDependencies().stream()); - var description = settings.moduleDescription(context.model()); + var description = resolveDescription(settings, context); writePyproject(settings, description, context.writerDelegator(), dependencies); - writeReadme(settings, context); + writeReadme(settings, description, context); + } + + /** + * Use the configured description if set, otherwise describe the client or types + * package by the service's {@code smithy.api#title} trait or else the service shape's name. + */ + private static String resolveDescription(PythonSettings settings, GenerationContext context) { + if (!StringUtils.isBlank(settings.moduleDescription())) { + return settings.moduleDescription(); + } + var service = context.model().expectShape(settings.service()); + var artifact = settings.artifactType() == PythonSettings.ArtifactType.TYPES ? "types" : "client"; + return "Python " + artifact + " for " + service.getTrait(TitleTrait.class) + .map(StringTrait::getValue) + .orElse(service.getId().getName()); } /** @@ -285,6 +300,7 @@ private static List getOptionalDependencies(SymbolDependency dependency) private static void writeReadme( PythonSettings settings, + String description, GenerationContext context ) { var service = context.model().expectShape(settings.service()); @@ -294,17 +310,13 @@ private static void writeReadme( .map(StringTrait::getValue) .orElse(StringUtils.capitalize(settings.moduleName())); - var description = StringUtils.isBlank(settings.moduleDescription()) - ? "Generated service client for " + title - : StringUtils.wrap(settings.moduleDescription(), 80); - context.writerDelegator().useFileWriter("README.md", writer -> { writer.pushState(new ReadmeSection()); writer.write(""" ## $L Client $L - """, title, description); + """, title, StringUtils.wrap(description, 80)); service.getTrait(DocumentationTrait.class).map(StringTrait::getValue).ifPresent(documentation -> { writer.write(""" diff --git a/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java b/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java index 837f4971c..097191436 100644 --- a/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java +++ b/codegen/core/src/test/java/software/amazon/smithy/python/codegen/generators/SetupGeneratorTest.java @@ -18,6 +18,7 @@ import software.amazon.smithy.build.MockManifest; import software.amazon.smithy.codegen.core.SymbolDependency; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.traits.DocumentationTrait; import software.amazon.smithy.model.traits.TitleTrait; @@ -78,43 +79,24 @@ public void readmeKeepsRawTripleQuotesInCodeSpans() { assertTrue(readme.contains("Use `\"\"\"` here."), readme); } - @Test - public void defaultDescriptionUsesServiceTitle() { - // Without an explicit moduleDescription, the pyproject.toml description should be - // derived from the standard smithy.api#title trait, while the README keeps its - // legacy " client" description. - assertDescription( - service("smithy.example#DynamoDB", "Amazon DynamoDB"), - null, - ArtifactType.CLIENT, - "Python client for Amazon DynamoDB", - "test_client client"); - } - static Stream packageDescriptionCases() { var titled = service("smithy.example#Weather", "Example Weather Service"); var untitled = service("smithy.example#Weather", null); var client = ArtifactType.CLIENT; var types = ArtifactType.TYPES; var titledDefault = "Python client for Example Weather Service"; + var titledTypesDefault = "Python types for Example Weather Service"; return Stream.of( - Arguments.of("explicit", - titled, - "Custom description", - client, - "Custom description", - "Custom description"), - Arguments.of("explicit old default", - titled, - "test_client client", - client, - "test_client client", - "test_client client"), - Arguments.of("blank", titled, " ", client, titledDefault, "test_client client"), - Arguments.of("titled", titled, null, client, titledDefault, "test_client client"), - Arguments.of("untitled", untitled, null, client, "Python client for Weather", "test_client client"), - Arguments.of("types default", titled, null, types, "test_client types", "test_client types"), - Arguments.of("types explicit", titled, "Custom types", types, "Custom types", "Custom types")); + Arguments.of("explicit", titled, "Custom description", client, "Custom description"), + Arguments.of("explicit old default", titled, "test_client client", client, "test_client client"), + Arguments.of("blank", titled, " ", client, titledDefault), + Arguments.of("titled", titled, null, client, titledDefault), + Arguments.of("untitled", untitled, null, client, "Python client for Weather"), + Arguments.of("types default", titled, null, types, titledTypesDefault), + Arguments.of("types untitled", untitled, null, types, "Python types for Weather"), + Arguments.of("types blank", titled, " ", types, titledTypesDefault), + Arguments.of("types explicit", titled, "Custom types", types, "Custom types"), + Arguments.of("types explicit old default", titled, "test_client types", types, "test_client types")); } @ParameterizedTest(name = "{0}") @@ -124,10 +106,9 @@ public void resolvesPackageDescription( ServiceShape service, String moduleDescription, ArtifactType artifactType, - String expectedPyproject, - String expectedLegacy + String expected ) { - assertDescription(service, moduleDescription, artifactType, expectedPyproject, expectedLegacy); + assertDescription(service, moduleDescription, artifactType, expected); } private static ServiceShape service(String id, String title) { @@ -142,17 +123,30 @@ private static void assertDescription( ServiceShape service, String moduleDescription, ArtifactType artifactType, - String expectedPyproject, - String expectedLegacy + String expected ) { var model = Model.builder().addShape(service).build(); - var settings = PythonSettings.builder() - .service(service.getId()) - .moduleName("test_client") - .moduleVersion("0.0.1") - .moduleDescription(moduleDescription) - .artifactType(artifactType) - .build(); + PythonSettings settings; + if (artifactType == ArtifactType.CLIENT) { + // Clients are configured through the plugin's settings parsing. + var config = ObjectNode.builder() + .withMember("service", service.getId().toString()) + .withMember("module", "test_client") + .withMember("moduleVersion", "0.0.1"); + if (moduleDescription != null) { + config.withMember("moduleDescription", moduleDescription); + } + settings = PythonSettings.fromNode(config.build()); + } else { + // The types plugin converts its own settings through the builder. + settings = PythonSettings.builder() + .service(service.getId()) + .moduleName("test_client") + .moduleVersion("0.0.1") + .moduleDescription(moduleDescription) + .artifactType(artifactType) + .build(); + } var manifest = new MockManifest(); var delegator = new PythonDelegator(manifest, new PythonSymbolProvider(model, settings), settings); var context = GenerationContext.builder() @@ -167,11 +161,9 @@ private static void assertDescription( String pyproject = manifest.expectFileString("pyproject.toml"); String readme = manifest.expectFileString("README.md"); - // pyproject.toml uses the model-aware description, while the README and the - // no-argument accessor keep their legacy " " default. + // Both generated files carry the same resolved description. assertAll( - () -> assertTrue(pyproject.contains("description = \"" + expectedPyproject + "\"\n"), pyproject), - () -> assertTrue(readme.contains("\n" + expectedLegacy + "\n"), readme), - () -> assertEquals(expectedLegacy, settings.moduleDescription())); + () -> assertTrue(pyproject.contains("description = \"" + expected + "\"\n"), pyproject), + () -> assertTrue(readme.contains("\n" + expected + "\n"), readme)); } } diff --git a/codegen/plugins/types/src/it/java/software/amazon/smithy/python/codegen/types/PythonTypeCodegenTest.java b/codegen/plugins/types/src/it/java/software/amazon/smithy/python/codegen/types/PythonTypeCodegenTest.java index ddd37d6ea..52ef94907 100644 --- a/codegen/plugins/types/src/it/java/software/amazon/smithy/python/codegen/types/PythonTypeCodegenTest.java +++ b/codegen/plugins/types/src/it/java/software/amazon/smithy/python/codegen/types/PythonTypeCodegenTest.java @@ -4,6 +4,11 @@ */ package software.amazon.smithy.python.codegen.types; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -39,7 +44,7 @@ public void testCodegen(@TempDir Path tempDir) { } @Test - public void testCodegenWithoutService(@TempDir Path tempDir) { + public void testCodegenWithoutService(@TempDir Path tempDir) throws IOException { PythonTypeCodegenPlugin plugin = new PythonTypeCodegenPlugin(); Model model = Model.assembler(PythonTypeCodegenTest.class.getClassLoader()) .discoverModels(PythonTypeCodegenTest.class.getClassLoader()) @@ -55,5 +60,14 @@ public void testCodegenWithoutService(@TempDir Path tempDir) { .model(model) .build(); plugin.execute(context); + + // Without a selected service, describe the package by its module name rather + // than the internal synthetic service. + var expected = "Python types for types_test"; + String pyproject = Files.readString(tempDir.resolve("pyproject.toml")); + String readme = Files.readString(tempDir.resolve("README.md")); + assertAll( + () -> assertTrue(pyproject.contains("description = \"" + expected + "\"\n"), pyproject), + () -> assertTrue(readme.contains("\n" + expected + "\n"), readme)); } } diff --git a/codegen/plugins/types/src/main/java/software/amazon/smithy/python/codegen/types/PythonTypeCodegenSettings.java b/codegen/plugins/types/src/main/java/software/amazon/smithy/python/codegen/types/PythonTypeCodegenSettings.java index ff70f76a6..4d95cf743 100644 --- a/codegen/plugins/types/src/main/java/software/amazon/smithy/python/codegen/types/PythonTypeCodegenSettings.java +++ b/codegen/plugins/types/src/main/java/software/amazon/smithy/python/codegen/types/PythonTypeCodegenSettings.java @@ -14,6 +14,7 @@ import software.amazon.smithy.python.codegen.PythonSettings; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; +import software.amazon.smithy.utils.StringUtils; import software.amazon.smithy.utils.ToSmithyBuilder; /** @@ -64,11 +65,17 @@ public Builder toBuilder() { } public PythonSettings toPythonSettings(ShapeId service) { + // Without a selected service, describe the package by its module name rather + // than the internal synthetic service. + var description = moduleDescription; + if (this.service.isEmpty() && StringUtils.isBlank(description)) { + description = "Python types for " + moduleName; + } return PythonSettings.builder() .service(service) .moduleName(moduleName) .moduleVersion(moduleVersion) - .moduleDescription(moduleDescription) + .moduleDescription(description) .artifactType(PythonSettings.ArtifactType.TYPES) .build(); }