|
1 | 1 | from datetime import datetime |
2 | | -from typing import Annotated, Literal |
| 2 | +from typing import Annotated, Any, Literal |
3 | 3 |
|
4 | | -from pydantic import BaseModel, ConfigDict, Field, field_validator |
| 4 | +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator |
| 5 | + |
| 6 | + |
| 7 | +def reject_explicit_null(data: Any, fields: tuple[str, ...]) -> Any: |
| 8 | + if isinstance(data, dict): |
| 9 | + null_fields = [field for field in fields if field in data and data[field] is None] |
| 10 | + if null_fields: |
| 11 | + raise ValueError(f"{', '.join(null_fields)} must be omitted instead of null") |
| 12 | + return data |
5 | 13 |
|
6 | 14 |
|
7 | 15 | class Actor(BaseModel): |
8 | 16 | model_config = ConfigDict(extra="forbid") |
9 | 17 | id: str = Field(min_length=1, max_length=256) |
10 | 18 | type: Literal["human", "agent", "service", "workflow"] |
11 | | - displayName: str | None = Field(default=None, min_length=1, max_length=256) |
| 19 | + displayName: str | None = Field( |
| 20 | + default=None, min_length=1, max_length=256, exclude_if=lambda value: value is None |
| 21 | + ) |
| 22 | + |
| 23 | + @model_validator(mode="before") |
| 24 | + @classmethod |
| 25 | + def reject_null_display_name(cls, data: Any) -> Any: |
| 26 | + return reject_explicit_null(data, ("displayName",)) |
12 | 27 |
|
13 | 28 |
|
14 | 29 | class TraceContext(BaseModel): |
15 | 30 | model_config = ConfigDict(extra="forbid") |
16 | 31 | traceparent: str = Field(pattern=r"^[\da-f]{2}-[\da-f]{32}-[\da-f]{16}-[\da-f]{2}$") |
17 | | - tracestate: str | None = Field(default=None, max_length=512) |
| 32 | + tracestate: str | None = Field( |
| 33 | + default=None, max_length=512, exclude_if=lambda value: value is None |
| 34 | + ) |
| 35 | + |
| 36 | + @model_validator(mode="before") |
| 37 | + @classmethod |
| 38 | + def reject_null_tracestate(cls, data: Any) -> Any: |
| 39 | + return reject_explicit_null(data, ("tracestate",)) |
18 | 40 |
|
19 | 41 |
|
20 | 42 | class LifecycleMetadata(BaseModel): |
@@ -51,7 +73,14 @@ class RunEvent(LifecycleMetadata): |
51 | 73 | eventType: str = Field(min_length=1, max_length=128) |
52 | 74 | sequence: int = Field(ge=0) |
53 | 75 | status: Literal["queued", "running", "succeeded", "failed", "cancelled"] |
54 | | - message: str | None = Field(default=None, max_length=5_000) |
| 76 | + message: str | None = Field( |
| 77 | + default=None, max_length=5_000, exclude_if=lambda value: value is None |
| 78 | + ) |
| 79 | + |
| 80 | + @model_validator(mode="before") |
| 81 | + @classmethod |
| 82 | + def reject_null_message(cls, data: Any) -> Any: |
| 83 | + return reject_explicit_null(data, ("message",)) |
55 | 84 |
|
56 | 85 |
|
57 | 86 | class WorkflowResult(BaseModel): |
|
0 commit comments