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

fix(Python): Support orphaned shapes, update Config shape generation #626

Merged
merged 81 commits into from
Oct 10, 2024

Conversation

lucasmcdonald3
Copy link
Contributor

@lucasmcdonald3 lucasmcdonald3 commented Oct 8, 2024

Issue #, if available:

Description of changes:

Python:

  • Supported "orphaned" shapes (shapes with no graph connection to the LocalService)
  • Refactor LocalService Config shape generation from its own logic to "normal" shape generation
    • Before, Config shapes were "orphaned" and could not be discovered by normal shape generation. I wrote manual generation for Config shapes.
    • Now, Config shape generation is more closely tied to normal shape generation, which gets it more fully-featured shape generation (constraints, as/from_dict)
  • Don't use forward references for boto3 client typehints

Check out this MPL PR for sample generated code: aws/aws-cryptographic-material-providers-library#831

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@lucasmcdonald3 lucasmcdonald3 marked this pull request as ready for review October 9, 2024 00:03
@lucasmcdonald3 lucasmcdonald3 requested a review from a team as a code owner October 9, 2024 00:03
@lucasmcdonald3 lucasmcdonald3 changed the title fix(Python): Support orphaned shapes, update type generation fix(Python): Support orphaned shapes, update Config shape generation Oct 9, 2024
@@ -252,7 +252,7 @@ protected boolean isOptionalDefault(MemberShape member) {
&& (target.isDocumentShape() || target.isListShape() || target.isMapShape());
}

