Skip to content

Commit

Permalink
Tags and AggTags should now have full set operator behavior.
Browse files Browse the repository at this point in the history
Now one should be able to use Tags and AggTags with sets or lists for
set comparison. Perhaps we should leave lists off, but they get
converted to sets anyway for the comparisons/operations. Need test
coverage yet for these though!
  • Loading branch information
dotsdl committed Mar 21, 2016
1 parent 0e5dd9e commit 53898a6
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 18 deletions.
5 changes: 5 additions & 0 deletions docs/groups.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ the same stored information at all times ::
>>> g2.tags
<Tags(['oaks'])>

Groups find their members when they go missing
==============================================



API Reference: Group
====================
See the :ref:`Group_api` API reference for more details.
11 changes: 9 additions & 2 deletions docs/tags-categories.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,16 @@ Or checked for membership ::
>>> 'mirky' in s.tags
True

Since the tags of a Treant behave as a set, we can do set operations directly ::
Since the tags of a Treant behave as a set, we can do set operations directly,
such as subset comparisons::

>>>
>>> {'elm', 'misty'} < s.tags
True

Or unions::

>>> {'elm', 'misty'} | s.tags

API Reference: Tags
-------------------
Expand Down
89 changes: 89 additions & 0 deletions src/datreant/core/agglimbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
"""
import itertools
import functools
from six import string_types, with_metaclass

from fuzzywuzzy import process

from . import filesystem
from . import _AGGTREELIMBS, _AGGLIMBS
from .collections import Bundle
from .limbs import Tags


class _AggTreeLimbmeta(type):
Expand Down Expand Up @@ -50,6 +52,7 @@ def __init__(self, collection):
self._collection = collection


@functools.total_ordering
class AggTags(AggLimb):
"""Interface to aggregated tags.
Expand Down Expand Up @@ -86,6 +89,92 @@ def __len__(self):
def __getitem__(self, value):
return [member.tags[value] for member in self._collection]

def __eq__(self, other):
if isinstance(other, (AggTags, Tags, set, list)):
return set(self) == set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __lt__(self, other):
if isinstance(other, (AggTags, Tags, set, list)):
return set(self) < set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __sub__(self, other):
"""Return a set giving the Tags in `a` that are not in `b`.
"""
if isinstance(other, (AggTags, Tags, set, list)):
return set(self) - set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __rsub__(self, other):
"""Return a set giving the Tags in `a` that are not in `b`.
"""
if isinstance(other, (AggTags, Tags, set, list)):
return set(self) - set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __or__(self, other):
"""Return a set giving the union of Tags `a` and `b`.
"""
if isinstance(other, (AggTags, Tags, set, list)):
return set(self) | set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __ror__(self, other):
"""Return a set giving the union of Tags `a` and `b`.
"""
if isinstance(other, (AggTags, Tags, set, list)):
return set(self) | set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __and__(self, other):
"""Return a set giving the intersection of Tags `a` and `b`.
"""
if isinstance(other, (AggTags, Tags, set, list)):
return set(self) & set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __rand__(self, other):
"""Return a set giving the intersection of Tags `a` and `b`.
"""
if isinstance(other, (AggTags, Tags, set, list)):
return set(self) & set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __xor__(self, other):
"""Return a set giving the symmetric difference of Tags
`a` and `b`.
"""
if isinstance(other, (AggTags, Tags, set, list)):
return set(self) ^ set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __rxor__(self, other):
"""Return a set giving the symmetric difference of Tags
`a` and `b`.
"""
if isinstance(other, (AggTags, Tags, set, list)):
return set(self) ^ set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

@property
def any(self):
"""Set of tags present among at least one Treant in collection.
Expand Down
69 changes: 53 additions & 16 deletions src/datreant/core/limbs.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ def _setter(self, val):
"""Used for constructing the property when attaching this Limb to a class.
"""
if isinstance(val, (list, set)):
self.members.clear()
self.members.add(list(val))
if isinstance(val, (Tags, list, set)):
self.tags.clear()
self.tags.add(list(val))
else:
raise TypeError("Can only set with a list or set")

Expand Down Expand Up @@ -143,53 +143,90 @@ def __len__(self):
return len(self._list())

def __eq__(self, other):
try:
if isinstance(other, (Tags, set, list)):
return set(self) == set(other)
except AttributeError:
raise TypeError("Operands must be Tags.")
else:
raise TypeError("Operands must be tags, a set, or list.")

def __lt__(self, other):
if isinstance(other, Tags):
if isinstance(other, (Tags, set, list)):
return set(self) < set(other)
else:
raise TypeError("Operands must be Tags.")
raise TypeError("Operands must be tags, a set, or list.")

def __sub__(self, other):
"""Return a set giving the Tags in `a` that are not in `b`.
"""
if isinstance(other, Tags):
if isinstance(other, (Tags, set, list)):
return set(self) - set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __rsub__(self, other):
"""Return a set giving the Tags in `a` that are not in `b`.
"""
if isinstance(other, (Tags, set, list)):
return set(self) - set(other)
else:
raise TypeError("Operands must be Tags.")
raise TypeError("Operands must be tags, a set, or list.")

def __or__(self, other):
"""Return a set giving the union of Tags `a` and `b`.
"""
if isinstance(other, Tags):
if isinstance(other, (Tags, set, list)):
return set(self) | set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __ror__(self, other):
"""Return a set giving the union of Tags `a` and `b`.
"""
if isinstance(other, (Tags, set, list)):
return set(self) | set(other)
else:
raise TypeError("Operands must be Tags.")
raise TypeError("Operands must be tags, a set, or list.")

def __and__(self, other):
"""Return a set giving the intersection of Tags `a` and `b`.
"""
if isinstance(other, Tags):
if isinstance(other, (Tags, set, list)):
return set(self) & set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __rand__(self, other):
"""Return a set giving the intersection of Tags `a` and `b`.
"""
if isinstance(other, (Tags, set, list)):
return set(self) & set(other)
else:
raise TypeError("Operands must be Tags.")
raise TypeError("Operands must be tags, a set, or list.")

def __xor__(self, other):
"""Return a set giving the symmetric difference of Tags
`a` and `b`.
"""
if isinstance(other, Tags):
if isinstance(other, (Tags, set, list)):
return set(self) ^ set(other)
else:
raise TypeError("Operands must be tags, a set, or list.")

def __rxor__(self, other):
"""Return a set giving the symmetric difference of Tags
`a` and `b`.
"""
if isinstance(other, (Tags, set, list)):
return set(self) ^ set(other)
else:
raise TypeError("Operands must be Tags.")
raise TypeError("Operands must be tags, a set, or list.")

def _list(self):
"""Get all tags for the Treant as a list.
Expand Down

0 comments on commit 53898a6

Please sign in to comment.