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.
@@ -0,0 +1,5 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """A Smithy code generator for Python clients and types."""
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,7 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from .cli import main
5
+
6
+ if __name__ == "__main__":
7
+ raise SystemExit(main())
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
+ ]