From 4ae8115139982f3d9bd2b40821df37a824976a31 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 30 Mar 2026 22:07:05 -0700 Subject: [PATCH] [DeferredDict] implement get/items/values for deferreddict --- masque/test/test_utils.py | 20 +++++++++++++++++++- masque/utils/deferreddict.py | 15 ++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/masque/test/test_utils.py b/masque/test/test_utils.py index f495285..45e347e 100644 --- a/masque/test/test_utils.py +++ b/masque/test/test_utils.py @@ -2,7 +2,7 @@ import numpy from numpy.testing import assert_equal, assert_allclose from numpy import pi -from ..utils import remove_duplicate_vertices, remove_colinear_vertices, poly_contains_points, rotation_matrix_2d, apply_transforms +from ..utils import remove_duplicate_vertices, remove_colinear_vertices, poly_contains_points, rotation_matrix_2d, apply_transforms, DeferredDict def test_remove_duplicate_vertices() -> None: @@ -86,3 +86,21 @@ def test_apply_transforms_advanced() -> None: # 2. rotate by outer rotation (pi/2): (10, 0) -> (0, 10) # 3. add outer offset (0, 0) -> (0, 10) assert_allclose(combined[0], [0, 10, pi / 2, 1, 1], atol=1e-10) + + +def test_deferred_dict_accessors_resolve_values_once() -> None: + calls = 0 + + def make_value() -> int: + nonlocal calls + calls += 1 + return 7 + + deferred = DeferredDict[str, int]() + deferred["x"] = make_value + + assert deferred.get("missing", 9) == 9 + assert deferred.get("x") == 7 + assert list(deferred.values()) == [7] + assert list(deferred.items()) == [("x", 7)] + assert calls == 1 diff --git a/masque/utils/deferreddict.py b/masque/utils/deferreddict.py index 31e6943..def9b10 100644 --- a/masque/utils/deferreddict.py +++ b/masque/utils/deferreddict.py @@ -1,5 +1,5 @@ from typing import TypeVar, Generic -from collections.abc import Callable +from collections.abc import Callable, Iterator from functools import lru_cache @@ -41,6 +41,19 @@ class DeferredDict(dict, Generic[Key, Value]): def __getitem__(self, key: Key) -> Value: return dict.__getitem__(self, key)() + def get(self, key: Key, default: Value | None = None) -> Value | None: + if key not in self: + return default + return self[key] + + def items(self) -> Iterator[tuple[Key, Value]]: + for key in self.keys(): + yield key, self[key] + + def values(self) -> Iterator[Value]: + for key in self.keys(): + yield self[key] + def update(self, *args, **kwargs) -> None: """ Update the DeferredDict. If a value is callable, it is used as a generator.