Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ jobs:
run: |
make build-py

- name: Check native Python codegen output
if: ${{ matrix.python-version == '3.12' }}
run: |
make generate-codegen-output-py

- name: Build (and test) java packages
run: |
make build-java
Expand Down
1 change: 1 addition & 0 deletions .licenserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ header:
- 'LICENSE'
- 'NOTICE'
- 'codegen-output/**'
- 'codegen-output-py/**'
- '**/smithy_core/rfc3986.py'

language:
Expand Down
16 changes: 15 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.DEFAULT_GOAL:=help

.PHONY: help install format-java check-java build-java test-protocols typecheck-protocols generate-protocol-tests api-snapshot ci lint-py check-py test-py benchmark build-py clean
.PHONY: help install format-java check-java build-java test-protocols typecheck-protocols generate-protocol-tests generate-codegen-output-py api-snapshot ci lint-py check-py test-py benchmark build-py clean

help: ## Show this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-25s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)
Expand Down Expand Up @@ -63,6 +63,19 @@ generate-protocol-tests: ## Generates protocol-test clients, copies them to ./co
@echo "codegen-output is up to date."


generate-codegen-output-py: ## Generates Python output and asserts no snapshot diff.
rm -rf codegen-output-py
uv run python scripts/codegen-output-py/generate.py
@if ! git diff --quiet --exit-code -- codegen-output-py || [ -n "$$(git ls-files --others --exclude-standard -- codegen-output-py)" ]; then \
echo "ERROR: generated codegen-output-py differs from the committed snapshot."; \
echo "Review the diff and commit it if the change is intended:"; \
git --no-pager status --short -- codegen-output-py; \
git --no-pager diff -- codegen-output-py; \
exit 1; \
fi
@echo "codegen-output-py is up to date."


api-snapshot: ## Records the public API surface of each package and asserts no git diff.
uv run python scripts/api-snapshot/api_snapshot.py

Expand All @@ -84,6 +97,7 @@ ci: ## Runs the full CI sequence locally (mirrors the CI workflow steps).
make test-py
make build-py
make build-java
make generate-codegen-output-py
make generate-protocol-tests test-protocols
make typecheck-protocols

Expand Down
6 changes: 6 additions & 0 deletions codegen-output-py/types/.smithy-python-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"files": {
"src/example_types/_private/schemas.py": "d0f35e8979c4aa9d49a1d9c0c8efeb860420bc0ceab3ba3edcc9488c053e9110"
},
"version": 1
}
176 changes: 176 additions & 0 deletions codegen-output-py/types/src/example_types/_private/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
from __future__ import annotations

from smithy_core._schema_compact import StaticSchema

n0 = "example"
_s0 = "GetExampleInput"
_s1 = "smithy.api#input"
_s2 = "id"
_s3 = "TagList"
_s4 = "member"
_s5 = "Attributes"
_s6 = "key"
_s7 = "value"
_s8 = "Value"
_s9 = "text"
_s10 = "number"
_s11 = "empty"
_s12 = "Status"
_s13 = "ACTIVE"
_s14 = "smithy.api#enumValue"
_s15 = "active"
_s16 = "INACTIVE"
_s17 = "inactive"
_s18 = "Code"
_s19 = "OK"
_s20 = "NOT_FOUND"
_s21 = "Example"
_s22 = "name"
_s23 = "count"
_s24 = "tags"
_s25 = "attributes"
_s26 = "choice"
_s27 = "status"
_s28 = "code"
_s29 = "GetExampleOutput"
_s30 = "smithy.api#output"
_s31 = "ExampleError"
_s32 = "smithy.api#error"
_s33 = "client"
_s34 = "message"
_s35 = "retryAfterSeconds"

GET_EXAMPLE_INPUT: StaticSchema = (
18,
n0,
_s0,
{_s1: None},
(_s2,),
((3, 1),),
)

