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
30 changes: 10 additions & 20 deletions src/furu/_batched.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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]: ...
1 change: 1 addition & 0 deletions src/furu/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,)]

Expand Down Expand Up @@ -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)]
2 changes: 1 addition & 1 deletion tests/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading