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
14 changes: 13 additions & 1 deletion src/latch_data_validation/data_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
Sequence as SequenceOld, # pyright: ignore[reportDeprecated]
)

from typing_extensions import NotRequired, Required, TypeForm, override
from typing_extensions import NotRequired, Required, TypeAliasType, TypeForm, override

__all__ = []

Expand Down Expand Up @@ -189,6 +189,18 @@ def _untraced_validate(
x: JsonValue, cls: TypeForm[T], *, type_vars: dict[int, TypeForm[object]]
) -> T:
# todo(maximsmol): improve error messages with generics
alias_origin = get_origin(cls)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already run get_origin later, don't run it twice

probably just move this code down unless it really needs to go first (in which case move the get_origin call up instead)

if isinstance(alias_origin, TypeAliasType):
type_vars2 = {**type_vars}
for parameter, argument in zip(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this duplicates code that handles typing.Generic, which should actually just be able to handle this directly anyway

alias_origin.__type_params__, get_args(cls), strict=True
):
type_vars2[id(parameter)] = argument
return _untraced_validate(x, alias_origin.__value__, type_vars=type_vars2)

if isinstance(cls, TypeAliasType):
return _untraced_validate(x, cls.__value__, type_vars=type_vars)

if isinstance(cls, TypeVar):
ref = type_vars.get(id(cls))
if ref is None:
Expand Down
34 changes: 33 additions & 1 deletion tests/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import typing
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from enum import Enum

import pytest
from syrupy.extensions.json import JSONSnapshotExtension
from typing_extensions import NotRequired, Required, TypedDict, TypeVar
from typing_extensions import NotRequired, Required, TypeAliasType, TypedDict, TypeVar

BrokenJsonArray: typing.TypeAlias = typing.Sequence["BrokenJsonValue"] # pyright: ignore[reportDeprecated]
BrokenJsonObject: typing.TypeAlias = typing.Mapping[str, "BrokenJsonValue"] # pyright: ignore[reportDeprecated]
Expand All @@ -18,6 +19,19 @@
BrokenJsonObject2 | BrokenJsonArray2 | str | int | float | bool | None
)

TestWaitingReason = TypeAliasType(
"TestWaitingReason", typing.Literal["workspace_capacity", "taiga_rate_limited"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this a much simpler test that doesn't also overlap with dataclasses support etc. unless there is a reason for it?

also don't pull in actual names/scenarios from other projects here

)


@dataclass
class AliasedRequest:
waiting_reason: TestWaitingReason | None


AliasT = TypeVar("AliasT")
TestBox = TypeAliasType("TestBox", list[AliasT], type_params=(AliasT,))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move these test resources into the test function rather than defining globally

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, test this only on Python 3.12+ since that's when the type A = B syntax was added

nobody is going around making TypeAliasType instances by hand, it used to just be

A: TypeAlias = B

which is transparent at runtime



from latch_data_validation.data_validation import (
DataValidationError,
Expand Down Expand Up @@ -157,6 +171,24 @@ class Test:
assert x.c.a == 2


def test_type_alias() -> None:
assert validate(
{"waiting_reason": "workspace_capacity"}, AliasedRequest
) == AliasedRequest(waiting_reason="workspace_capacity")
assert validate({"waiting_reason": None}, AliasedRequest) == AliasedRequest(
waiting_reason=None
)

with pytest.raises(DataValidationError):
_ = validate({"waiting_reason": "unknown"}, AliasedRequest)


def test_generic_type_alias() -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge this into test_type_alias

assert validate([1, 2], TestBox[int]) == [1, 2]
with pytest.raises(DataValidationError):
_ = validate([1, "two"], TestBox[int])


def test_forwardref(snapshot_json) -> None:
_ = validate({"a": 123}, JsonValue)

Expand Down
Loading