Skip to content

Releases: raceychan/ididi

Release v1.8.4

31 Dec 06:27

Choose a tag to compare

version 1.8.4

Fix:

Now Resolver.scope & Resolver.ascope no longer requires a parent scope.

This might help with solving the LookupError problem when creating new scopes in a new thread/new event loop.

Release v1.8.3

25 Dec 08:17

Choose a tag to compare

version 1.8.3

Fixes:
fix a bug where function dependency won't be properly parsed and cause return type to be treated as factory
e.g.

def get_user(user_id: str) -> Ignore[User]: 
    ...

in 1.8.2 get_user will be treated as factory of User whereas it should be treated as a function dep.

Release v1.8.2

24 Dec 14:17

Choose a tag to compare

version 1.8.2

Highlights

  • NodeMeta and use are now public exports, so applications can declare factories or metadata without reaching into private modules.
  • resolve_meta now preserves ignore flags and infers factories from Annotated[Service, use()], keeping ignored dependencies and default factories consistent during analysis and scope checks.

Details

  • ididi.__init__ re-exports NodeMeta and use alongside other public helpers.
  • Graph analysis and should_be_scoped respect NodeMeta(ignore=True) in nested Annotated metadata, so ignored parameters stay skipped and UnsolvableNodeError.__notes__ still include dependent/parameter context when resolution fails.
  • use() markers without explicit factories now default to the annotated type, matching earlier behavior while keeping the new metadata path intact.

Release v1.8.1

22 Dec 11:51

Choose a tag to compare

version 1.8.1

Highlights

  • Ignore[...] is now implemented via NodeMeta(ignore=True), so the legacy IGNORE_PARAM_MARK
    sentinel is gone. Any typing.Annotated blocks that previously used the constant can switch to
    ididi.Ignore (or embed their own NodeMeta(ignore=True) marker) with no behavioral change.
  • NodeMeta gained the new ignore flag, and all use(...) helpers / graph analysis logic now honor
    it when deciding whether a parameter should participate in dependency resolution.

Details

  • Removed IGNORE_PARAM_MARK from the public config surface and runtime checks.
  • Updated Ignore helper to expand to Annotated[T, NodeMeta(ignore=True)].
  • resolve_type_from_meta, Dependency.should_ignore, and _from_function now look for the new
    metadata instead of literal sentinels, so nested Annotated stacks keep functioning.
  • Tests and documentation were refreshed to showcase the new marker form.

Release v1.8.0

05 Dec 19:55

Choose a tag to compare

version 1.8.0

Minor typing improvements:

  • now use() without any arguments won't cause typing issue
  • flatten_annotated is a public method from utils.typing_utils

Release v1.7.9

23 Nov 21:37

Choose a tag to compare

fix

fix a bug where union of unsolveable type is not treated as unsolvable type

def test_is_unsolvable_type():
    assert is_unsolvable_type(str)
    assert is_unsolvable_type(int)
    assert is_unsolvable_type(list[str])
    assert is_unsolvable_type(dict[int, float])
    assert is_unsolvable_type(tuple[int, ...])
    assert is_unsolvable_type(set[bytes])
    assert is_unsolvable_type(Union[str, None])
    assert not is_unsolvable_type(User)

Release v1.7.8

23 Nov 21:23

Choose a tag to compare

Release v1.7.7

23 Nov 20:27

Choose a tag to compare

version 1.7.7

Highlights

  • Dependency.annotation now stores the exact annotation object (including typing.Annotated metadata), so analysis tooling can interrogate user-defined markers without recomputing them.
  • Dependency and DependentNode are now slot-based data classes—no __dict__—which shrinks each node and makes accidental attribute writes fail fast.

Details

  • Annotation preservation (tests/versions/test_v1_7_7.py:20-tests/versions/test_v1_7_7.py:30): running graph.analyze(get_user) keeps the original Annotated[str, meta(...)] object on node.dependencies[0].annotation, letting advanced callers recover the metadata that produced the dependency.
  • Slot-only nodes (tests/versions/test_v1_7_7.py:31-tests/versions/test_v1_7_7.py:35, ididi/_node.py): both Dependency and DependentNode now declare __slots__, so reaching for __dict__ raises AttributeError and per-node memory drops because Python no longer allocates dictionaries for them.
from dataclasses import dataclass
from typing import Annotated
import pytest

from ididi import Graph, Ignore

@dataclass
class Meta:
    name: str
    version: str

def meta(name: str, version: str) -> Meta:
    return Meta(name=name, version=version)

def get_user(query: Annotated[str, meta("query_param", "v1")]) -> Ignore[str]:
    return f"User({query})"

graph = Graph()
node = graph.analyze(get_user)
dependency = node.dependencies[0]

assert dependency.annotation == Annotated[str, meta("query_param", "v1")]
with pytest.raises(AttributeError):
    dependency.__dict__
with pytest.raises(AttributeError):
    node.__dict__

The preserved annotation makes it trivial to read custom metadata back out of analyzed dependencies, while __slots__ keeps heavily-used graph structures lean and predictable in large applications.

Release v1.7.6

31 Oct 11:24

Choose a tag to compare

version 1.7.6

Highlights

  • Node reuse now starts as "unspecified", giving decorator registration and use() hints a chance to agree before conflicts are raised.
  • Parameter-level reuse clashes now surface as ParamReusabilityConflictError, pointing directly at the factory parameter that introduced the mismatch.
  • use() return annotations propagate reuse flags to their nodes, so factory overrides can mark themselves reusable without extra bookkeeping.

