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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ jobs:
- uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8
- run: uv tool install poethepoet
- run: uv remove google-adk --optional google-adk
- run: uv add --dev --python 3.10 "googleapis-common-protos==1.70.0"
- run: uv add --python 3.10 "protobuf<4"
- run: uv sync --all-extras
- run: poe build-develop
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ to docs, or any other relevant information.
- AWS Lambda worker `configure` parameter supports sync, async, and async
generator style functions. This callback is invoked on the asyncio event
loop.
- Relaxed the protobuf dependency bounds to allow protobuf 7 where compatible
with the selected optional dependencies.

### Breaking Changes

Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ license-files = ["LICENSE"]
keywords = ["temporal", "workflow"]
dependencies = [
"nexus-rpc==1.4.0",
"protobuf>=3.20,<7.0.0",
"protobuf>=3.20,<8.0.0",
"python-dateutil>=2.8.2,<3 ; python_version < '3.11'",
"types-protobuf>=3.20,<7.0.0",
"types-protobuf>=3.20,<8.0.0",
"typing-extensions>=4.2.0,<5",
]
classifiers = [
Expand Down Expand Up @@ -74,7 +74,7 @@ dev = [
"openai-agents[litellm]>=0.14.0; python_version < '3.14'",
"litellm>=1.83.0",
"openinference-instrumentation-google-adk>=0.1.11",
"googleapis-common-protos==1.70.0",
"googleapis-common-protos>=1.75.0,<2",
"pytest-rerunfailures>=16.1",
"pytest-xdist>=3.6,<4",
"moto[s3,server]>=5",
Expand Down
1 change: 1 addition & 0 deletions scripts/_proto/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ COPY ./ ./

RUN mkdir -p ./temporalio/api
RUN uv remove google-adk --optional google-adk
RUN uv add --dev "googleapis-common-protos==1.70.0"
RUN uv add "protobuf<4"
RUN uv sync --all-extras
RUN uv run scripts/gen_protos.py
Expand Down
29 changes: 21 additions & 8 deletions scripts/gen_payload_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ def name_for(desc: Descriptor) -> str:
return desc.full_name.replace(".", "_")


def field_is_repeated(field: FieldDescriptor) -> bool:
return bool(
getattr(
field,
"is_repeated",
getattr(field, "label") == FieldDescriptor.LABEL_REPEATED,
)
)


def emit_loop(
field_name: str,
iter_expr: str,
Expand Down Expand Up @@ -290,17 +300,16 @@ def walk(self, desc: Descriptor) -> bool:
continue

# Repeated fields (including maps which are represented as repeated messages)
if field.label == FieldDescriptor.LABEL_REPEATED:
if (
field.message_type is not None
and field.message_type.GetOptions().map_entry
):
val_fd = field.message_type.fields_by_name.get("value")
if field_is_repeated(field):
message_type = field.message_type
if message_type is not None and message_type.GetOptions().map_entry:
val_fd = message_type.fields_by_name.get("value")
if (
val_fd is not None
and val_fd.type == FieldDescriptor.TYPE_MESSAGE
):
child_desc = val_fd.message_type
assert child_desc is not None
child_needed = self.walk(child_desc)
if child_needed:
has_payload = True
Expand All @@ -313,12 +322,13 @@ def walk(self, desc: Descriptor) -> bool:
)
)

key_fd = field.message_type.fields_by_name.get("key")
key_fd = message_type.fields_by_name.get("key")
if (
key_fd is not None
and key_fd.type == FieldDescriptor.TYPE_MESSAGE
):
child_desc = key_fd.message_type
assert child_desc is not None
child_needed = self.walk(child_desc)
if child_needed:
has_payload = True
Expand All @@ -331,14 +341,16 @@ def walk(self, desc: Descriptor) -> bool:
)
)
else:
assert message_type is not None
item = self._collect_repeated(
field.message_type, field, f"o.{field.name}"
message_type, field, f"o.{field.name}"
)
if item is not None:
has_payload = True
emit_items.append(item)
else:
child_desc = field.message_type
assert child_desc is not None
child_has_payload = self.walk(child_desc)
has_payload |= child_has_payload
if child_has_payload:
Expand All @@ -358,6 +370,7 @@ def walk(self, desc: Descriptor) -> bool:
first = True
for field in fields:
child_desc = field.message_type
assert child_desc is not None
child_has_payload = self.walk(child_desc)
has_payload |= child_has_payload
if child_has_payload:
Expand Down
4 changes: 3 additions & 1 deletion temporalio/converter/_failure_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,9 @@ def _nexus_failure_to_temporal_failure(
failure.metadata
and failure.metadata.get("type") == _TEMPORAL_FAILURE_PROTO_TYPE
):
google.protobuf.json_format.ParseDict(failure.details, temporal_failure)
google.protobuf.json_format.ParseDict(
dict(failure.details or {}), temporal_failure
)
else:
temporal_failure.application_failure_info.SetInParent()
temporal_failure.application_failure_info.type = "NexusFailure"
Expand Down
5 changes: 3 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
f"Expected {temporalio.__file__} to be in {sys.prefix}"
)