private void writeClassDocs(boolean isError) {
protected void writeClassDocs(boolean isError) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately, this change means another

make mvn_local_deploy_polymorph_python_dependencies

on merging this from main, but I think this is better than duplicating the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm inclined to add mvn_local_deploy_polymorph_dependencies as a dependency of all polymorph targets, given it should be cheap and hopefully Gradle-cached anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. I'll include this

Copy link
Contributor

@robin-aws robin-aws left a comment

Choose a reason for hiding this comment

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

Quite happy with the general approach to handle orphaned shapes. Cut #627 for longer term follow up on the problem.

Are you dependent on an OrphanedShapes test model to get added to test these changes?

@@ -252,7 +252,7 @@ protected boolean isOptionalDefault(MemberShape member) {
&& (target.isDocumentShape() || target.isListShape() || target.isMapShape());
}

private void writeClassDocs(boolean isError) {
protected void writeClassDocs(boolean isError) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm inclined to add mvn_local_deploy_polymorph_dependencies as a dependency of all polymorph targets, given it should be cheap and hopefully Gradle-cached anyway.

orderedShapes.add(shape);
}
}
for (Shape shape : topologicalIndex.getRecursiveShapes()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wasn't 100% sure from the TopologicalIndex documentation, but is this guaranteed to be a superset of getOrderedShapes()?

Would be nice to have an explicit unit test just for getTopologicallyOrderedOrphanedShapesForService, especially since it can clarify what is or isn't a recursive shape.

Copy link
Contributor Author

@lucasmcdonald3 lucasmcdonald3 Oct 9, 2024

Choose a reason for hiding this comment

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

I wasn't 100% sure from the TopologicalIndex documentation, but is this guaranteed to be a superset of getOrderedShapes()?

I suspect they're disjoint, otherwise some shapes would be generated multiple times.

Would be nice to have an explicit unit test just for getTopologicallyOrderedOrphanedShapesForService, especially since it can clarify what is or isn't a recursive shape.

I think a unit test might be more effort than it's worth --

Primarily, I'm inclined to trust whatever Smithy-Core is doing here, but verify it against a TestModel/real projects. I built this PR against the MPL and DBESDK, it picked up orphaned shapes, and I can run the code. I don't think the test you're proposing gets us as much ROI.

For a unit test, we'd have to create a Smithy model as a POJO (sort of like this), which seems more brittle than is worth doing.

Let me know your thoughts.

Copy link
Contributor

Choose a reason for hiding this comment

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

I suspect they're disjoint, otherwise some shapes would be generated multiple times.

Good point, I think they must be.

For a unit test, we'd have to create a Smithy model as a POJO (sort of like this), which seems more brittle than is worth doing.

You can include a *.smithy file as a resource and read it in pretty easily instead: https://github.com/smithy-lang/smithy-dafny/blob/main-1.x/codegen/smithy-dafny-codegen/src/test/java/software/amazon/polymorph/smithyjava/modeled/ModeledShapeValueTest.java#L77-L82

But I'll leave it to your judgement on whether you want to bother with that on this PR, I won't block on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's pretty nifty -- I'll plan to do this when I do the OrphanedShapes TestModel, but not now.

@lucasmcdonald3
Copy link
Contributor Author

Are you dependent on an OrphanedShapes test model to get added to test these changes?

I don't think so, but let me know if you disagree.

I built this PR into the MPL and DBESDK, and both of those projects are still passing.

This (plus the MPL PR) is ideally the last PR I'll to land before I release the Python MPL, so I'm trying to push any non-essential work out until after that lands.
I'm carving out some time to handle these post-launch fast follows, and I added OrphanedShapes TestModel to that list

Copy link
Contributor

@robin-aws robin-aws left a comment

Choose a reason for hiding this comment

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

LGTM, some non-blocking questions

@@ -269,6 +272,7 @@ transpile_dependencies_test:
# make polymorph_code_gen CODEGEN_CLI_ROOT=/path/to/smithy-dafny/codegen/smithy-dafny-codegen-cli
# StandardLibrary is filtered out from dependent-model patsubst list;
# Its model is contained in $(LIBRARY_ROOT)/model, not $(LIBRARY_ROOT)/../StandardLibrary/Model.
_polymorph: mvn_local_deploy_polymorph_dependencies
Copy link
Contributor

Choose a reason for hiding this comment

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

❤️

() -> {
writer.write("self,");
if (!shape.members().isEmpty()) {
// Adding this star to the front prevents the use of positional arguments.
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this lifted from smithy-python too then? It's a great idea but I wasn't sure if they'd acted on it yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

@@ -203,7 +309,14 @@ protected void writePropertyForMember(
);
}

if (target.hasTrait(ReferenceTrait.class)) {
// Reference shapes require forward reference to avoid circular import,
// but references to AWS SDKs don't
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit picking, but isn't it more accurately that shapes from dependencies are already declared?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is actually about circular dependencies within one project.
I'm going to write about this problem and solutions in a Smithy-Dafny Python dev guide, but an overview/instance of the problem is that models.py can't import references.py at the top-level because references.py depends on models.py at the top-level, but models.py still needs to import references.py to use its classes.
One solution is to defer the imports like I've done here.
I wrote a fair amount about this and drew some graphs, but I'm going to collate all that later.

Copy link
Contributor

Choose a reason for hiding this comment

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

I will be thrilled to review a PR that targets docs/python in the future :)

) {
Model transformedModel = model;
transformedModel =
addWrappedLocalServiceTrait(transformedModel, serviceShape);
Copy link
Contributor

Choose a reason for hiding this comment

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

off topic question: what does this transformation do exactly?

Copy link
Contributor Author

@lucasmcdonald3 lucasmcdonald3 Oct 10, 2024

Choose a reason for hiding this comment

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

Check out https://github.com/smithy-lang/smithy-dafny/blob/main-1.x/codegen/smithy-dafny-codegen/src/main/java/software/amazon/polymorph/smithypython/wrappedlocalservice/extensions/DafnyPythonWrappedLocalServiceClientCodegenPlugin.java#L52

In preprocessing, it replaces a LocalServiceTrait on the ServiceTrait with a WrappedLocalServiceTrait.
This results in some different codegen; wrapped LocalServices don't get all the codegen from LocalServices. (No models, etc.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah thanks! I wasn't aware of that trait.

@lucasmcdonald3 lucasmcdonald3 merged commit 0ac4a65 into main-1.x Oct 10, 2024
80 checks passed
@lucasmcdonald3 lucasmcdonald3 deleted the config-unions branch October 10, 2024 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants