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
4 changes: 3 additions & 1 deletion codegen-output-py/types/.smithy-python-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"files": {
"src/example_types/_private/schemas.py": "d0f35e8979c4aa9d49a1d9c0c8efeb860420bc0ceab3ba3edcc9488c053e9110"
"src/example_types/_private/schemas.py": "d0f35e8979c4aa9d49a1d9c0c8efeb860420bc0ceab3ba3edcc9488c053e9110",
"src/example_types/models.py": "6042e2e6454b40fef929d96cb7608ae8c591ac7205577f14337435718bb7c077",
"src/example_types/models.pyi": "bb3d59e91999a62438fce52fe3be551c63f998565e4f9640e4fb1e275b972b14"
},
"version": 1
}
75 changes: 75 additions & 0 deletions codegen-output-py/types/src/example_types/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from __future__ import annotations

from smithy_core._dynamic_models import ModelRegistry
from smithy_core.exceptions import ModeledError

from ._private import schemas


class ServiceError(ModeledError):
"""
Base error for all errors in the service.

Some exceptions do not extend from this class, including synthetic,
implicit, and shared exception types.
"""


_MODEL_INDEX = {
"GetExampleInput": "GET_EXAMPLE_INPUT",
"GetExampleOutput": "GET_EXAMPLE_OUTPUT",
"ExampleError": "EXAMPLE_ERROR",
"RenamedExample": "RENAMED_EXAMPLE",
"ExampleValue": "EXAMPLE_VALUE",
"ExampleValueText": "EXAMPLE_VALUE",
"ExampleValueNumber": "EXAMPLE_VALUE",
"ExampleValueEmpty": "EXAMPLE_VALUE",
"ExampleValueUnknown": "EXAMPLE_VALUE",
"Status": "STATUS",
"Code": "CODE",
}

_RENAMES = {
"RenamedExample": "RENAMED_EXAMPLE",
"ExampleValue": "EXAMPLE_VALUE",
}

_TARGET_NAMES = {
"example#Example": "RenamedExample",
"example#Value": "ExampleValue",
}

_MODEL_NAMES = (*tuple(_MODEL_INDEX), "Unit")
__all__ = ("ServiceError", *_MODEL_NAMES)


_registry = ModelRegistry(
schemas,
renames=_RENAMES,
index=_MODEL_INDEX,
target_names=_TARGET_NAMES,
shape_names=frozenset(
{
"Code",
"ExampleError",
"ExampleValue",
"GetExampleInput",
"GetExampleOutput",
"RenamedExample",
"Status",
"Unit",
}
),
service_error="ServiceError",
)
del schemas, ModelRegistry, ModeledError


def __getattr__(name: str):
value = _registry.build(name)
globals()[name] = value
return value


def __dir__() -> list[str]:
return sorted({*globals(), *__all__})
135 changes: 135 additions & 0 deletions codegen-output-py/types/src/example_types/models.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from __future__ import annotations

from dataclasses import dataclass
from enum import IntEnum, StrEnum
from typing import Self

from smithy_core.deserializers import ShapeDeserializer
from smithy_core.documents import Document
from smithy_core.exceptions import ModeledError
from smithy_core.response import ResponseMetadata
from smithy_core.serializers import ShapeSerializer
from smithy_core.types import UnknownEnumMixin

__all__ = [
"Code",
"ExampleError",
"ExampleValue",
"ExampleValueEmpty",
"ExampleValueNumber",
"ExampleValueText",
"ExampleValueUnknown",
"GetExampleInput",
"GetExampleOutput",
"RenamedExample",
"ServiceError",
"Status",
"Unit",
]

class _SerdeShape:
def serialize(self, serializer: ShapeSerializer) -> None: ...
def serialize_members(self, serializer: ShapeSerializer) -> None: ...
@classmethod
def deserialize(cls, deserializer: ShapeDeserializer) -> Self: ...

class ServiceError(ModeledError):
"""
Base error for all errors in the service.
Some exceptions do not extend from this class, including synthetic,
implicit, and shared exception types.
"""

@dataclass(kw_only=True)
class GetExampleInput(_SerdeShape):
"Input for retrieving an example."

id: str | None = ...

@dataclass(kw_only=True)
class GetExampleOutput(_SerdeShape):
"Dataclass for GetExampleOutput structure."

example: RenamedExample | None = ...
value: ExampleValue | None = ...
response_metadata: ResponseMetadata = ...
"""
Metadata about the response that produced this output.
Use this to recover the request identifiers a service's support team
needs in order to investigate a call. Members of the metadata are
individually optional.
"""

@dataclass(kw_only=True)
class ExampleError(ServiceError, _SerdeShape):
"An example modeled error."

retry_after_seconds: int | None = ...
response_metadata: ResponseMetadata = ...
"""
Metadata about the response that produced this output.
Use this to recover the request identifiers a service's support team
needs in order to investigate a call. Members of the metadata are
individually optional.
"""

