Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

Workflow sandbox re-imports annotated_types, so pydantic silently drops constraints

Đang mở Phù hợp với người mới
#1,897 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
84/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
tooling

Hướng nghiên cứu

Bắt đầu trong temporalio/worker/workflow_sandbox/_restrictions.py tại passthrough_modules_with_temporal, sau đó xem lại các mẫu importer trong tests/worker/workflow_sandbox/test_importer.py. Sử dụng bản tái hiện pydantic và annotated_types được cung cấp để thêm một regression test; hoàn tất khi constraint vẫn được thực thi trong cả hai trường hợp host-first và sandbox-first mà không có cảnh báo import sandbox.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Summary

SandboxRestrictions.passthrough_modules_with_temporal passes pydantic and, since 1.33.0 (#1834), pydantic_core through, but not annotated_types. pydantic imports annotated_types inside functions, so each sandbox gets its own copy, and pydantic's lru_cached class → constraint map (_get_at_to_constraint_map) only knows the copy that filled it first. A constraint built from the other copy falls through to "ignore any other unknown metadata", with no error:

  • The host builds a constrained model first: a workflow's Annotated[int, annotated_types.Ge(1)] is not enforced.
  • A workflow builds one first: host-side Field(ge=0) and conint(ge=0) are not enforced for the rest of the process, activities included.

Each sandbox also pays a fresh import: a median 4.7 ms here, against 0.003 ms passed through. When that import happens during an activation, the sandbox warns Module annotated_types was imported after initial workflow load.

Repro

temporalio 1.33.0, pydantic 2.13.4, annotated-types 0.7.0, Python 3.12.3, macOS 26 arm64. It drives the sandbox's Importer the way tests/worker/workflow_sandbox/test_importer.py does.

"""pydantic drops constraints built from the sandbox's annotated_types copy, or the host's."""

import subprocess
import sys
import tempfile
from pathlib import Path

MODELS = """
from typing import Annotated
import annotated_types
from pydantic import BaseModel

class Positive(BaseModel):
    x: Annotated[int, annotated_types.Ge(1)]
"""


def case(first, passthrough):
    from pydantic import BaseModel, Field, ValidationError
    from temporalio.worker.workflow_sandbox import SandboxRestrictions
    from temporalio.worker.workflow_sandbox._importer import Importer
    from temporalio.worker.workflow_sandbox._restrictions import RestrictionContext

    restrictions = SandboxRestrictions.default
    if passthrough:
        restrictions = restrictions.with_passthrough_modules("annotated_types")

    def in_sandbox():
        with Importer(restrictions, RestrictionContext()).applied():
            import sandboxed_models
        return sandboxed_models.Positive

    def on_host():
        class Host(BaseModel):
            n: int = Field(ge=0)

        return Host

    if first == "host":
        host, sandboxed = on_host(), in_sandbox()
    else:
        sandboxed, host = in_sandbox(), on_host()

    def result(model, **values):
        try:
            model(**values)
        except ValidationError:
            return "enforced"
        return "DROPPED"

    return f"sandbox Ge(1): {result(sandboxed, x=0)}, host ge=0: {result(host, n=-1)}"


if __name__ == "__main__":
    if len(sys.argv) == 4:  # one case per fresh process: pydantic's cache is process-wide
        sys.path.insert(0, sys.argv[3])
        print(case(sys.argv[1], sys.argv[2] == "passthrough"))
    else:
        models = tempfile.mkdtemp()
        Path(models, "sandboxed_models.py").write_text(MODELS)
        for first in ("host", "sandbox"):
            for mode in ("default", "passthrough"):
                out = subprocess.run(
                    [sys.executable, __file__, first, mode, models],
                    capture_output=True,
                    text=True,
                    check=True,
                ).stdout.strip()
                print(f"{first + ' first,':<14} {mode + ':':<13} {out}")
host first,    default:      sandbox Ge(1): DROPPED, host ge=0: enforced
host first,    passthrough:  sandbox Ge(1): enforced, host ge=0: enforced
sandbox first, default:      sandbox Ge(1): enforced, host ge=0: DROPPED
sandbox first, passthrough:  sandbox Ge(1): enforced, host ge=0: enforced
Proposal

Add "annotated_types" next to "pydantic" and "pydantic_core" in passthrough_modules_with_temporal. It is a pure-Python module of constraint dataclasses that imports only the standard library and typing_extensions, with no I/O, time or randomness. temporalio's own StrandsPlugin and GoogleGenAIPlugin already pass it through; other users need a runner override such as SandboxRestrictions.default.with_passthrough_modules("annotated_types").

I can open the PR: the one-line change, a CHANGELOG entry, and a regression test in tests/worker/workflow_sandbox/test_importer.py built from the repro.

Ngôn ngữ chính
Python
Star
1.2k
Fork
245
Merge trung bình
2 ngày 17 giờ
Pull request đã merge (30 ngày)
38

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của temporalio/sdk-python

Tất cả issue của temporalio/sdk-python

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.