smithy-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- smithy_python/__init__.py +5 -0
- smithy_python/__main__.py +7 -0
- smithy_python/cli.py +180 -0
- smithy_python/environment.py +40 -0
- smithy_python/exceptions.py +27 -0
- smithy_python/model/__init__.py +56 -0
- smithy_python/model/_loader.py +573 -0
- smithy_python/model/_model.py +134 -0
- smithy_python/model/_prelude.py +60 -0
- smithy_python/model/_shape_id.py +115 -0
- smithy_python/model/_shapes.py +251 -0
- smithy_python/py.typed +0 -0
- smithy_python/selection.py +157 -0
- smithy_python-0.1.0.dist-info/METADATA +47 -0
- smithy_python-0.1.0.dist-info/RECORD +18 -0
- smithy_python-0.1.0.dist-info/WHEEL +4 -0
- smithy_python-0.1.0.dist-info/entry_points.txt +2 -0
- smithy_python-0.1.0.dist-info/licenses/NOTICE +1 -0
smithy_python/cli.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Command-line interface for the Smithy Python code generator."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
from collections.abc import Mapping, Sequence
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import BinaryIO, Final
|
|
13
|
+
|
|
14
|
+
from . import __version__
|
|
15
|
+
from .environment import PluginEnvironment
|
|
16
|
+
from .exceptions import CodegenError, InvalidInvocationError
|
|
17
|
+
from .model import load_model
|
|
18
|
+
from .selection import select_shapes
|
|
19
|
+
|
|
20
|
+
_GENERATION_NOT_IMPLEMENTED: Final = (
|
|
21
|
+
"smithy-python: error: {artifact} generation is not implemented yet\n"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class _Invocation:
|
|
27
|
+
artifact: str
|
|
28
|
+
model_source: bytes
|
|
29
|
+
output_dir: Path
|
|
30
|
+
environment: PluginEnvironment
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main(
|
|
34
|
+
argv: Sequence[str] | None = None,
|
|
35
|
+
*,
|
|
36
|
+
environ: Mapping[str, str] | None = None,
|
|
37
|
+
stdin: BinaryIO | None = None,
|
|
38
|
+
) -> int:
|
|
39
|
+
"""Run the CLI with the provided process inputs and return its exit code."""
|
|
40
|
+
parser = _create_parser()
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
args = parser.parse_args(argv)
|
|
44
|
+
except SystemExit as error:
|
|
45
|
+
return error.code if isinstance(error.code, int) else 1
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
invocation = _resolve_invocation(args, environ=environ, stdin=stdin)
|
|
49
|
+
except InvalidInvocationError as error:
|
|
50
|
+
sys.stderr.write(f"smithy-python: error: {error}\n")
|
|
51
|
+
return 2
|
|
52
|
+
except (CodegenError, OSError) as error:
|
|
53
|
+
sys.stderr.write(f"smithy-python: error: {error}\n")
|
|
54
|
+
return 1
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
model = load_model(invocation.model_source)
|
|
58
|
+
selection = select_shapes(
|
|
59
|
+
model,
|
|
60
|
+
service_id=args.service,
|
|
61
|
+
require_service=invocation.artifact == "client",
|
|
62
|
+
)
|
|
63
|
+
except InvalidInvocationError as error:
|
|
64
|
+
sys.stderr.write(f"smithy-python: error: {error}\n")
|
|
65
|
+
return 2
|
|
66
|
+
except CodegenError as error:
|
|
67
|
+
sys.stderr.write(f"smithy-python: error: {error}\n")
|
|
68
|
+
return 1
|
|
69
|
+
|
|
70
|
+
if selection.excluded_count and selection.service is not None:
|
|
71
|
+
sys.stderr.write(
|
|
72
|
+
f"smithy-python: excluded {selection.excluded_count} eligible data shape(s) "
|
|
73
|
+
f"outside the closure of service {selection.service.id}\n"
|
|
74
|
+
)
|
|
75
|
+
sys.stderr.write(_GENERATION_NOT_IMPLEMENTED.format(artifact=args.artifact))
|
|
76
|
+
return 1
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _create_parser() -> argparse.ArgumentParser:
|
|
80
|
+
parser = argparse.ArgumentParser(
|
|
81
|
+
prog="smithy-python",
|
|
82
|
+
description="Generate Python source from Smithy models.",
|
|
83
|
+
)
|
|
84
|
+
parser.add_argument(
|
|
85
|
+
"--version",
|
|
86
|
+
action="version",
|
|
87
|
+
version=f"%(prog)s {__version__}",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
commands = parser.add_subparsers(required=True)
|
|
91
|
+
generate = commands.add_parser("generate", help="Generate Python source")
|
|
92
|
+
artifacts = generate.add_subparsers(dest="artifact", required=True)
|
|
93
|
+
common = _common_artifact_options()
|
|
94
|
+
for name, help_text in (
|
|
95
|
+
("client", "Generate a client package"),
|
|
96
|
+
("types", "Generate a standalone types package"),
|
|
97
|
+
):
|
|
98
|
+
artifacts.add_parser(name, help=help_text, parents=[common])
|
|
99
|
+
|
|
100
|
+
return parser
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _common_artifact_options() -> argparse.ArgumentParser:
|
|
104
|
+
"""Build a parent parser with the options shared by every artifact."""
|
|
105
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
106
|
+
parser.add_argument(
|
|
107
|
+
"--service",
|
|
108
|
+
metavar="SHAPE_ID",
|
|
109
|
+
help="Absolute service shape ID; defaults to the sole non-mixin service.",
|
|
110
|
+
)
|
|
111
|
+
parser.add_argument(
|
|
112
|
+
"--model",
|
|
113
|
+
type=Path,
|
|
114
|
+
help=(
|
|
115
|
+
"The Smithy JSON AST model file to use for code generation. "
|
|
116
|
+
"If not set, the model is read from standard input."
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"--output",
|
|
121
|
+
type=Path,
|
|
122
|
+
help=(
|
|
123
|
+
"Output directory for generated files. Defaults to the Smithy run "
|
|
124
|
+
"plugin's output directory (SMITHY_PLUGIN_DIR) when invoked by Smithy."
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
return parser
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _resolve_invocation(
|
|
131
|
+
args: argparse.Namespace,
|
|
132
|
+
*,
|
|
133
|
+
environ: Mapping[str, str] | None,
|
|
134
|
+
stdin: BinaryIO | None,
|
|
135
|
+
) -> _Invocation:
|
|
136
|
+
# PluginEnvironment falls back to os.environ, so the defaulting lives there.
|
|
137
|
+
environment = PluginEnvironment.from_environ(environ)
|
|
138
|
+
model_path: Path | None = args.model
|
|
139
|
+
output_path: Path | None = args.output
|
|
140
|
+
|
|
141
|
+
if (plugin_dir := environment.plugin_dir) is not None:
|
|
142
|
+
if model_path is not None:
|
|
143
|
+
raise InvalidInvocationError(
|
|
144
|
+
"--model cannot be used with the Smithy run plugin"
|
|
145
|
+
)
|
|
146
|
+
if output_path is not None:
|
|
147
|
+
raise InvalidInvocationError(
|
|
148
|
+
"--output cannot be used with the Smithy run plugin"
|
|
149
|
+
)
|
|
150
|
+
output_dir = plugin_dir
|
|
151
|
+
else:
|
|
152
|
+
if output_path is None:
|
|
153
|
+
raise InvalidInvocationError("Direct invocation requires --output")
|
|
154
|
+
output_dir = output_path
|
|
155
|
+
|
|
156
|
+
if model_path is not None:
|
|
157
|
+
# A model that cannot be read is an I/O failure like any other, whether
|
|
158
|
+
# the path is missing, a directory, or unreadable.
|
|
159
|
+
try:
|
|
160
|
+
model_source = model_path.read_bytes()
|
|
161
|
+
except OSError as error:
|
|
162
|
+
raise OSError(
|
|
163
|
+
f"Cannot read model {model_path}: {error.strerror or error}"
|
|
164
|
+
) from error
|
|
165
|
+
else:
|
|
166
|
+
model_stream = sys.stdin.buffer if stdin is None else stdin
|
|
167
|
+
if environment.plugin_dir is None and model_stream.isatty():
|
|
168
|
+
raise InvalidInvocationError(
|
|
169
|
+
"Direct invocation requires --model or a model piped to standard input"
|
|
170
|
+
)
|
|
171
|
+
model_source = model_stream.read()
|
|
172
|
+
if not model_source:
|
|
173
|
+
raise InvalidInvocationError("Expected a Smithy JSON AST model")
|
|
174
|
+
|
|
175
|
+
return _Invocation(
|
|
176
|
+
artifact=args.artifact,
|
|
177
|
+
model_source=model_source,
|
|
178
|
+
output_dir=output_dir,
|
|
179
|
+
environment=environment,
|
|
180
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Smithy build environment provided to the code generator."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Self
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class PluginEnvironment:
|
|
16
|
+
"""Environment values supplied by Smithy's process-based run plugin."""
|
|
17
|
+
|
|
18
|
+
root_dir: Path | None = None
|
|
19
|
+
plugin_dir: Path | None = None
|
|
20
|
+
projection_name: str | None = None
|
|
21
|
+
artifact_name: str | None = None
|
|
22
|
+
includes_prelude: bool = False
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_environ(cls, environ: Mapping[str, str] | None = None) -> Self:
|
|
26
|
+
"""Load the Smithy run plugin environment from a mapping."""
|
|
27
|
+
|
|
28
|
+
source = os.environ if environ is None else environ
|
|
29
|
+
|
|
30
|
+
def path(name: str) -> Path | None:
|
|
31
|
+
return Path(value) if (value := source.get(name)) else None
|
|
32
|
+
|
|
33
|
+
return cls(
|
|
34
|
+
root_dir=path("SMITHY_ROOT_DIR"),
|
|
35
|
+
plugin_dir=path("SMITHY_PLUGIN_DIR"),
|
|
36
|
+
projection_name=source.get("SMITHY_PROJECTION_NAME"),
|
|
37
|
+
artifact_name=source.get("SMITHY_ARTIFACT_NAME"),
|
|
38
|
+
includes_prelude=source.get("SMITHY_INCLUDES_PRELUDE", "false").lower()
|
|
39
|
+
== "true",
|
|
40
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Exceptions raised by Smithy Python code generation."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SmithyPythonError(Exception):
|
|
7
|
+
"""Base exception for errors raised by the Smithy Python generator."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CodegenError(SmithyPythonError):
|
|
11
|
+
"""Raised when code generation fails."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InvalidInvocationError(SmithyPythonError):
|
|
15
|
+
"""Raised when command-line inputs do not form a valid invocation."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ModelError(CodegenError):
|
|
19
|
+
"""Raised when a Smithy model cannot be loaded or navigated."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class InvalidShapeIdError(ModelError, ValueError):
|
|
23
|
+
"""Raised when a string is not a valid Smithy shape ID."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ShapeNotFoundError(ModelError, LookupError):
|
|
27
|
+
"""Raised when a shape ID does not resolve to a shape in the model."""
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""The Smithy semantic model used by the Python code generator.
|
|
4
|
+
|
|
5
|
+
Use :func:`load_model` to parse a Smithy JSON AST into a :class:`Model`. The
|
|
6
|
+
loaded model is mixin-flattened, has ``apply`` entries merged, includes the
|
|
7
|
+
embedded Smithy prelude, and preserves the order of everything in the input.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from ..exceptions import InvalidShapeIdError, ModelError, ShapeNotFoundError
|
|
11
|
+
from ._loader import load_model
|
|
12
|
+
from ._model import Model
|
|
13
|
+
from ._shape_id import ShapeId
|
|
14
|
+
from ._shapes import (
|
|
15
|
+
EnumShape,
|
|
16
|
+
IntEnumShape,
|
|
17
|
+
JsonValue,
|
|
18
|
+
ListShape,
|
|
19
|
+
MapShape,
|
|
20
|
+
MemberShape,
|
|
21
|
+
Node,
|
|
22
|
+
OperationShape,
|
|
23
|
+
ResourceShape,
|
|
24
|
+
ServiceShape,
|
|
25
|
+
Shape,
|
|
26
|
+
ShapeType,
|
|
27
|
+
SimpleShape,
|
|
28
|
+
StructureShape,
|
|
29
|
+
UnionShape,
|
|
30
|
+
to_json,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"EnumShape",
|
|
35
|
+
"IntEnumShape",
|
|
36
|
+
"InvalidShapeIdError",
|
|
37
|
+
"JsonValue",
|
|
38
|
+
"ListShape",
|
|
39
|
+
"MapShape",
|
|
40
|
+
"MemberShape",
|
|
41
|
+
"Model",
|
|
42
|
+
"ModelError",
|
|
43
|
+
"Node",
|
|
44
|
+
"OperationShape",
|
|
45
|
+
"ResourceShape",
|
|
46
|
+
"ServiceShape",
|
|
47
|
+
"Shape",
|
|
48
|
+
"ShapeId",
|
|
49
|
+
"ShapeNotFoundError",
|
|
50
|
+
"ShapeType",
|
|
51
|
+
"SimpleShape",
|
|
52
|
+
"StructureShape",
|
|
53
|
+
"UnionShape",
|
|
54
|
+
"load_model",
|
|
55
|
+
"to_json",
|
|
56
|
+
]
|