Skip to content

Commit

Permalink
Deprecate betty.functools.walk()
Browse files Browse the repository at this point in the history
  • Loading branch information
bartfeenstra committed May 5, 2024
1 parent 07e353b commit aac2e05
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 38 deletions.
5 changes: 2 additions & 3 deletions betty/extension/cotton_candy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from betty.config import Configuration
from betty.extension.cotton_candy.search import Index
from betty.extension.webpack import Webpack, WebpackEntrypointProvider
from betty.functools import walk
from betty.gui import GuiBuilder
from betty.html import CssProvider
from betty.jinja2 import (
Expand Down Expand Up @@ -427,9 +426,9 @@ def _person_timeline_events(person: Person, lifetime_threshold: int) -> Iterable
is_public,
(
# All ancestors.
*walk(person, "parents"),
*person.ancestors,
# All descendants.
*walk(person, "children"),
*person.descendants,
# All siblings.
*person.siblings,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
{%- endtrans -%}
{% endif %}
</p>
{% set ancestral_affiliation_names = person | walk('parents') | select('public') | map(attribute='names') | flatten | select('public') | map(attribute='affiliation') | reject('none') | unique | list | sort %}
{% set ancestral_affiliation_names = person.ancestors | select('public') | map(attribute='names') | flatten | select('public') | map(attribute='affiliation') | reject('none') | unique | list | sort %}
{% if ancestral_affiliation_names | length > 0 %}
<p>
{%- trans -%}
Expand Down Expand Up @@ -167,7 +167,7 @@
</p>
{% set ns = namespace(descendant_affiliation_names=[]) %}
{% for per_parent_child in family_children %}
{% set ns.descendant_affiliation_names = ns.descendant_affiliation_names + (per_parent_child | walk('children') | list + [per_parent_child]) | select('public') | map(attribute='names') | flatten | select('public') | map(attribute='affiliation') | reject('none') | list %}
{% set ns.descendant_affiliation_names = ns.descendant_affiliation_names + (per_parent_child.descendants | list + [per_parent_child]) | select('public') | map(attribute='names') | flatten | select('public') | map(attribute='affiliation') | reject('none') | list %}
{% endfor %}
{% set ns.descendant_affiliation_names = ns.descendant_affiliation_names | unique | list | sort | list %}
{% if ns.descendant_affiliation_names | length > 0 %}
Expand Down
5 changes: 5 additions & 0 deletions betty/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@
Awaitable,
)

from betty.warnings import deprecated

T = TypeVar("T")
U = TypeVar("U")


@deprecated(
"This function is deprecated as of Betty 0.3.4, and will be removed in Betty 0.4.x. Instead, use a custom function, tailored to your data type."
)
def walk(item: Any, attribute_name: str) -> Iterable[Any]:
"""
Walk over a graph of objects by following a single attribute.
Expand Down
14 changes: 13 additions & 1 deletion betty/model/ancestry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from __future__ import annotations

from collections.abc import MutableSequence
from collections.abc import MutableSequence, Iterator
from contextlib import suppress
from enum import Enum
from pathlib import Path
Expand Down Expand Up @@ -1851,6 +1851,12 @@ def entity_type_label(cls) -> Str:
def entity_type_label_plural(cls) -> Str:
return Str._("People")

@property
def ancestors(self) -> Iterator[Person]:
for parent in self.parents:
yield parent
yield from parent.ancestors

@property
def siblings(self) -> list[Person]:
siblings = []
Expand All @@ -1860,6 +1866,12 @@ def siblings(self) -> list[Person]:
siblings.append(sibling)
return siblings

@property
def descendants(self) -> Iterator[Person]:
for child in self.children:
yield child
yield from child.descendants

@property
def associated_files(self) -> Iterable[File]:
files = [
Expand Down
3 changes: 1 addition & 2 deletions betty/privatizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from datetime import datetime
from typing import Iterator, TypeAlias, Any

from betty.functools import walk
from betty.locale import DateRange, Date, Localizer
from betty.model import Entity
from betty.model.ancestry import (
Expand Down Expand Up @@ -199,7 +198,7 @@ def _determine_person_privacy(self, person: Person) -> None:
return

# If any descendant has any expired event, the person is considered not private.
for descendant in walk(person, "children"):
for descendant in person.descendants:
if self.has_expired(descendant, 1):
person.public = True
return
Expand Down
67 changes: 37 additions & 30 deletions betty/tests/test_functools.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from collections.abc import Awaitable, Callable, Iterable
from typing import Any
from typing import Any, Self

import pytest

from betty.functools import walk, slice_to_range, Do
from betty.warnings import BettyDeprecationWarning


class TestWalk:
class _Item:
def __init__(self, child: "TestWalk._Item | Iterable[TestWalk._Item] | None"):
def __init__(self, child: Self | Iterable[Self] | None):
self.child = child

@pytest.mark.parametrize(
Expand All @@ -21,44 +22,50 @@ def __init__(self, child: "TestWalk._Item | Iterable[TestWalk._Item] | None"):
],
)
async def test_with_invalid_attribute(self, item: Any) -> None:
with pytest.raises(AttributeError):
list(walk(item, "invalid_attribute_name"))
with pytest.warns(BettyDeprecationWarning):
with pytest.raises(AttributeError):
list(walk(item, "invalid_attribute_name"))

async def test_one_to_one_without_descendants(self) -> None:
item = self._Item(None)
actual = walk(item, "child")
expected: list[None] = []
assert expected == list(actual)
with pytest.warns(BettyDeprecationWarning):
item = self._Item(None)
actual = walk(item, "child")
expected: list[None] = []
assert expected == list(actual)

async def test_one_to_one_with_descendants(self) -> None:
grandchild = self._Item(None)
child = self._Item(grandchild)
item = self._Item(child)
actual = walk(item, "child")
expected = [child, grandchild]
assert expected == list(actual)
with pytest.warns(BettyDeprecationWarning):
grandchild = self._Item(None)
child = self._Item(grandchild)
item = self._Item(child)
actual = walk(item, "child")
expected = [child, grandchild]
assert expected == list(actual)

async def test_one_to_many_without_descendants(self) -> None:
item = self._Item([])
actual = walk(item, "child")
expected: list[None] = []
assert expected == list(actual)
with pytest.warns(BettyDeprecationWarning):
item = self._Item([])
actual = walk(item, "child")
expected: list[None] = []
assert expected == list(actual)

async def test_with_one_to_many_descendants(self) -> None:
grandchild = self._Item([])
child = self._Item([grandchild])
item = self._Item([child])
actual = walk(item, "child")
expected = [child, grandchild]
assert expected == list(actual)
with pytest.warns(BettyDeprecationWarning):
grandchild = self._Item([])
child = self._Item([grandchild])
item = self._Item([child])
actual = walk(item, "child")
expected = [child, grandchild]
assert expected == list(actual)

async def test_with_mixed_descendants(self) -> None:
grandchild = self._Item([])
child = self._Item(grandchild)
item = self._Item([child])
actual = walk(item, "child")
expected = [child, grandchild]
assert expected == list(actual)
with pytest.warns(BettyDeprecationWarning):
grandchild = self._Item([])
child = self._Item(grandchild)
item = self._Item([child])
actual = walk(item, "child")
expected = [child, grandchild]
assert expected == list(actual)


class TestSliceToRange:
Expand Down

0 comments on commit aac2e05

Please sign in to comment.