@dataclass(kw_only=True)
class RenamedExample(_SerdeShape):
"A representative generated structure."

name: str
"The example name."
count: int | None = ...
tags: list[str] | None = ...
attributes: dict[str, Document] | None = ...
choice: ExampleValue | None = ...
status: str | None = ...
code: int | None = ...

@dataclass
class ExampleValueText(_SerdeShape):
value: str

@dataclass
class ExampleValueNumber(_SerdeShape):
value: int

@dataclass
class ExampleValueEmpty(_SerdeShape):
value: Unit

@dataclass
class ExampleValueUnknown(_SerdeShape):
"""
Represents an unknown variant.
If you receive this value, you will need to update your library to
receive the parsed value.
This value may not be deliberately sent.
"""

tag: str

ExampleValue = (
ExampleValueText | ExampleValueNumber | ExampleValueEmpty | ExampleValueUnknown
)
"A representative generated union."

class _ExampleValueDeserializer:
def deserialize(self, deserializer: ShapeDeserializer) -> ExampleValue: ...

class Status(UnknownEnumMixin, StrEnum):
ACTIVE = "active"
INACTIVE = "inactive"

class Code(UnknownEnumMixin, IntEnum):
OK = 200
NOT_FOUND = 404

@dataclass(kw_only=True)
class Unit(_SerdeShape):
pass
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ public String toMemberName(MemberShape shape) {
}

var memberName = escaper.escapeMemberName(CaseUtils.toSnakeCase(shape.getMemberName()));
if (memberName.startsWith("__") && !memberName.endsWith("__")) {
memberName += "___";
}

// Escape words that are only reserved for error members.
if (shape.hasTrait(ErrorTrait.class)) {
Expand All @@ -170,6 +173,16 @@ && carriesResponseMetadata(container)) {
}

if (container.isEnumShape() || container.isIntEnumShape()) {
if (memberName.startsWith("__")
&& memberName.endsWith("__")
&& !memberName.endsWith("___")) {
memberName += "_";
} else if (memberName.startsWith("_")
&& !memberName.startsWith("__")
&& memberName.endsWith("_")
&& !memberName.endsWith("__")) {
memberName += "_";
}
memberName = memberName.toUpperCase(Locale.ENGLISH);
}
return memberName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,17 @@ str
# that are likely imported in the generated code (e.g. datetime)
# We only escape the types we use.
datetime

# Generated model runtime methods.
serialize
serialize_members
deserialize

# Dataclass-generated methods and hooks.
__init__
__post_init__
__repr__
__annotations__

# Generated model runtime methods.
_smithy_default
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,68 @@ public void testUnrelatedMembersOnOutputsAndErrorsAreUnaffected() {
assertEquals("retry_after", memberName(provider, model, "ThingError$retryAfter"));
}

@Test
public void testDataclassHooksAreEscaped() {
Model model = loadModel("""
$version: "2"
namespace smithy.example

service TestService {
version: "2024-01-01"
operations: [GetThing]
}

operation GetThing {
input: GetThingInput
}

structure GetThingInput {
__init__: String
__post_init__: String
_smithy_default: String
__repr__: String
__annotations__: String
__value: String
}
""");
PythonSymbolProvider provider = createProvider(model);

assertEquals("__init___", memberName(provider, model, "GetThingInput$__init__"));
assertEquals("__post_init___", memberName(provider, model, "GetThingInput$__post_init__"));
assertEquals("_smithy_default_", memberName(provider, model, "GetThingInput$_smithy_default"));
assertEquals("__repr___", memberName(provider, model, "GetThingInput$__repr__"));
assertEquals("__annotations___", memberName(provider, model, "GetThingInput$__annotations__"));
assertEquals("__value___", memberName(provider, model, "GetThingInput$__value"));
}

@Test
public void testEnumSunderNamesAreEscaped() {
Model model = loadModel("""
$version: "2"
namespace smithy.example

service TestService {
version: "2024-01-01"
}

enum StringValues {
_VALUE_ = "value"
__DUNDER__ = "dunder"
}

intEnum IntValues {
_VALUE_ = 1
__DUNDER__ = 2
}
""");
PythonSymbolProvider provider = createProvider(model);

assertEquals("_VALUE__", memberName(provider, model, "StringValues$_VALUE_"));
assertEquals("_VALUE__", memberName(provider, model, "IntValues$_VALUE_"));
assertEquals("__DUNDER___", memberName(provider, model, "StringValues$__DUNDER__"));
assertEquals("__DUNDER___", memberName(provider, model, "IntValues$__DUNDER__"));
}

private static final String RESPONSE_METADATA_MODEL = """
$version: "2"
namespace smithy.example
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "enhancement",
"description": "Added optional full shape ID target mappings to `ModelRegistry` so generators can resolve renamed and same-named cross-namespace model targets without ambiguous local-name lookups."
}
Loading
Loading