Skip to content

feat(codegen): lazily load operation definitions - #877

Draft
stobrien89 wants to merge 2 commits into
developfrom
lazy-operation-loading
Draft

stobrien89 wants to merge 2 commits into
developfrom
lazy-operation-loading

Conversation

@stobrien89

@stobrien89 stobrien89 commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Issue #, if available:
#797

Description

Lazily constructs generated APIOperation instances when they are first accessed.

This is PR 1 of 2. The follow-up PR partitions generated schemas and model stubs.

Why this comes first

Schema partitioning only helps if importing a client does not access every operation schema. Eager operation construction would resolve those schemas during import and load most or all schema partitions immediately.

This change stores lightweight operation metadata instead. When an operation is first used, models.__getattr__ constructs and caches its APIOperation. This allows the follow-up partitioning change to load only the schema partitions needed by the operations and models being used.

Changes

  • Store operation schema, error, and authentication IDs as metadata.
  • Construct operations through models.__getattr__.
  • Cache operation instances after first access.
  • Resolve operations from models when clients invoke them.
  • Preserve existing operation exports and identity.

EC2 benchmark

Measured on x86 with 20 runs, three warmups, and five allocation runs.

Metric Eager Lazy Change
Generated source 6.18 MiB 6.19 MiB +0.2%
Client import 184.87 ms 182.81 ms -1.1%
Process time 296.02 ms 294.08 ms -0.7%
Peak RSS 55.86 MiB 53.73 MiB -3.8%
Peak Python allocation 22.72 MiB 21.95 MiB -3.4%
First operation access 0.001 ms 0.011 ms +0.010 ms

Remaining work

PR 2 will:

  • Partition generated schemas for large services.
  • Partition models.pyi to improve static-analysis performance.
  • Preserve stable schemas.py and models.pyi facades.
  • Keep recursive schema components together.
  • Continue producing single files for smaller services.

The combined prototype using 25-shape schema partitions reduced EC2 client import by 10.8%, process time by 9.2%, peak RSS by 15.4%, and peak Python allocation by 29.5% compared with the eager, unpartitioned baseline.

Runtime model classes will not be partitioned because they are already constructed lazily.

Compatibility

Generated operation names remain available from models, and repeated access returns the same cached instance.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Concurrent first accesses can return different APIOperation instances and overwrite the cached instance, breaking stable operation identity.

1 open issue
What changed in this PR

This PR makes the Java generator defer APIOperation construction until first access, preparing generated clients for schema partitioning in the follow-up PR.

Changes:

  • Replace eager operation instances with metadata and lazy construction through models.__getattr__.
  • Resolve operations when client methods run, while retaining operation exports and caching.
  • Regenerate nine client/model pairs and add generation assertions.
File Description
codegen/​core/​src/​main/​java/​software/​amazon/​smithy/​python/​codegen/​generators/​OperationGenerator.java Emits operation metadata instead of instances.
codegen/​core/​src/​main/​java/​software/​amazon/​smithy/​python/​codegen/​generators/​ModelModuleGenerator.java Generates lazy construction, caching, and exports.
codegen/​core/​src/​main/​java/​software/​amazon/​smithy/​python/​codegen/​ClientGenerator.java Defers operation lookup until invocation.
codegen/​core/​src/​it/​java/​software/​amazon/​smithy/​python/​codegen/​test/​PythonCodegenTest.java Adds assertions for lazy operation generation.
codegen-output/​rpcv2-cbor/​src/​rpcv2cbor/​models.py Adds lazy RPC v2 CBOR operations.
codegen-output/​rpcv2-cbor/​src/​rpcv2cbor/​client.py Uses deferred operation lookup.
codegen-output/​rest-xml/​src/​restxml/​models.py Adds lazy REST XML operations.
codegen-output/​rest-xml/​src/​restxml/​client.py Uses deferred operation lookup.
codegen-output/​rest-xml-with-namespace/​src/​restxmlwithnamespace/​models.py Adds lazy namespaced REST XML operations.
codegen-output/​rest-xml-with-namespace/​src/​restxmlwithnamespace/​client.py Uses deferred operation lookup.
codegen-output/​rest-json-1/​src/​restjson/​models.py Adds lazy REST JSON operations.
codegen-output/​rest-json-1/​src/​restjson/​client.py Defers regular and event-stream operation lookup.
codegen-output/​my-handwritten-model/​src/​my_handwritten_model/​models.py Adds lazy event-stream operations.
codegen-output/​my-handwritten-model/​src/​my_handwritten_model/​client.py Defers event-stream operation lookup.
codegen-output/​ec2-query/​src/​ec2query/​models.py Adds lazy EC2 Query operations.
codegen-output/​ec2-query/​src/​ec2query/​client.py Uses deferred operation lookup.
codegen-output/​aws-query/​src/​awsquery/​models.py Adds lazy AWS Query operations.
codegen-output/​aws-query/​src/​awsquery/​client.py Uses deferred operation lookup.
codegen-output/​aws-json-1-1/​src/​awsjson11/​models.py Adds lazy AWS JSON 1.1 operations.
codegen-output/​aws-json-1-1/​src/​awsjson11/​client.py Uses deferred operation lookup.
codegen-output/​aws-json-1-0/​src/​awsjson10/​models.py Adds lazy AWS JSON 1.0 operations.
codegen-output/​aws-json-1-0/​src/​awsjson10/​client.py Uses deferred operation lookup.

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The added source-string assertions leave runtime laziness and cached identity unverified, so a fresh-process regression test is still needed.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Test lazy loading and cached operation identity at runtime

codegen/​core/​src/​main/​java/​software/​amazon/​smithy/​python/​codegen/​generators/​ModelModuleGenerator.java:149

The added source-string assertions do not verify runtime laziness or cached identity, so regressions in these guarantees could pass unnoticed. Add a regression test in a fresh process that imports a generated client, confirms operation constants are absent from vars(models), then accesses one operation through both attribute access and from ...models import ..., asserting the same cached instance while unrelated operations remain unloaded.

1 resolved since last review

Review effort: Balanced

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