From 6f88aedfbb938c60600b75e273f40c2fbcf57b9b Mon Sep 17 00:00:00 2001 From: Herman Brunborg Date: Sun, 2 Aug 2026 09:15:16 -0700 Subject: [PATCH] Simplify batched creation access - Use the shared public create function for batched lists - Keep instance create access for single-object batches --- src/furu/_batched.py | 30 ++++++++++-------------------- src/furu/core.py | 1 + tests/test_core.py | 7 ++++--- tests/test_typing.py | 2 +- 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/furu/_batched.py b/src/furu/_batched.py index 90011dbe..a5b156bd 100644 --- a/src/furu/_batched.py +++ b/src/furu/_batched.py @@ -1,8 +1,7 @@ from __future__ import annotations -import functools from collections.abc import Callable, Hashable -from typing import Any, NamedTuple, overload +from typing import TYPE_CHECKING, Any, NamedTuple, overload class _BatchedHook(NamedTuple): @@ -26,23 +25,14 @@ def __call__[S, T]( return _BatchedCreate(func, self.batch_fn) -class _BatchedCreate[S, T]: - def __init__( - self, - func: Callable[[list[S]], list[T]], - batch_fn: Callable[[Any], tuple[Hashable, int]], - /, - ) -> None: - self.func = func - self.batch_fn = batch_fn +class _BatchedCreate[S, T](NamedTuple): + func: Callable[[list[S]], list[T]] + batch_fn: Callable[[Any], tuple[Hashable, int]] + + if TYPE_CHECKING: - @overload - def __get__(self, obj: None, _objtype: type, /) -> Callable[[list[S]], list[T]]: ... - @overload - def __get__(self, obj: S, _objtype: type, /) -> Callable[[], T]: ... - def __get__(self, obj: Any, _objtype: type | None = None, /) -> Any: - from furu.execution.load_or_create import _load_or_create + @overload + def __get__(self, obj: None, _objtype: type, /) -> Callable[[S], T]: ... - if obj is None: - return _load_or_create - return functools.partial(_load_or_create, obj) + @overload + def __get__(self, obj: S, _objtype: type, /) -> Callable[[], T]: ... diff --git a/src/furu/core.py b/src/furu/core.py index dcdf2628..143dac3f 100644 --- a/src/furu/core.py +++ b/src/furu/core.py @@ -139,6 +139,7 @@ def __init_subclass__(cls, **kwargs: Any) -> None: ) case _BatchedCreate() as hook: cls._furu_create_hook = _BatchedHook(hook.func, hook.batch_fn) + del cls.create # unshadow the inherited create verb case hook: cls._furu_create_hook = hook del cls.create # unshadow the inherited create verb diff --git a/tests/test_core.py b/tests/test_core.py index 4f75007b..591666c7 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1890,6 +1890,7 @@ def test_single_object_on_batch_only_class_uses_create_batched() -> None: def test_instance_access_on_batched_create_runs_a_group_of_one() -> None: + assert BatchOnlyValue.create is Spec.create assert BatchOnlyValue(key=7).create() == "batch:7" assert BatchOnlyValue.batch_calls == [(7,)] @@ -2286,11 +2287,11 @@ def test_create_publicly_loads_or_computes_result() -> None: assert CountedSingleValue.create_calls == [99] -def test_class_access_on_batched_create_is_the_batch_verb() -> None: +def test_public_create_batches_lists() -> None: objs = [BatchOnlyValue(key=1), BatchOnlyValue(key=2)] - assert BatchOnlyValue.create(objs) == ["batch:1", "batch:2"] + assert furu.create(objs) == ["batch:1", "batch:2"] assert BatchOnlyValue.batch_calls == [(1, 2)] # A second call is served from cache without invoking the hook again. - assert BatchOnlyValue.create(objs) == ["batch:1", "batch:2"] + assert furu.create(objs) == ["batch:1", "batch:2"] assert BatchOnlyValue.batch_calls == [(1, 2)] diff --git a/tests/test_typing.py b/tests/test_typing.py index 22676e02..26c60a68 100644 --- a/tests/test_typing.py +++ b/tests/test_typing.py @@ -69,7 +69,7 @@ class TypingRefOutput: assert_type(furu.create([parent.cached_child]), list[int]) assert_type(TypingChild().create(), int) assert_type(TypingBatched(key=1).create(), str) - assert_type(TypingBatched.create([TypingBatched(key=1)]), list[str]) + assert_type(furu.create([TypingBatched(key=1)]), list[str]) assert_type(typed_letter_count(source="banana", letter="a"), furu.Spec[int]) assert_type(typed_letter_count(source="banana", letter="a").create(), int) assert_type(