Skip to content
Merged
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
9 changes: 5 additions & 4 deletions Justfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
python := ""
covcleanup := "true"

sync:
uv sync {{ if python != '' { '-p ' + python } else { '' } }} --all-groups --all-extras
# Sync the environment, to a particular version if provided. The `python` variable takes precedence over the argument.
sync version="":
uv sync {{ if python != '' { '-p ' + python } else if version != '' { '-p ' + version } else { '' } }} --all-groups --all-extras

lint:
uv run -p python3.13 --group lint ruff check src/ tests bench
Expand All @@ -13,8 +14,8 @@ test *args="-x --ff -n auto tests":

testall:
just python=python3.9 test
just python=pypy3.9 test
just python=python3.10 test
just python=pypy3.10 test
just python=python3.11 test
just python=python3.12 test
just python=python3.13 test
Expand All @@ -28,10 +29,10 @@ cov *args="-x --ff -n auto tests":
covall:
just python=python3.9 covcleanup=false cov
just python=python3.10 covcleanup=false cov
just python=pypy3.10 covcleanup=false cov
just python=python3.11 covcleanup=false cov
just python=python3.12 covcleanup=false cov
just python=python3.13 covcleanup=false cov
just python=pypy3.10 covcleanup=false cov
uv run coverage combine
uv run coverage report
@rm .coverage*
Expand Down
56 changes: 41 additions & 15 deletions tests/test_baseconverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,24 +141,53 @@ def handler(obj, _):


@pytest.mark.skipif(not is_py310_plus, reason="3.10+ union syntax")
@settings(suppress_health_check=[HealthCheck.too_slow])
@given(
simple_typed_classes(
defaults="never", newtypes=False, allow_nan=False, min_attrs=1
),
simple_typed_classes(
defaults="never", newtypes=False, allow_nan=False, min_attrs=1
),
unstructure_strats,
)
def test_310_union_field_roundtrip(cl_and_vals_a, cl_and_vals_b, strat):
def test_310_union_field_roundtrip_dict(cl_and_vals_a, cl_and_vals_b):
"""
Classes with union fields can be unstructured and structured.
"""
converter = BaseConverter(unstruct_strat=strat)
converter = BaseConverter()
cl_a, vals_a, kwargs_a = cl_and_vals_a
cl_b, _, _ = cl_and_vals_b
a_field_names = {a.name for a in fields(cl_a)}
b_field_names = {a.name for a in fields(cl_b)}

common_names = a_field_names & b_field_names
assume(len(a_field_names) > len(common_names))

@define
class C:
a: cl_a | cl_b

inst = C(a=cl_a(*vals_a, **kwargs_a))

assert inst == converter.structure(converter.unstructure(inst), C)


@pytest.mark.skipif(not is_py310_plus, reason="3.10+ union syntax")
@given(
simple_typed_classes(
defaults="never", newtypes=False, allow_nan=False, min_attrs=2, kw_only="never"
),
simple_typed_classes(
defaults="never", newtypes=False, allow_nan=False, min_attrs=1
),
)
def test_310_union_field_roundtrip_tuple(cl_and_vals_a, cl_and_vals_b):
"""
Classes with union fields can be unstructured and structured.
"""
converter = BaseConverter(unstruct_strat=UnstructureStrategy.AS_TUPLE)
cl_a, vals_a, kwargs_a = cl_and_vals_a
cl_b, _, _ = cl_and_vals_b
assume(strat is UnstructureStrategy.AS_DICT or not kwargs_a)
assert not kwargs_a
a_field_names = {a.name for a in fields(cl_a)}
b_field_names = {a.name for a in fields(cl_b)}

Expand All @@ -171,18 +200,15 @@ class C:

inst = C(a=cl_a(*vals_a, **kwargs_a))

if strat is UnstructureStrategy.AS_DICT:
assert inst == converter.structure(converter.unstructure(inst), C)
else:
# Our disambiguation functions only support dictionaries for now.
with pytest.raises(StructureHandlerNotFoundError):
converter.structure(converter.unstructure(inst), C)
# Our disambiguation functions only support dictionaries for now.
with pytest.raises(StructureHandlerNotFoundError):
converter.structure(converter.unstructure(inst), C)

def handler(obj, _):
return converter.structure(obj, cl_a)
def handler(obj, _):
return converter.structure(obj, cl_a)

converter.register_structure_hook(cl_a | cl_b, handler)
assert inst == converter.structure(converter.unstructure(inst), C)
converter.register_structure_hook(cl_a | cl_b, handler)
assert inst == converter.structure(converter.unstructure(inst), C)


@given(simple_typed_classes(defaults="never", newtypes=False, allow_nan=False))
Expand Down