TAG_LIST: StaticSchema = (
16,
n0,
_s3,
0,
(_s4,),
((3, 0),),
)

ATTRIBUTES: StaticSchema = (
17,
n0,
_s5,
0,
(
_s6,
_s7,
),
(
(3, 0),
(13, 0),
),
)

EXAMPLE_VALUE: StaticSchema = (
19,
n0,
_s8,
0,
(
_s9,
_s10,
_s11,
),
(
(3, 0),
(7, 0),
(-1, 0),
),
)

STATUS: StaticSchema = (
14,
n0,
_s12,
0,
(
_s13,
_s16,
),
(
(-1, {_s14: _s15}),
(-1, {_s14: _s17}),
),
)

CODE: StaticSchema = (
15,
n0,
_s18,
0,
(
_s19,
_s20,
),
(
(-1, {_s14: 200}),
(-1, {_s14: 404}),
),
)

RENAMED_EXAMPLE: StaticSchema = (
18,
n0,
_s21,
0,
(
_s22,
_s23,
_s24,
_s25,
_s26,
_s27,
_s28,
),
(
(3, 1),
(-5, 0),
(TAG_LIST, 0),
(ATTRIBUTES, 0),
(EXAMPLE_VALUE, 0),
(STATUS, 0),
(CODE, 0),
),
)

GET_EXAMPLE_OUTPUT: StaticSchema = (
18,
n0,
_s29,
{_s30: None},
(
n0,
_s7,
),
(
(RENAMED_EXAMPLE, 0),
(EXAMPLE_VALUE, 0),
),
)

EXAMPLE_ERROR: StaticSchema = (
18,
n0,
_s31,
{_s32: _s33},
(
_s34,
_s35,
),
(
(3, 0),
(7, 0),
),
)
29 changes: 14 additions & 15 deletions designs/codegen/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ generate as follows:
* `--service SHAPE_ID` selects a specific service shape. The ID MUST be absolute
and MUST NOT identify a member. The shape MUST exist in the model and MUST be
a service that is not marked `@mixin`.
* When `--service` is omitted and the model contains exactly one non-mixin
service shape, that service is used.
* When `--service` is omitted and the model contains more than one non-mixin
service shape, the command fails with an invocation error that lists the
candidates.
* For `client`, omitting `--service` selects the sole non-mixin service. Multiple
services produce an invocation error that lists the candidates. A model
without a service also fails.
* For `types`, omitting `--service` does not infer one. Selection starts from
eligible declarations across the model even when the model contains one or
more services.

The `client` artifact requires a resolved service. The `types` artifact does
not. The CLI MUST NOT synthesize a placeholder service to satisfy generation.
The CLI MUST NOT synthesize a placeholder service to satisfy generation.

### Generated Shapes

Expand All @@ -50,16 +50,15 @@ resources, errors, and members. This matches the surface produced by the other
Smithy code generators. Data shapes in the model that are not connected to the
service are not generated, and the CLI reports how many were left out.

When no service is resolved, the `types` artifact generates every data shape in
the model. Smithy guarantees case-insensitively unique shape names only within a
service closure, so in this mode the command fails when two shapes have
case-insensitively equal names, identifying the conflicting shape IDs.
When no service is resolved, the `types` artifact starts with eligible
declaration shapes across the model. Its selector and input/output settings
filter those roots before member-target dependencies are added. Smithy
guarantees case-insensitively unique shape names only within a service closure,
so the command fails when two final declarations have case-insensitively equal
names, identifying the conflicting shape IDs.

Trait definitions, prelude shapes, and shapes marked `@mixin` are never
generated. Builds that need a different set of shapes, such as types that are
not bound to any operation, apply smithy-build transforms in the projection.
An option to generate every shape in the model regardless of the service MAY be
added when there is a need for it.
generated. Projection transforms apply before the artifact's selector.

