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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "relattrs"
version = "0.0.5"
version = "1.0.0"
description = "Toolkit for working with nth order relational attributes in Python classes."
readme = "README.rst"
license = { text = "MIT" }
Expand Down
17 changes: 11 additions & 6 deletions relattrs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Any, Optional


def rgetattr(obj: object, rattr: str, *default, sep: Optional[str] = None) -> Any:
def rgetattr(obj: object, rattr: str, /, *default, sep: Optional[str] = None) -> Any:
"""
Recursively gets an attribute from an object based on a dotted string representation.

Expand Down Expand Up @@ -39,7 +39,7 @@ def rgetattr(obj: object, rattr: str, *default, sep: Optional[str] = None) -> An
return reduce(getattr, rattr, obj)


def rhasattr(obj: object, rattr: str, sep: Optional[str] = None) -> bool:
def rhasattr(obj: object, rattr: str, /, *, sep: Optional[str] = None) -> bool:
"""
Recursively checks if an object has an attribute based on a dotted string representation.

Expand All @@ -65,11 +65,16 @@ def rhasattr(obj: object, rattr: str, sep: Optional[str] = None) -> bool:
"""

rattr = rattr.split(sep or ".")
obj = reduce(getattr, rattr[:-1], obj)
return hasattr(obj, rattr[-1])
try:
obj = reduce(getattr, rattr[:-1], obj)
return hasattr(obj, rattr[-1])
except AttributeError:
return False


def rsetattr(obj: object, rattr: str, val: Any, sep: Optional[str] = None) -> None:
def rsetattr(
obj: object, rattr: str, val: Any, /, *, sep: Optional[str] = None
) -> None:
"""
Recursively sets an attribute on an object based on a dotted string representation.

Expand All @@ -96,7 +101,7 @@ def rsetattr(obj: object, rattr: str, val: Any, sep: Optional[str] = None) -> No
setattr(obj, rattr[-1], val)


def rdelattr(obj: object, rattr: str, sep: Optional[str] = None) -> None:
def rdelattr(obj: object, rattr: str, /, *, sep: Optional[str] = None) -> None:
"""
Recursively deletes an attribute from an object based on a dotted string representation.

Expand Down
75 changes: 45 additions & 30 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,80 +29,95 @@ def container():

@pytest.mark.parametrize("sep", separators)
@pytest.mark.parametrize(
"attr_path, expected",
("attr_path", "expected"),
[
(["outer", "inner", "value"], 42),
(["outer", "inner", "non_existent"], AttributeError),
(["outer", "non_existent", "value"], AttributeError),
(["simple_value"], 10),
(["non_existent"], AttributeError),
([""], AttributeError),
],
)
def test_rgetattr(container, sep, attr_path, expected):
attr_path = sep.join(attr_path)
if expected is AttributeError:
# with default value
assert rgetattr(container, attr_path, "default", sep=sep) == "default"

# whitout default value
with pytest.raises(AttributeError):
rgetattr(container, attr_path, sep=sep)

# with default value
assert rgetattr(container, attr_path, "default", sep=sep) == "default"

else:
assert rgetattr(container, attr_path, sep=sep) == expected


@pytest.mark.parametrize("sep", separators)
@pytest.mark.parametrize(
"attr_path, expected",
("attr_path", "expected"),
[
(["outer", "inner", "value"], True),
(["outer", "inner", "non_existent"], False),
(["outer", "non_existent", "value"], False),
(["simple_value"], True),
(["non_existent"], False),
([""], False),
],
)
def test_rhasattr(container, sep, attr_path, expected):
attr_path = sep.join(attr_path)
assert rhasattr(container, attr_path, sep) == expected
assert rhasattr(container, attr_path, sep=sep) == expected


@pytest.mark.parametrize("sep", separators)
@pytest.mark.parametrize(
"attr_path, value",
("attr_path", "value", "raises"),
[
(["outer", "inner", "value"], 100),
(["outer", "inner", "new_attr"], "test"),
(["simple_value"], 20),
(["new_simple_attr"], "simple_test"),
(["outer", "inner", "value"], 100, False),
(["outer", "inner", "new_attr"], "test", False),
(["outer", "non_existent", "value"], 200, True),
(["simple_value"], 20, False),
(["new_simple_attr"], "simple_test", False),
([""], "test", False),
],
)
def test_rsetattr(container, sep, attr_path, value):
def test_rsetattr(container, sep, attr_path, value, raises):
attr_path = sep.join(attr_path)
rsetattr(container, attr_path, value, sep=sep)
assert rgetattr(container, attr_path, sep=sep) == value
if raises:
with pytest.raises(AttributeError):
rsetattr(container, attr_path, value, sep=sep)

else:
rsetattr(container, attr_path, value, sep=sep)
assert rgetattr(container, attr_path, sep=sep) == value


@pytest.mark.parametrize("sep", separators)
@pytest.mark.parametrize(
"attr_path",
("attr_path", "raises"),
[
["outer", "inner", "value"],
["outer", "inner", "non_existent"],
["simple_value"],
["non_existent"],
(["outer", "inner", "value"], False),
(["outer", "inner", "non_existent"], True),
(["outer", "non_existent", "value"], True),
(["simple_value"], False),
(["non_existent"], True),
([""], True),
],
)
def test_rdelattr(container, sep, attr_path):
def test_rdelattr(container, sep, attr_path, raises):
attr_path = sep.join(attr_path)

if "non_existent" in attr_path:
if raises:
with pytest.raises(AttributeError):
rdelattr(container, attr_path, sep)
rdelattr(container, attr_path, sep=sep)

else:
rdelattr(container, attr_path, sep)
assert not rhasattr(container, attr_path, sep)

temp_attr = "outer.inner.temp" if sep == "." else "outer__inner__temp"
rsetattr(container, temp_attr, "temporary", sep)
assert rhasattr(container, temp_attr, sep)
rdelattr(container, temp_attr, sep)
assert not rhasattr(container, temp_attr, sep)
rdelattr(container, attr_path, sep=sep)
assert not rhasattr(container, attr_path, sep=sep)

temp_attr = sep.join(["outer", "inner", "temp_attr"])
rsetattr(container, temp_attr, "temporary", sep=sep)
assert rhasattr(container, temp_attr, sep=sep)
rdelattr(container, temp_attr, sep=sep)
assert not rhasattr(container, temp_attr, sep=sep)
Loading