# Unless specifically overridden, we expect tests to run under protobuf 4.x/5.x lib
# Unless specifically overridden, we expect tests to run under protobuf 4.x/5.x/6.x/7.x lib
import google.protobuf

protobuf_version = google.protobuf.__version__
Expand All @@ -43,7 +43,8 @@
protobuf_version.startswith("4.")
or protobuf_version.startswith("5.")
or protobuf_version.startswith("6.")
), f"Expected protobuf 4.x/5.x/6.x, got {protobuf_version}"
or protobuf_version.startswith("7.")
), f"Expected protobuf 4.x/5.x/6.x/7.x, got {protobuf_version}"


def pytest_runtest_setup(item): # type: ignore[reportMissingParameterType]
Expand Down
27 changes: 21 additions & 6 deletions tests/nexus/test_temporal_system_nexus.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,13 @@ def _build_proto_sample(message_type: type[Message]) -> Message:

def _populate_proto_sample(message: Message, *, path: str = "value") -> None:
seen_oneofs: set[str] = set()
for field in message.DESCRIPTOR.fields:
for raw_field in message.DESCRIPTOR.fields:
field = cast(FieldDescriptor, raw_field)
if field.containing_oneof is not None:
if field.containing_oneof.name in seen_oneofs:
continue
seen_oneofs.add(field.containing_oneof.name)
if field.label == FieldDescriptor.LABEL_REPEATED:
if _field_is_repeated(field):
if (
field.message_type is not None
and field.message_type.GetOptions().map_entry
Expand Down Expand Up @@ -186,8 +187,10 @@ def _populate_proto_map_entry(
*,
path: str,
) -> None:
key_field = field.message_type.fields_by_name["key"]
value_field = field.message_type.fields_by_name["value"]
message_type = field.message_type
assert message_type is not None
key_field = message_type.fields_by_name["key"]
value_field = message_type.fields_by_name["value"]
key = _proto_scalar_sample(key_field, path=f"{path}.{field.name}.key")
container = getattr(message, field.name)
if value_field.cpp_type == FieldDescriptor.CPPTYPE_MESSAGE:
Expand Down Expand Up @@ -222,13 +225,25 @@ def _proto_scalar_sample(field: FieldDescriptor, *, path: str) -> Any:
):
return 1.5
if field.cpp_type == FieldDescriptor.CPPTYPE_ENUM:
for enum_value in field.enum_type.values:
enum_type = field.enum_type
assert enum_type is not None
for enum_value in enum_type.values:
if enum_value.number != 0:
return enum_value.number
return field.enum_type.values[0].number
return enum_type.values[0].number
raise TypeError(f"Unhandled proto scalar sample at {path}: {field!r}")


def _field_is_repeated(field: FieldDescriptor) -> bool:
return bool(
getattr(
field,
"is_repeated",
getattr(field, "label") == FieldDescriptor.LABEL_REPEATED,
)
)


@pytest.mark.parametrize(
"message_type",
[
Expand Down
4 changes: 2 additions & 2 deletions tests/worker/test_command_aware_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ def _get_workflow_command_protos_with_seq() -> Iterator[type[Any]]:
"""Get concrete classes of all workflow command protos with a seq field."""
for descriptor in workflow_commands_pb2.DESCRIPTOR.message_types_by_name.values():
if "seq" in descriptor.fields_by_name:
yield descriptor._concrete_class
yield getattr(descriptor, "_concrete_class")


def _get_workflow_activation_job_protos_with_seq() -> Iterator[type[Any]]:
"""Get concrete classes of all workflow activation job protos with a seq field."""
for descriptor in workflow_activation_pb2.DESCRIPTOR.message_types_by_name.values():
if "seq" in descriptor.fields_by_name:
yield descriptor._concrete_class
yield getattr(descriptor, "_concrete_class")
20 changes: 10 additions & 10 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading