Skip to content
Open
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
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ This code generator, and the clients it generates, are unstable and should not
be used in production systems yet. Several features, such as detailed logging,
have not been implemented yet.

> [!NOTE]
> The Java generator in `codegen` remains the authoritative implementation.
> `packages/smithy-python` contains an experimental Python-native CLI scaffold
> that does not generate code yet.

### What is this repository?

This repository contains two major components:
Expand All @@ -20,9 +25,9 @@ This repository contains two major components:
2) Core modules and interfaces for building service clients in Python

These components facilitate generating clients for any [Smithy](https://smithy.io/)
service. The `codegen` directory contains the source code for generating clients.
The `python-packages` directory contains the source code for the handwritten python
components.
service. The `codegen` directory contains the current Java generator,
`packages/smithy-python` contains the Python-native generator scaffold, and the
other directories under `packages` contain the handwritten Python components.

This repository does *not* contain any generated clients, such as for S3 or other
AWS services. Rather, these are the tools that facilitate the generation of those
Expand Down
141 changes: 141 additions & 0 deletions designs/codegen/cli.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I haven't talked much about this publicly since I'm still working on implementations and a blog post, but check a look at shape closures. This is a way to drive generation that doesn't root everything in a service.

What it means for you is relatively little, other than that you should tolerate generating without a set service. Current type codegen generates a synthetic one, but you shouldn't operate that way.

Computing a closure is beyond your scope as a non-java plugin. I would suggest rather that you generate what's given to you and put the onus on customers to use smithy-build transforms to narrow it down to what they want. Maybe later you could implement everything needed to compute it and relax that restriction.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nice, thanks for making us aware of this.

I agree with you. I think not requiring --service <service_id> is also an improved user experience. It's pretty easy to detect on our end. For the AWS services, we've automated our infra to do smithy select --selector service to find it for us. I think we can do that at the Python layer here. The only issue I see here is if someone defines two services (not sure if smithy allows it). But in that case, we'd ask them to specify.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, that's basically how it works on the java side right now. You can ask that there be exactly one service. It may be possible to generate for multiple services, but there's no expectation.

Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Code Generator CLI

The `smithy-python` command is the process interface described in the
[Python Code Generation](index.md) overview. It supports direct use and
invocation from Smithy's
[`run` plugin](https://smithy.io/2.0/guides/smithy-build-json.html#run-plugin).

## Commands

Generation is organized by artifact type:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

But what if you could mix artifacts. You don't now - but you could. Types+client or types+server is an obvious combo that's easy to do without much difficulty.

smithy-python generate --modes client,types

You could default that to just be client since most will want that.

@jonathan343 jonathan343 Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm a bit hesitant to combine multiple modes into one command since that complicates mode-specific options. I'm wondering if we instead could do something like smithy-python generate client ... --with-types or similar.

But honestly, I'm not sure what you mean by client+types compared to what we have now. Can you elaborate a bit?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The idea is to support related types that aren't part of the service's operations. For example, say a service wants to define the events they send out through SNS. Those wouldn't be part of the service shape closure, so if you're filtering down to that then you exclude those shapes.

@jonathan343 jonathan343 Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Got it, that makes sense. The gap is that the Java client generator only emits the service closure (directive.connectedShapes()), so shapes like your SNS events example just get dropped silently today.

My first thought was to have the Python generator skip the closure entirely and generate every data shape in the AST. Before committing to that I ran it over all 431 published AWS models. It broke healthlake out of the box (leaked, unconnected shapes in a second namespace colliding with real ones) and pulled in orphan Cfn* types for two others, while the service closure reproduced the existing SDK surface on all 431. So I'm keeping the service closure as the default, which also matches what Java, smithy-ruby, and smithy-php do. The CLI prints a note when unconnected shapes get left out so it's not silent anymore.

Everything else you called out is in: --service is optional (we auto-detect when there's one), we don't synthesize a service, and types works without a service by just generating every data shape.

That does mean the "types outside the closure" case is deferred for now. I left room in the design doc for an all-shapes option or reading shapeClosures metadata later. I'd like to get moving on the core implementation and we can re-evaluate the interface before we cut a major version. Lmk if you see this as a blocker though and we can hash it out now.


```console
smithy-python generate client [OPTIONS]
smithy-python generate types [OPTIONS]
```

`client` generates a service client and the data shapes it uses. `types`
generates a standalone package containing only data shapes. Both commands accept
the following process options:

* `--model PATH` reads a JSON AST from a file instead of standard input.
* `--output PATH` selects the output directory. It defaults to the Smithy run
plugin's output directory (`SMITHY_PLUGIN_DIR`) when invoked by Smithy.

Settings specific to each artifact will be added with the functionality that
consumes them.

### Service Selection

The CLI does not require a service to be named. It resolves the service to
generate as follows:

* `--service SHAPE_ID` selects a specific service shape. The shape MUST exist in
the model and MUST be a service.
* When `--service` is omitted and the model contains exactly one service shape,
that service is used.
* When `--service` is omitted and the model contains more than one service
shape, the command fails with an invocation error that lists the candidates.

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

### Generated Shapes

When a service is resolved, both artifacts generate the data shapes in the
service closure: every shape reachable from the service through its operations,
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.

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.

The command MUST return zero after successful generation and non-zero when
arguments, settings, the model, or generation are invalid. Diagnostics are
written to standard error. Invalid command syntax and invocation inputs return
2, while I/O and generation failures return 1.

## Smithy `run` Plugin

The Smithy `run` plugin executes an external program during a build. It sends the
projection's Smithy model as a JSON AST to the process's standard input and runs
the process in the plugin's output directory.

A plugin ID MUST use `run::` followed by a custom artifact name. The configured
command identifies the artifact to generate:

```json
{
"version": "1.0",
"projections": {
"client": {
"plugins": {
"run::python-client": {
"command": ["smithy-python", "generate", "client"]
}
}
}
}
}
```

Artifact-specific options will be appended to `command` after they are defined.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is more for my own understanding: the existing Java generator receives service, module, and moduleVersion as plugin settings for each generated client. So in the future are we going to add options like the following?

{
  "run::python-client": {
    "command": [
      "smithy-python",
      "generate",
      "client",
      "--service",
      "com.amazonaws.bedrockruntime#AmazonBedrockFrontendService",
      "--module",
      "aws_sdk_bedrock_runtime",
      "--module-version",
      "0.8.0"
    ]
  }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yup. I need to look into Jordon's comment to see if --service will still be needed. I'll also consider using --package-name which we derive the module name from w/ an optional override (.e.g a aws-sdk-s3 package name generates a aws_sdk_s3 module name). I also think --module-version should become --package-version.

We may also grow this overtime to add new options like --http-client aiohttp | httpx | awscrt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note that you can also put these into environment variables. The run plugin supports putting stuff there. So you could also accept them from the environment if you want.

The `run` plugin can also pass settings through its `env` property, so an option
MAY additionally be read from an environment variable. A command-line option
takes precedence over its environment variable.

The `smithy-python` executable MUST be installed or otherwise available on the
Smithy process's `PATH`. Smithy passes no arguments other than those in
`command`.

### Input and Output

When invoked by Smithy, the CLI reads one JSON AST document from standard input.
The document represents the model after projection transforms have been applied.

Smithy serializes only what a shape introduces, so shapes that use mixins arrive
without their inherited members, traits, and properties, and traits added to
inherited members arrive as `apply` statements. The CLI resolves mixins while
loading the model, following the rules of the
[Smithy mixins specification](https://smithy.io/2.0/spec/mixins.html), so builds
do not need the `flattenAndRemoveMixins` transform.

The presence of `SMITHY_PLUGIN_DIR` identifies an invocation by the `run` plugin.
Generated files are written beneath this directory, which Smithy also uses as the
process's working directory. The CLI MUST NOT write generated files outside it,
and `--model` and `--output` MUST NOT be used in this mode.

The `run` plugin provides the following environment variables:

| Name | Purpose |
|------|---------|
| `SMITHY_ROOT_DIR` | Root directory of the Smithy build. |
| `SMITHY_PLUGIN_DIR` | Output and working directory for the plugin. |
| `SMITHY_PROJECTION_NAME` | Name of the active projection. |
| `SMITHY_ARTIFACT_NAME` | Custom artifact name from the plugin ID. |
| `SMITHY_INCLUDES_PRELUDE` | Whether the JSON AST includes prelude shapes. |

The CLI uses this context to interpret the model. Protocol and platform
integrations MAY also use it while generating files.

Smithy omits prelude shapes by default. A build MAY set `sendPrelude` to `true`
in the `run` plugin configuration when those shapes are needed.

## Direct Invocation

When `SMITHY_PLUGIN_DIR` is absent, the CLI treats the command as a direct
invocation and requires `--output`. It follows the same
generation path as Smithy invocation and can read a JSON AST from a file instead
of standard input by using `--model`. When standard input is an interactive
terminal, `--model` is required so that an omitted input does not wait indefinitely
for input. This mode is intended for development, testing, and integration with
tools other than the Smithy CLI.
63 changes: 63 additions & 0 deletions designs/codegen/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Python Code Generation

Smithy Python currently generates clients with the Java implementation in
`codegen`. This document describes the Python code generator that will replace
that implementation over time.

The Python generator is distributed as `smithy-python`. It is separate from the
runtime packages used by generated code, and is only needed while generating a
package.

## Goals

* Generate Python clients and standalone types packages from Smithy models.
* Integrate with standard Smithy builds without requiring a Java code generator.
* Provide extension points for protocol and platform-specific behavior.
* Produce code compatible with the existing Smithy Python runtime packages.
* Allow the Python and Java generators to coexist during migration.

## Architecture

The generator consumes a Smithy JSON AST and settings for an artifact. It loads
the model, applies artifact and protocol-specific behavior, and writes a Python
package.

```text
Smithy JSON AST + settings
|
v
smithy-python generator
|
v
client or types package
```

Two artifact types are initially planned:

* `client` will generate a service client and its required types.
* `types` will generate a standalone package of types selected from a model.

The artifact set may grow over time. A `server` artifact is a natural addition,
so the generator should not assume that only `client` and `types` exist.

The command-line interface is the generator's first entry point. Smithy's `run`
plugin invokes it as an external process, so the generator does not need to be
loaded into the Smithy CLI or implemented in Java.

Generated packages MUST NOT depend on `smithy-python` at runtime. They MAY
depend on the handwritten runtime packages in this repository.

## Migration

The Java generator remains authoritative while the Python generator is under
development. Features may be implemented and reviewed incrementally without
changing the Java path. A generated artifact SHOULD move to the Python generator
only after the required behavior is supported and tested.

The Python generator does not need to reproduce Java implementation details or
byte-for-byte output. It MUST preserve the supported Smithy semantics and public
behavior of generated packages.

## Designs

* [Code Generator CLI](cli.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "feature",
"description": "Added the experimental smithy-python package and CLI scaffold."
}
1 change: 1 addition & 0 deletions packages/smithy-python/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note for reviewers: Our release infra will automatically handle version bumping and changelog management.

1 change: 1 addition & 0 deletions packages/smithy-python/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
19 changes: 19 additions & 0 deletions packages/smithy-python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# smithy-python

> [!WARNING]
> This package is an experimental scaffold. It does not generate code yet. The
> Java generator in the repository's `codegen` directory remains authoritative.

`smithy-python` will provide Python-native code generation for Smithy models.
The initial command-line interface exposes the planned client and types generation
commands so that their top-level shape can be developed independently from the
generator implementation.

```console
smithy-python generate client [OPTIONS]
smithy-python generate types [OPTIONS]
```

After validating their invocation options, both generation commands currently exit
with an error explaining that generation has not been implemented. The package is
included in workspace builds to validate its packaging and entry points.
51 changes: 51 additions & 0 deletions packages/smithy-python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[project]
name = "smithy-python"
dynamic = ["version"]
requires-python = ">=3.12"
authors = [
{name = "Amazon Web Services"},
]
description = "A Smithy code generator for Python clients and types."
readme = "README.md"
license = {text = "Apache License 2.0"}
keywords = ["smithy", "codegen", "sdk"]
classifiers = [
"Development Status :: 2 - Pre-Alpha",
"Intended Audience :: Developers",
"Natural Language :: English",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Free Threading :: 2 - Beta",
"Topic :: Software Development :: Code Generators",
]
dependencies = []

[project.scripts]
smithy-python = "smithy_python.cli:main"

[project.urls]
"Changelog" = "https://github.com/smithy-lang/smithy-python/blob/develop/packages/smithy-python/CHANGELOG.md"
"Code" = "https://github.com/smithy-lang/smithy-python/tree/develop/packages/smithy-python/"
"Issue tracker" = "https://github.com/smithy-lang/smithy-python/issues"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.version]
path = "src/smithy_python/__init__.py"

[tool.hatch.build]
exclude = [
"tests",
]

[tool.ruff]
src = ["src"]
5 changes: 5 additions & 0 deletions packages/smithy-python/src/smithy_python/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""A Smithy code generator for Python clients and types."""

__version__ = "0.0.0"
7 changes: 7 additions & 0 deletions packages/smithy-python/src/smithy_python/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

from .cli import main

if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading