Skip to content

Commit

Permalink
Rename flow.check_context_str to flow.contains. Now it accepts string…
Browse files Browse the repository at this point in the history
…s without dots and allows values to be compared with a string. Add it to the documentation. Improve docs.
  • Loading branch information
ynikitenko committed Apr 28, 2020
1 parent f6231ec commit 2f2f15d
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 33 deletions.
2 changes: 2 additions & 0 deletions docs/source/context.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Context
.. currentmodule:: lena.context.functions
.. autosummary::

contains
difference
format_context
get_recursively
Expand Down Expand Up @@ -51,6 +52,7 @@ Functions
.. .. automodule:: lena.context.functions
.. currentmodule:: lena.context.functions

.. autofunction:: contains
.. autofunction:: difference
.. autofunction:: format_context
.. autofunction:: get_recursively
Expand Down
4 changes: 4 additions & 0 deletions docs/source/flow.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Flow
GroupBy
GroupPlots
GroupScale
Not
Selector

**Iterators:**
Expand Down Expand Up @@ -88,6 +89,9 @@ Group plots
.. autoclass:: GroupPlots
.. autoclass:: GroupScale

.. autoclass:: Not
:show-inheritance:
:special-members: __call__
.. autoclass:: Selector
:special-members: __call__

Expand Down
4 changes: 2 additions & 2 deletions lena/context/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from lena.context.context import Context
from lena.context.functions import (
check_context_str,
contains,
difference, format_context, get_recursively,
intersection, iterate_update, make_context,
str_to_dict, str_to_list, update_nested, update_recursively,
Expand All @@ -11,7 +11,7 @@
__all__ = [
'Context',
'UpdateContext',
'check_context_str', 'difference', 'format_context',
'contains', 'difference', 'format_context',
'get_recursively',
'intersection',
'iterate_update',
Expand Down
43 changes: 30 additions & 13 deletions lena/context/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,32 @@
# used in Python documentation for dict.


def check_context_str(d, s):
"""Check that dictionary *d* satisfies a dot-separated string *s*.
def contains(d, s):
"""Check that a dictionary *d* contains a subdictionary
defined by a string *s*.
True if *d* contains a subdictionary, which is represented by *s*.
Dots in *s* signify nesting.
*s* must have at least two dot-separated parts,
otherwise :exc:`.LenaValueError` is raised.
True if *d* contains a subdictionary that is represented by *s*.
Dots in *s* mean nested subdictionaries.
A string without dots means a key in *d*.
Example:
>>> d = {'fit': {'coordinate': 'x'}}
>>> contains(d, "fit")
True
>>> contains(d, "fit.coordinate.x")
True
>>> contains(d, "fit.coordinate.y")
False
If the most nested element of *d* to be compared with *s*
is not a string, its string representation is used for comparison.
See also :func:`str_to_dict`.
"""
# todo: rename to is_subdictionary?
# add examples
# todo: s can be a list, or a dict?
levels = s.split(".")
if len(levels) < 2:
raise lena.core.LenaValueError(
"provide at least two dot-separated values."
)
return s in d
subdict = d
for key in levels[:-1]:
if key not in subdict:
Expand All @@ -35,8 +44,16 @@ def check_context_str(d, s):
last_val = levels[-1]
if isinstance(subdict, dict):
return last_val in subdict
else: # just a value
return subdict == last_val
else:
# just a value
try:
# it's better to test for an object to be cast to str
# than to disallow "dim.1"
subd = str(subdict)
except Exception:
return False
else:
return subd == last_val


def difference(d1, d2):
Expand Down
5 changes: 3 additions & 2 deletions lena/flow/group_by.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ class GroupBy(object):
def __init__(self, group_by):
"""*group_by* is a function, which returns
distinct hashable results for items from different groups.
It can be a dot-separated string, which corresponds to
a subcontext (see :func:`.get_recursively`).
It can be a dot-separated string,
which corresponds to a subcontext
(see :func:`context.get_recursively <.get_recursively>`).
If *group_by* is not a callable or a string,
:exc:`.LenaTypeError` is raised.
Expand Down
10 changes: 5 additions & 5 deletions lena/flow/selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def __init__(self, selector):
A callable is used as is.
A string means that value's context must conform to that
(as in :func:`lena.context.check_context_str`).
(as in :func:`context.contains <.contains>`).
*selector* can be a container. In this case its items
are converted to selectors.
Expand All @@ -75,7 +75,7 @@ def __init__(self, selector):
elif callable(selector):
self._selector = selector
elif isinstance(selector, str):
self._selector = lambda val: lena.context.check_context_str(
self._selector = lambda val: lena.context.contains(
lena.flow.get_context(val), selector
)
elif isinstance(selector, list):
Expand Down Expand Up @@ -122,9 +122,9 @@ def __call__(self, value):
"""Negate the result of the initialized *selector*.
This is a complete negation (including the case of an error
was encountered in the *selector*).
encountered in the *selector*).
For example, if the *selector* is *variable.name*,
and *value*'s context contains no variable,
*Not(selector)(value)* will be ``True``.
and *value*'s context contains no *"variable"*,
*Not("variable.name")(value)* will be ``True``.
"""
return not self._selector(value)
6 changes: 3 additions & 3 deletions lena/variables/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,11 @@ def __getattr__(self, name):
def get(self, key, default=None):
"""Return the attribute *key* if present, else default.
*Key* can be a dot-separated string, a list or a dictionary
(see :func:`lena.context.get_recursively`).
*key* can be a dot-separated string, a list or a dictionary
(see :func:`context.get_recursively <.get_recursively>`).
If default is not given, it defaults to ``None``,
so that this method never raises a *KeyError*.
so that this method never raises a :exc:`KeyError`.
"""
return lena.context.get_recursively(
self.var_context, key, default=default
Expand Down
16 changes: 8 additions & 8 deletions tests/context/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
)


def test_check_context_str():
d = {'a': {'b': 'c d'}}
# wrong length
with pytest.raises(lena.core.LenaValueError):
lena.context.check_context_str(d, "a")
assert lena.context.check_context_str(d, "a.b.c d")
assert lena.context.check_context_str(d, "b.c") is False
assert lena.context.check_context_str(d, "a.b")
def test_contains():
d = {'a': {'b': 'c d'}, 'e': 1}
assert lena.context.contains(d, "a") is True
assert lena.context.contains(d, "a.b.c d") is True
assert lena.context.contains(d, "b.c") is False
assert lena.context.contains(d, "a.b") is True
# not string contents are cast to strings
assert lena.context.contains(d, "e.1") is True


def test_difference():
Expand Down

0 comments on commit 2f2f15d

Please sign in to comment.