The command MUST return zero after successful generation and non-zero when
arguments, settings, the model, or generation are invalid. Diagnostics are
Expand Down
5 changes: 3 additions & 2 deletions designs/codegen/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,6 @@ behavior of generated packages.

* [Code Generator CLI](cli.md)
* [Service and Data-Shape Selection](selection.md)
* [Native Python Symbols](symbols.md)
* [Native Python Writer](writer.md)
* [Python Symbols](symbols.md)
* [Python Writer](writer.md)
* [Standalone Types Generation](types.md)
65 changes: 50 additions & 15 deletions designs/codegen/selection.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Service and Data-Shape Selection

Selection follows [model loading](model.md) and precedes generation. The CLI's
[service selection rules](cli.md#service-selection) apply to both artifacts.
[service selection rules](cli.md#service-selection) differ by artifact.
`--service` accepts only an absolute, existing, non-member, non-mixin service ID.
Without it, the sole non-mixin service is used; ambiguity lists the candidates
in model order. Clients require a service, while standalone types do not.
These selection failures are invocation errors (exit 2).
Clients infer a sole service when the option is absent. Standalone types use a
service only when the option is explicit. Selection failures are invocation
errors (exit 2).

## API and traversal

Expand All @@ -15,6 +15,22 @@ require_service=False)` returns a frozen `Selection` containing the resolved
`require_service=True`. The API has no I/O and reuses the loaded shape objects.
`resolve_service` is also available for consumers needing only resolution.

Standalone types use:

```python
select_type_shapes(
model,
*,
service_id=None,
selector=Selector.identity(),
generate_inputs_and_outputs=False,
)
```

This function does not infer a service. An explicit service establishes the
initial service closure. Without one, every eligible declaration shape in the
model is an initial candidate.

A service closure uses an iterative worklist and a visited-ID set. Its edges are:

* Service operations, resources, and errors.
Expand All @@ -35,17 +51,36 @@ Filtering eligible shapes in original model order preserves that order rather
than worklist or sorted order. The excluded count includes only eligible data
shapes outside the closure; the CLI reports it to stderr only when nonzero.

## Types without a service
## Standalone types

Standalone types selection proceeds in four stages:

1. Select declaration candidates from an explicit service closure or the
complete model.
2. Intersect the candidates with the selector result.
3. Exclude input and output structures as roots unless
`generate_inputs_and_outputs=True`.
4. Add every member-target dependency reachable from the remaining
declarations.

Declaration candidates are structures, unions, enums, and integer enums.
Prelude shapes, mixins, trait definitions, and shapes marked `@private` are
ineligible. Dependency closure may add simple and collection shapes needed by
schemas and serde. Final shapes retain model order.

Final declaration names are grouped case-insensitively. A conflicting group
produces a `ModelError` listing every conflicting ID (exit 1). Dependency-only
shapes do not participate in this check.

## Selectors

All eligible data shapes are selected. Names are grouped case-insensitively;
any conflicting groups produce a `ModelError` listing every conflicting ID
(exit 1). Ineligible shapes do not participate. This check is intentionally not
run when a service resolves: Smithy validation is responsible for service name
semantics, and selection is not a general validator or symbol provider.
`Selector.parse(source)` builds an immutable syntax tree for the Smithy selector
language. Evaluation uses the loaded semantic model and returns shape IDs.
Parsing occurs once while resolving `TypesSettings`. Selection intersects the
result with eligible declaration candidates and does not reorder either set.

## Current boundary
Selector conformance fixtures run the same expressions and models through the
Java and Python implementations. The selected shape ID sets must match.

Successful selection still reaches the generation-not-implemented diagnostic
(exit 1). No files or output directories are created, and stdout remains clean.
The generator has no new dependencies; selection neither interprets selectors
nor introduces runtime schemas, naming, or writers.
Selection has no output side effects. It returns model objects for generation
and does not construct symbols, schemas, or writers.
Loading
Loading