Skip to content

_diff

_diff

Change detection utilities for the event polling system.

diff_dicts

diff_dicts(
    old: dict[str, Any],
    new: dict[str, Any],
    *,
    include_fields: frozenset[str] | None = None,
    exclude_fields: frozenset[str] | None = None,
) -> list[Change]

Shallow diff of two dicts, returning a list of Change objects.

Compares top-level keys only. Nested dicts/lists are compared by equality.

PARAMETER DESCRIPTION
old

Previous snapshot.

TYPE: dict[str, Any]

new

Current snapshot.

TYPE: dict[str, Any]

include_fields

If set, only diff these fields.

TYPE: frozenset[str] | None DEFAULT: None

exclude_fields

If set, skip these fields.

TYPE: frozenset[str] | None DEFAULT: None

RETURNS DESCRIPTION
list[Change]

List of Change objects for fields that differ.

Source code in cocapi/events/_diff.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def diff_dicts(
    old: dict[str, Any],
    new: dict[str, Any],
    *,
    include_fields: frozenset[str] | None = None,
    exclude_fields: frozenset[str] | None = None,
) -> list[Change]:
    """Shallow diff of two dicts, returning a list of Change objects.

    Compares top-level keys only. Nested dicts/lists are compared by equality.

    Args:
        old: Previous snapshot.
        new: Current snapshot.
        include_fields: If set, only diff these fields.
        exclude_fields: If set, skip these fields.

    Returns:
        List of Change objects for fields that differ.
    """
    changes: list[Change] = []
    all_keys = set(old.keys()) | set(new.keys())

    for key in sorted(all_keys):
        if include_fields is not None and key not in include_fields:
            continue
        if exclude_fields is not None and key in exclude_fields:
            continue

        old_val = old.get(key)
        new_val = new.get(key)

        if old_val != new_val:
            changes.append(Change(field=key, old_value=old_val, new_value=new_val))

    return changes

diff_member_tags

diff_member_tags(
    old_members: list[dict[str, Any]],
    new_members: list[dict[str, Any]],
) -> tuple[
    list[dict[str, Any]],
    list[dict[str, Any]],
    list[tuple[dict[str, Any], dict[str, Any]]],
]

Compare member lists by tag to detect joins, leaves, and updates.

PARAMETER DESCRIPTION
old_members

Previous member list.

TYPE: list[dict[str, Any]]

new_members

Current member list.

TYPE: list[dict[str, Any]]

RETURNS DESCRIPTION
list[dict[str, Any]]

Tuple of (joined, left, updated) where:

list[dict[str, Any]]
  • joined: list of new member dicts
list[tuple[dict[str, Any], dict[str, Any]]]
  • left: list of old member dicts no longer present
tuple[list[dict[str, Any]], list[dict[str, Any]], list[tuple[dict[str, Any], dict[str, Any]]]]
  • updated: list of (old_member, new_member) tuples where data changed
Source code in cocapi/events/_diff.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def diff_member_tags(
    old_members: list[dict[str, Any]],
    new_members: list[dict[str, Any]],
) -> tuple[
    list[dict[str, Any]],
    list[dict[str, Any]],
    list[tuple[dict[str, Any], dict[str, Any]]],
]:
    """Compare member lists by tag to detect joins, leaves, and updates.

    Args:
        old_members: Previous member list.
        new_members: Current member list.

    Returns:
        Tuple of (joined, left, updated) where:
        - joined: list of new member dicts
        - left: list of old member dicts no longer present
        - updated: list of (old_member, new_member) tuples where data changed
    """
    old_by_tag: dict[str, dict[str, Any]] = {m["tag"]: m for m in old_members}
    new_by_tag: dict[str, dict[str, Any]] = {m["tag"]: m for m in new_members}

    old_tags = set(old_by_tag.keys())
    new_tags = set(new_by_tag.keys())

    joined = [new_by_tag[t] for t in sorted(new_tags - old_tags)]
    left = [old_by_tag[t] for t in sorted(old_tags - new_tags)]

    updated: list[tuple[dict[str, Any], dict[str, Any]]] = []
    for tag in sorted(old_tags & new_tags):
        if old_by_tag[tag] != new_by_tag[tag]:
            updated.append((old_by_tag[tag], new_by_tag[tag]))

    return joined, left, updated

diff_named_list

diff_named_list(
    old_items: list[dict[str, Any]],
    new_items: list[dict[str, Any]],
    key: str = "name",
    compare_field: str = "level",
) -> list[tuple[str, Any, Any]]

Diff two lists of dicts keyed by key, comparing compare_field.

Used for troops, spells, heroes, and equipment which all share a {"name": str, "level": int} structure.

PARAMETER DESCRIPTION
old_items

Previous list snapshot.

TYPE: list[dict[str, Any]]

new_items

Current list snapshot.

TYPE: list[dict[str, Any]]

key

Dict key used to identify items (default "name").

TYPE: str DEFAULT: 'name'

compare_field

Dict key whose value is compared (default "level").

TYPE: str DEFAULT: 'level'

RETURNS DESCRIPTION
list[tuple[str, Any, Any]]

List of (name, old_value, new_value) for items where

list[tuple[str, Any, Any]]

compare_field changed. New items appear as (name, None, value).

Source code in cocapi/events/_diff.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def diff_named_list(
    old_items: list[dict[str, Any]],
    new_items: list[dict[str, Any]],
    key: str = "name",
    compare_field: str = "level",
) -> list[tuple[str, Any, Any]]:
    """Diff two lists of dicts keyed by *key*, comparing *compare_field*.

    Used for troops, spells, heroes, and equipment which all share a
    ``{"name": str, "level": int}`` structure.

    Args:
        old_items: Previous list snapshot.
        new_items: Current list snapshot.
        key: Dict key used to identify items (default ``"name"``).
        compare_field: Dict key whose value is compared (default ``"level"``).

    Returns:
        List of ``(name, old_value, new_value)`` for items where
        *compare_field* changed.  New items appear as ``(name, None, value)``.
    """
    old_by_key: dict[str, dict[str, Any]] = {item[key]: item for item in old_items}
    new_by_key: dict[str, dict[str, Any]] = {item[key]: item for item in new_items}

    changes: list[tuple[str, Any, Any]] = []
    for name in sorted(new_by_key):
        new_val = new_by_key[name].get(compare_field)
        if name in old_by_key:
            old_val = old_by_key[name].get(compare_field)
            if old_val != new_val:
                changes.append((name, old_val, new_val))
        else:
            # Newly unlocked
            changes.append((name, None, new_val))

    return changes