Details

  • Missing-as-default reuse (tests/versions/test_v1_7_6.py:22): analyzing a dependency that uses use(get_user, reuse=False) flips the previously unspecified node to non-reusable, and a conflicting reuse=True hint now fails fast with ParamReusabilityConflictError.
  • Return annotations drive node config (tests/versions/test_v1_7_6.py:35): factories that return Annotated[T, use(reuse=True)] mark the target reusable immediately; later attempts to re-register them with reuse=False raise ConfigConflictError.
  • Explicit overrides stay authoritative (tests/versions/test_v1_7_6.py:50, tests/versions/test_v1_7_6.py:66): decorator-specified reuse continues to win, and any subsequent override that disagrees is rejected with ConfigConflictError.
  • Public is_provided helper (ididi/__init__.py:34): expose is_provided so callers can tell whether a node's reuse flag has been set before deciding on overrides.

Behavior Change

Nodes now keep their reuse flag unset until code explicitly opts in, because _reuse starts as the sentinel MISSING during node construction (ididi/_node.py:349-ididi/_node.py:392). When the same dependent is included again we run through DependentNode.update_reusability; any side that still has _reuse equal to MISSING simply adopts the provided value, so ConfigConflictError only fires when both registrations made an explicit, conflicting choice (ididi/graph.py:700-ididi/graph.py:708). use(...) markers participate in the same flow: they publish their reuse preference while analysis walks the parameter graph, and we only escalate to ParamReusabilityConflictError when an earlier declaration disagrees (ididi/graph.py:551-ididi/graph.py:563, tests/versions/test_v1_7_6.py:22-tests/versions/test_v1_7_6.py:47). The public is_provided helper lets applications check whether a node already committed to a reuse value before attempting overrides (ididi/__init__.py:34, tests/versions/test_v1_7_6.py:26, tests/versions/test_v1_7_6.py:72).

from typing import Annotated
import pytest

from ididi import Graph, use, is_provided
from ididi.errors import ConfigConflictError, ParamReusabilityConflictError

graph = Graph()

class User: ...

# First registration – leaves reuse as the sentinel MISSING
graph.node(User)
assert not is_provided(graph.nodes[User].reuse)

# A handler pins the dependency to reuse=False; this now succeeds
class LoginHandler:
    def __init__(self, user: Annotated[User, use(reuse=False)]):
        self.user = user

graph.analyze(LoginHandler)
assert graph.nodes[User].reuse is False  # value captured only now

# A conflicting handler tries to demand reuse=True; we now get a targeted error
class ProfileHandler:
    def __init__(self, user: Annotated[User, use(reuse=True)]):
        self.user = user

with pytest.raises(ParamReusabilityConflictError):
    graph.analyze(ProfileHandler)

# Direct overrides behave the same way: once reuse is explicit, any opposite
# declaration fails fast with ConfigConflictError.
with pytest.raises(ConfigConflictError):
    graph.node(User, reuse=True)

# If you really need an opposite policy, supply it before anyone else fixes reuse:
graph2 = Graph()
graph2.node(User, reuse=True)          # explicit upfront
class AuditHandler:
    def __init__(self, user: User): ...
graph2.analyze(AuditHandler)           # fine, reuse already decided

Previously graph.node(User) defaulted to a boolean reuse flag, so any later use(reuse=True)/use(reuse=False) annotation collided instantly. The sentinel-driven workflow leaves the decision open until a caller makes an explicit choice, keeping decorator registration, annotations, and overrides in sync and ensuring conflicts only occur when two intentional policies disagree.

Release v1.7.5

28 Oct 13:07

Choose a tag to compare

version 1.7.5

A quick patch to improve error message when reusability conflicts

    dg = Graph()

    class User: ...

    @dg.node
    def user_factory() -> Annotated[User, use(reuse=True)]:
        return User()

    class UserManager:
        def __init__(self, user: User):
            self.user = user


    assert dg.nodes[User].reuse

    @dg.node
    def user_manager_factory(user: Annotated[User, use(user_factory, reuse=False)]) -> UserManager:
        return UserManager(user)


    with pytest.raises(ParamReusabilityConflictError):
        dg.analyze(user_manager_factory)

This would show error message

E                   ididi.errors.ParamReusabilityConflictError: 
E                   user_manager_factory(Param[user: typing.Annotated[tests.test_graph.test_graph_analyze_reuse_dependentcy.<locals>.User, typing.Annotated[~T, '__ididi_use_factory__', <function test_graph_analyze_reuse_dependentcy.<locals>.user_factory at 0x7061322e2b80>, False]]]) has param conflict: 
E                       Existing node DependentNode(type: <class 'tests.test_graph.test_graph_analyze_reuse_dependentcy.<locals>.User'>, factory: <function test_graph_analyze_reuse_dependentcy.<locals>.user_factory at 0x7061322e2b80>, function_dependent: False, reuse: True) has config conflicts with DependentNode(type: <class 'tests.test_graph.test_graph_analyze_reuse_dependentcy.<locals>.User'>, factory: <function test_graph_analyze_reuse_dependentcy.<locals>.user_factory at 0x7061322e2b80>, function_dependent: False, reuse: False)