class ResponseContainer(Persistent):

    implements(IResponseContainer)
    adapts(IClaim)
    ANNO_KEY = 'claim.responses'

    def __init__(self, context):
        self.context = context
        annotations = IAnnotations(self.context)
        self.__mapping = annotations.get(self.ANNO_KEY, None)
        if self.__mapping is None:
            self.__mapping = PersistentList()
            annotations[self.ANNO_KEY] = self.__mapping

    def __contains__(self, key):
        return key in self.__mapping

    def __getitem__(self, i):
        i = int(i)
        return self.__mapping.__getitem__(i)

    def __delitem__(self, item):
        self.__mapping.__delitem__(item)

    def __len__(self):
        return self.__mapping.__len__()

    def __setitem__(self, i, y):
        self.__mapping.__setitem__(i, y)

    def append(self, item):
        self.__mapping.append(item)

    def remove(self, id):
        id = int(id)
        self[id] = None

    def add(self, item):
        self.append(item)
        id = str(len(self))
        event = ObjectAddedEvent(item, newParent=self.context, newName=id)
        notify(event)

    def delete(self, id):
        event = ObjectRemovedEvent(self[id],
                                   oldParent=self.context,
                                   oldName=id)
        self.remove(id)
        notify(event)
class MicroUpdateContainer(Persistent):

    implements(IMicroUpdateContainer)
    adapts(ILiveblog)
    ANNO_KEY = 'liveblog.microupdates'

    def __init__(self, context):
        self.context = context
        annotations = IAnnotations(self.context)
        self.__mapping = annotations.get(self.ANNO_KEY, None)
        if self.__mapping is None:
            self.__mapping = PersistentList()
            annotations[self.ANNO_KEY] = self.__mapping

    def __contains__(self, key):
        return key in self.__mapping

    def __getitem__(self, i):
        i = int(i)
        return self.__mapping.__getitem__(i)

    def __delitem__(self, item):
        self.__mapping.__delitem__(item)

    def __len__(self):
        return self.__mapping.__len__()

    def __setitem__(self, i, y):
        self.__mapping.__setitem__(i, y)

    def append(self, item):
        self.__mapping.append(item)

    def remove(self, id):
        id = int(id)
        self[id] = None

    def add(self, item):
        self.append(item)
        id = str(len(self))
        event = ObjectAddedEvent(item, newParent=self.context, newName=id)
        notify(event)

    def delete(self, id):
        event = ObjectRemovedEvent(self[id], oldParent=self.context, oldName=id)
        self.remove(id)
        notify(event)
Beispiel #3
0
class ResponseContainer(object):
    ANNO_KEY = 'poi.responses'

    def __init__(self, context):
        self.context = context
        annotations = unprotected_write(IAnnotations(self.context))
        self.__mapping = annotations.get(self.ANNO_KEY, None)
        if self.__mapping is None:
            self.__mapping = PersistentList()
            annotations[self.ANNO_KEY] = self.__mapping

    def __contains__(self, key):
        '''See interface IReadContainer

        Taken from zope.app.container.btree.

        Reimplement this method, since has_key() returns the key if available,
        while we expect True or False.

        >>> c = ResponseContainer()
        >>> "a" in c
        False
        >>> c["a"] = 1
        >>> "a" in c
        True
        >>> "A" in c
        False
        '''
        return key in self.__mapping

    has_key = __contains__

    def __getitem__(self, i):
        i = int(i)
        return self.__mapping.__getitem__(i)

    def __delitem__(self, item):
        self.__mapping.__delitem__(item)

    def __len__(self):
        return self.__mapping.__len__()

    def __setitem__(self, i, y):
        self.__mapping.__setitem__(i, y)

    def append(self, item):
        self.__mapping.append(item)

    def remove(self, item):
        self.__mapping.remove(item)

    def add(self, item):
        if not IResponse.providedBy(item):
            raise UnaddableError(self, item,
                                 "IResponse interface not provided.")
        self.append(item)
        id = str(len(self))
        event = ObjectAddedEvent(item, newParent=self.context, newName=id)
        notify(event)

    def delete(self, id):
        # We need to fire an ObjectRemovedEvent ourselves here because
        # self[id].__parent__ is not exactly the same as self, which
        # in the end means that __delitem__ does not fire an
        # ObjectRemovedEvent for us.
        #
        # Also, now we can say the oldParent is the issue instead of
        # this adapter.
        event = ObjectRemovedEvent(self[id], oldParent=self.context,
                                   oldName=id)
        self.remove(self[id])
        notify(event)
Beispiel #4
0
class ResponseContainer(Persistent):

    implements(IResponseContainer)
    adapts(ITicket)
    ANNO_KEY = 'izug.ticketbox.responses'

    def __init__(self, context):
        self.context = context
        annotations = IAnnotations(self.context)
        self.__mapping = annotations.get(self.ANNO_KEY, None)
        if self.__mapping is None:
            self.__mapping = PersistentList()
            annotations[self.ANNO_KEY] = self.__mapping

    def __contains__(self, key):
        '''See interface IReadContainer

        Taken from zope.app.container.btree.

        Reimplement this method, since has_key() returns the key if available,
        while we expect True or False.

        >>> c = ResponseContainer()
        >>> "a" in c
        False
        >>> c["a"] = 1
        >>> "a" in c
        True
        >>> "A" in c
        False
        '''
        return key in self.__mapping

    has_key = __contains__

    def __getitem__(self, i):
        i = int(i)
        return self.__mapping.__getitem__(i)

    def __delitem__(self, item):
        self.__mapping.__delitem__(item)

    def __len__(self):
        return self.__mapping.__len__()

    def __setitem__(self, i, y):
        self.__mapping.__setitem__(i, y)

    def append(self, item):
        self.__mapping.append(item)

    def remove(self, id_):
        """Remove item 'id_' from the list.

        We don't actually remove the item, we just set it to None,
        so that when you edit item 3 out of 3 and someone deletes
        item 2 you are not left in the water.

        Note that we used to get passed a complete item, not an id.
        """
        id_ = int(id_)
        self[id_] = None

    def add(self, item):
        if not IResponse.providedBy(item):
            raise UnaddableError(self, item,
                                 "IResponse interface not provided.")
        self.append(item)
        id_ = str(len(self))
        event = ObjectAddedEvent(item, newParent=self.context, newName=id_)
        notify(event)

    def delete(self, id_):
        # We need to fire an ObjectRemovedEvent ourselves here because
        # self[id_].__parent__ is not exactly the same as self, which
        # in the end means that __delitem__ does not fire an
        # ObjectRemovedEvent for us.
        #
        # Also, now we can say the oldParent is the issue instead of
        # this adapter.
        event = ObjectRemovedEvent(self[id_], oldParent=self.context,
                                   oldName=id_)
        self.remove(id_)
        notify(event)
Beispiel #5
0
class ResponseContainer(Persistent):

    implements(IResponseContainer)
    adapts(IIssue)
    ANNO_KEY = 'poi.responses'

    def __init__(self, context):
        self.context = context
        annotations = IAnnotations(self.context)
        self.__mapping = annotations.get(self.ANNO_KEY, None)
        if self.__mapping is None:
            self.__mapping = PersistentList()
            annotations[self.ANNO_KEY] = self.__mapping

    def __contains__(self, key):
        '''See interface IReadContainer

        Taken from zope.app.container.btree.

        Reimplement this method, since has_key() returns the key if available,
        while we expect True or False.

        >>> c = ResponseContainer()
        >>> "a" in c
        False
        >>> c["a"] = 1
        >>> "a" in c
        True
        >>> "A" in c
        False
        '''
        return key in self.__mapping

    has_key = __contains__

    def __getitem__(self, i):
        i = int(i)
        return self.__mapping.__getitem__(i)

    def __delitem__(self, item):
        self.__mapping.__delitem__(item)

    def __len__(self):
        return self.__mapping.__len__()

    def __setitem__(self, i, y):
        self.__mapping.__setitem__(i, y)

    def append(self, item):
        self.__mapping.append(item)

    def remove(self, id):
        """Remove item 'id' from the list.

        We don't actually remove the item, we just set it to None,
        so that when you edit item 3 out of 3 and someone deletes
        item 2 you are not left in the water.

        Note that we used to get passed a complete item, not an id.
        """
        id = int(id)
        self[id] = None

    def add(self, item):
        if not IResponse.providedBy(item):
            raise ValueError("IResponse interface not provided.")
        self.append(item)
        id = str(len(self))
        event = ObjectAddedEvent(item, newParent=self.context, newName=id)
        notify(event)

    def delete(self, id):
        # We need to fire an ObjectRemovedEvent ourselves here because
        # self[id].__parent__ is not exactly the same as self, which
        # in the end means that __delitem__ does not fire an
        # ObjectRemovedEvent for us.
        #
        # Also, now we can say the oldParent is the issue instead of
        # this adapter.
        event = ObjectRemovedEvent(self[id],
                                   oldParent=self.context,
                                   oldName=id)
        self.remove(id)
        notify(event)
Beispiel #6
0
class ResponseContainer(object):
    ANNO_KEY = 'poi.responses'

    def __init__(self, context):
        self.context = context
        annotations = unprotected_write(IAnnotations(self.context))
        self.__mapping = annotations.get(self.ANNO_KEY, None)
        if self.__mapping is None:
            self.__mapping = PersistentList()
            annotations[self.ANNO_KEY] = self.__mapping

    def __contains__(self, key):
        '''See interface IReadContainer

        Taken from zope.app.container.btree.

        Reimplement this method, since has_key() returns the key if available,
        while we expect True or False.

        >>> c = ResponseContainer()
        >>> "a" in c
        False
        >>> c["a"] = 1
        >>> "a" in c
        True
        >>> "A" in c
        False
        '''
        return key in self.__mapping

    has_key = __contains__

    def __getitem__(self, i):
        i = int(i)
        return self.__mapping.__getitem__(i)

    def __delitem__(self, item):
        self.__mapping.__delitem__(item)

    def __len__(self):
        return self.__mapping.__len__()

    def __setitem__(self, i, y):
        self.__mapping.__setitem__(i, y)

    def append(self, item):
        self.__mapping.append(item)

    def remove(self, item):
        self.__mapping.remove(item)

    def add(self, item):
        if not IResponse.providedBy(item):
            raise UnaddableError(self, item,
                                 "IResponse interface not provided.")
        self.append(item)
        id = str(len(self))
        event = ObjectAddedEvent(item, newParent=self.context, newName=id)
        notify(event)

    def delete(self, id):
        # We need to fire an ObjectRemovedEvent ourselves here because
        # self[id].__parent__ is not exactly the same as self, which
        # in the end means that __delitem__ does not fire an
        # ObjectRemovedEvent for us.
        #
        # Also, now we can say the oldParent is the issue instead of
        # this adapter.
        event = ObjectRemovedEvent(self[id],
                                   oldParent=self.context,
                                   oldName=id)
        self.remove(self[id])
        notify(event)
Beispiel #7
0
class PCardList(Persistent):
    def __init__(self, cards=()):
        if type(cards) is PCardList:
            self.cards = PersistentList(cards.cards)
        elif type(cards) is list:
            self.cards = PersistentList(cards)
        else:
            self.cards = PersistentList()

        self.name = ''

    def __getitem__(self, item):
        return self.cards.__getitem__(item)

    def __setitem__(self, key, value):
        self.cards.__setitem__(key, value)

    def __iter__(self):
        return iter(self.cards)

    def __str__(self):
        return str(self.cards)

    def __repr__(self):
        return repr(self.cards)

    def __add__(self, other):
        if type(other) is PCardList:
            return PCardList(self.cards + other.cards)
        elif type(other) is list:
            return PCardList(self.cards + other)

    def __len__(self):
        return self.cards.__len__()

    def append(self, card):
        self.cards.append(card)
        return self

    def extend(self, other):
        if type(other) is PCardList:
            self.cards.extend(other.cards)
        elif type(other) is list:
            self.cards.extend(other)
        return self

    def get(self, invert=False, matchExactly=True, **kwargs):
        qResult = set()

        for (key, val) in kwargs.items():
            if invert:
                if matchExactly:
                    result = [
                        card for card in self if not getattr(card, key) == val
                    ]
                else:
                    result = [
                        card for card in self if getattr(card, key)
                        and not set(val).issubset(set(getattr(card, key, [])))
                    ]
            else:
                if matchExactly:
                    result = [
                        card for card in self if getattr(card, key) == val
                    ]
                else:
                    result = [
                        card for card in self if getattr(card, key)
                        and set(val).issubset(set(getattr(card, key, [])))
                    ]
            qResult.update(result)

        return PCardList(list(qResult))

    def getRandomSample(self, num, duplicates=False):
        if duplicates:
            return PCardList([random.choice(self.cards) for _ in range(num)])
        else:
            try:
                return random.sample(self.cards, num)
            except ValueError:
                return PCardList(
                    [random.choice(self.cards) for _ in range(num)])

    def getRandomPack(self, numOfCards, numOfRares=1, numOfUncommons=3):
        if not numOfRares:
            numOfRares = int(round(numOfCards / 14.0))

        if not numOfUncommons:
            numOfUncommons = 3 * int(round(numOfCards / 14.0))

        numOfCommons = numOfCards - numOfRares - numOfUncommons

        commons = self.get(rarity='Common').getRandomSample(numOfCommons)
        uncommons = self.get(rarity='Uncommon').getRandomSample(numOfUncommons)
        rares = PCardList()

        if self.get(rarity='Mythic Rare'):
            for _ in range(numOfRares):
                if random.randint(0, 7) == 0:
                    rares.extend(
                        self.get(rarity='Mythic Rare').getRandomSample(1))
                else:
                    rares.extend(self.get(rarity='Rare').getRandomSample(1))
        else:
            rares.extend(self.get(rarity='Rare').getRandomSample(numOfRares))

        return PCardList().extend(commons).extend(uncommons).extend(rares)

    def getStats(self):
        totManacosts = ''
        totC = 0

        for c in self.cards:
            if c.mana_cost:
                totManacosts += c.mana_cost
                digits = ''.join(filter(lambda x: x.isdigit(), c.mana_cost))
                if digits:
                    totC += int(digits)

        creatures = sorted(self.get(types=['Creature'], matchExactly=False),
                           key=lambda c: c.cmc or 0)
        nonCreatures = sorted(self.get(types=['Creature'],
                                       matchExactly=False,
                                       invert=True).get(types=['Land'],
                                                        invert=True,
                                                        matchExactly=False),
                              key=lambda c: c.cmc or 0)
        lands = self.get(types=['Land'], matchExactly=False)
        nonLands = creatures + nonCreatures

        cardsByColors = dict((tuple(k), len(list(v))) for k, v in groupby(
            sorted(self, key=lambda card: card.colors or ['Colorless']),
            key=lambda card: card.colors or ['Colorless']))

        cmc = dict(
            (k, len(list(v)))
            for k, v in groupby(nonLands, key=lambda card: card.cmc or 0))

        types = dict((tuple(k), len(list(v))) for k, v in groupby(
            sorted(self.cards, key=lambda x: x.types or ['None']),
            key=lambda x: x.types or ['None']))

        creatureCmc = dict(
            (k, len(list(v)))
            for k, v in groupby(creatures, key=lambda card: card.cmc or 0))

        nonCreatureCmc = dict(
            (k, len(list(v)))
            for k, v in groupby(nonCreatures, key=lambda card: card.cmc or 0))

        manaSymbols = {
            'B': totManacosts.count('B'),
            'R': totManacosts.count('R'),
            'G': totManacosts.count('G'),
            'U': totManacosts.count('U'),
            'W': totManacosts.count('W'),
            'C': totC
        }

        simpleTypes = {
            'Creature': len(creatures),
            'Non-Creature': len(nonCreatures),
            'Land': len(lands)
        }

        try:
            avgCmc = round(
                float(sum([cmc * num for (cmc, num) in cmc.items()])) /
                float(sum(cmc.values())), 1)
        except ZeroDivisionError:
            avgCmc = 0

        return {
            'colors': cardsByColors,
            'cmc': cmc,
            'types': types,
            'nonCreatureCmc': nonCreatureCmc,
            'creatureCmc': creatureCmc,
            'manaSymbols': manaSymbols,
            'simpleTypes': simpleTypes,
            'avgCmc': avgCmc
        }

    def filterNonPlayableCards(self):
        return PCardList([
            card for card in self.cards
            if card.isFrontSide() and card.layout not in [
                'token', 'plane', 'scheme', 'phenomenon', 'leveler',
                'vanguard', 'Conspiracy'
            ]
        ])

    def filterBasicLands(self):
        return PCardList([
            card for card in self.cards
            if not card.supertypes or 'Basic' not in card.supertypes
        ])

    def filterDuplicateNames(self):
        temp = set()
        return PCardList([
            card for card in self.cards
            if card.name not in temp and (temp.add(card.name) or True)
        ])

    def getPool(self):
        return self.filterBasicLands().filterDuplicateNames(
        ).filterNonPlayableCards()

    def toMwsStr(self):
        creatures = self.get(types=['Creature'], matchExactly=False)
        nonCreatures = sorted(self.get(types=['Creature'],
                                       matchExactly=False,
                                       invert=True).get(types=['Land'],
                                                        invert=True,
                                                        matchExactly=False),
                              key=lambda c: c.name)
        lands = sorted(self.get(types=['Land'], matchExactly=False),
                       key=lambda c: c.name)
        print(creatures)
        print(creatures.sorted(lambda c: c.id))

        mwsStr = '// Deck file for Magic Workstation (http://www.magicworkstation.com)\n\n// Lands\n'

        for k, v in groupby(lands, key=lambda card: card.id):
            lst = list(v)
            card = lst[0]
            num = len(lst)
            mwsStr += '{} [{}] {}\n'.format(num, card.set, card.name)

        mwsStr += '\n// Creatures\n'

        for k, v in groupby(creatures, key=lambda card: card.id):
            card = list(v)[0]
            num = len(lst)
            mwsStr += '{} [{}] {}\n'.format(num, card.set, card.name)

        mwsStr += '\n// Non-Creatures\n'

        for k, v in groupby(nonCreatures, key=lambda card: card.id):
            lst = list(v)
            card = lst[0]
            num = len(lst)
            mwsStr += '{} [{}] {}\n'.format(num, card.set, card.name)

        return mwsStr

    def toJSON(self):
        return json.dumps({'cards': [card.__dict__ for card in self.cards]},
                          sort_keys=True,
                          indent=4)

    def prettyPrint(self):
        for card in self.cards:
            print('{:40s} {:30s} {:25s} {:10s}'.format(str(card.name),
                                                       str(card.types),
                                                       str(card.mana_cost),
                                                       str(card.rarity)))

    def sorted(self, func):
        return PCardList(sorted(self.cards, key=func))
Beispiel #8
0
class PSetList(Persistent):
    """PSetList is a persistent set list object that mostly acts just like a normal Python list for PSet objects.
    These lists can be saved in the database just like any other persistent objects. It can optionally be initialized
    with another list of PSet objects and a name. Additionally, it will also have an attribute 'creation_date' and
    a unique uuid attribute 'id'. PSetLists are considered equal if they have the same 'id'.

    Except for the usual list methods like 'extend' and 'append', the PCardList is functional in style, meaning that
    calling any of the other filtering or querying methods return new PCardList objects leaving the original untouched.

    args:
        sets (PSetList, PersistentList[PSet], list[PSet], tuple[PSet]): Initial sets of the list.
        name (str): Name of the set list.
    """

    def __init__(self, sets=None, name=''):
        if isinstance(sets, PSetList):
            self._sets = PersistentList(sets.sets)
        elif isinstance(sets, (PersistentList, list, tuple)):
            self._sets = PersistentList(sets)
        elif not sets:
            self._sets = PersistentList()
        else:
            raise TypeError

        self.name = name
        self.creation_date = datetime.datetime.now()
        self.id = uuid.uuid4()

    def __getitem__(self, item):
        if isinstance(item, int):
            return self._sets.__getitem__(item)
        else:
            return PSetList(self._sets.__getitem__(item))

    def __setitem__(self, key, value):
        self._sets.__setitem__(key, value)

    def __iter__(self):
        return iter(self._sets)

    def __str__(self):
        return str(self._sets)

    def __repr__(self):
        return repr(self._sets)

    def __add__(self, other):
        if isinstance(other, PSetList):
            return PSetList(self.sets + other.sets)
        elif isinstance(other, (PersistentList, list, tuple)):
            return PSetList(self.sets + other)
        elif isinstance(other, PSet):
            new_sets = PersistentList(self.sets)
            new_sets.append(other)
            return PSetList(new_sets)
        else:
            raise TypeError

    def __radd__(self, other):
        if isinstance(other, PSetList):
            return PSetList(self.sets + other.sets)
        elif isinstance(other, (PersistentList, list, tuple)):
            return PSetList(self.sets + other)
        elif isinstance(other, PSet):
            new_sets = PersistentList(self.sets)
            new_sets.append(other)
            return PSetList(new_sets)
        else:
            raise TypeError

    def __iadd__(self, other):
        if isinstance(other, PSetList):
            return PSetList(self.sets + other.sets)
        elif isinstance(other, (PersistentList, list, tuple)):
            return PSetList(self.sets + other)
        elif isinstance(other, PSet):
            new_sets = PersistentList(self.sets)
            new_sets.append(other)
            return PSetList(new_sets)
        else:
            raise TypeError

    def __len__(self):
        return len(self.sets)

    def __contains__(self, pset):
        return self.sets.__contains__(pset)

    def __eq__(self, other):
        if isinstance(other, PSetList):
            return self.id == other.id

    def append(self, pset):
        """Appends the given set object to this list in-place.

        Args:
            pset (PSet): The set object to append.
        """
        self.sets.append(pset)

    def extend(self, psets):
        """Extends the list with a list of set objects in-place.

        Args:
            psets (PSetList, list, tuple, PersistentList): A PSetList, PersistentList, list or a tuple of
                set objects to extend this list with.
        """
        if isinstance(psets, PSetList):
            self.sets.extend(psets.sets)
        elif isinstance(psets, (PersistentList, list, tuple)):
            self.sets.extend(psets)
        else:
            raise TypeError

    def insert(self, index, pset):
        """Inserts a set object to a given index in this list in-place.

        Args:
            pset (PSet): The set object to be inserted in the given index in this list.
            index (int): The index to insert the given set object.
        """
        self._sets.insert(index, pset)

    def index(self, pset):
        """Returns the index where the given set object is located in this list.

        Args:
            pset (PSet): The set object to be searched.
        """
        self._sets.index(pset)

    def clear(self):
        """Clears this list."""
        self._sets.clear()

    def remove(self, pset):
        """Removes a given set from this list in-place.

        Args:
            pset (PSet): A set object to remove from this list.
        """
        self._sets.remove(pset)

    def pop(self, index):
        """Removes a set from a given index from this list in-place.

        Args:
            index (int): An index to remove a set from.
        """
        self._sets.pop(index)

    def count(self, pset):
        """Returns the number of given set objects in this list. Sets are considered same if they have the same code.

        Args:
            pset (pset): A set object to count.

        Returns:
            int: The number of given set objects in this list
        """
        return self._sets.count(pset)

    def sort(self, func):
        """Sorts the sets of this list with a given function in-place. The given function should return some
        attribute of a set object by which this list is sorted.

        Args:
            func: A function to sort this list with.
        """
        self._sets.sort(key=func)

    def filter(self, func):
        """Filters the sets of this list with a given function in-place. The new list contains all the cards
        for which the given function returns True.

        Args:
            func: A function to filter with.
        """
        self._sets.filter(key=func)

    def sorted(self, func):
        """Returns a new list with the sets of this list sorted with a given function. The given function should return
        some attribute of a set object by which this list is sorted.

        Args:
            func: A function to sort this list with.

        Returns:
            PCardList: A new instance of this list sorted.

        """
        return PSetList(sorted(self.sets, key=func))

    def filtered(self, func):
        """Returns a new list filtered with a given function. The new list contains all the sets
        for which the given function returns True.

        Args:
            func: A function to filter with.

        Returns:
            PCardList: A new instance of this list filtered.

        """
        return PSetList(list(filter(func, self.sets)))

    def where(self, invert=False, **kwargs):
        """Returns a new list of sets for which any of the given keyword arguments match partly or completely with the
        attributes of the sets in this list. The arguments should be any set attribute names such as 'name',
        'type' and 'block'. String attributes are case insensitive and it is enough that the argument is a
        substring. For list arguments the order does not matter and it is enough for one of the elements to match.

        The search can also be inverted by setting invert=True so that all the cards NOT matching will be returned.

        Note that searching for Null arguments is not supported.

        Args:
            invert: If True, a list of sets NOT matching the arguments is returned.
            **kwargs: Arguments to match with the attributes of this list's sets.

        Returns:
            bool: A new list of sets for which any of the given keyword arguments match partly or completely.
        """
        del_keys = []

        for (key, val) in kwargs.items():
            if not val:
                msg = 'Ignoring an empty or null value for keyword {}. Null or empty values are not supported.'
                warnings.warn(msg.format(key))
                del_keys.append(key)
            elif len(self.sets) == 0:
                msg = 'Searching an empty list.'
                warnings.warn(msg)
            elif not hasattr(self.sets[0], key):
                msg = 'Ignoring an unrecognized keyword {}. Make sure you are using correct api type and spelling.'
                warnings.warn(msg.format(key))
                del_keys.append(key)

        for key in del_keys:
            del kwargs[key]

        if not invert:
            return PSetList([pset for pset in self if pset.matches_any(**kwargs)])
        else:
            return PSetList([pset for pset in self if not pset.matches_any(**kwargs)])

    def where_exactly(self, invert=False, **kwargs):
        """Returns a new list of sets for which all of the given keyword arguments match completely with the attributes
        of the sets in this list. The arguments should be any set attribute names such as 'name', 'type' and 'block'.
        String attributes are case insensitive and must match exactly. For list arguments the order does not
        matter and and each element must match exactly.

        The search can also be inverted by setting invert=True so that all the cards NOT matching will be returned.

        Note that searching for Null arguments is not supported.

        Args:
            invert: If True, a list of sets NOT matching the arguments is returned.
            **kwargs: Arguments to match with the attributes of this list's cards.

        Returns:
            bool: A new list of sets for which all of the given keyword arguments match completely.
        """
        del_keys = []

        for (key, val) in kwargs.items():
            if not val:
                msg = 'Ignoring an empty or null value for keyword {}. Null or empty values are not supported.'
                warnings.warn(msg.format(key))
                del_keys.append(key)
            elif len(self.sets) == 0:
                msg = 'Searching an empty list.'
                warnings.warn(msg)
            elif not hasattr(self.sets[0], key):
                msg = 'Ignoring an unrecognized keyword {}. Make sure you are using correct api type and spelling.'
                warnings.warn(msg.format(key))
                del_keys.append(key)

        for key in del_keys:
            del kwargs[key]

        if not invert:
            return PSetList([pset for pset in self if pset.matches_all(**kwargs)])
        else:
            return PSetList([pset for pset in self if not pset.matches_all(**kwargs)])

    def pprint(self):
        """Prints out the contents of this list in a nice readable way."""
        print(self.pprint_str())

    def pprint_str(self):
        """Returns a nice readable string of the contents of this list.

        Returns:
            str: a string of the contents of this list in a nice readable format.
        """

        if len(self) == 0:
            if self.name:
                return 'Empty set list "{}" created at {}\n'.format(self.name, str(self.creation_date))
            else:
                return 'Unnamed empty set list created at {}\n'.format(self.creation_date)

        pp_str = ''

        if self.name:
            pp_str += 'Set list "{}" created at {}\n'.format(self.name, str(self.creation_date))
        else:
            pp_str += 'Unnamed set list created at {}\n'.format(self.creation_date)

        longest_name = max(len(pset.name) for pset in self.sets)
        longest_type = max(len(getattr(pset, 'set_type', getattr(pset, 'type', ''))) for pset in self.sets)
        longest_block = max(len(pset.block) if pset.block else 0 for pset in self.sets)
        longest_code = max(len(pset.code) if pset.code else 0 for pset in self.sets)

        pp_str += '-' * (longest_name + longest_type + longest_block + longest_code + 17)
        pp_str += '\n'

        format_str = '{name:{w1}s}   {code:{w2}s}   {block:{w3}s}   {type:{w4}s}   {cards}\n'
        pp_str += format_str.format(name='Set',
                                    w1=longest_name,
                                    code='Code',
                                    w2=longest_code,
                                    block='Block',
                                    w3=longest_block,
                                    type='Type',
                                    w4=longest_type,
                                    cards='Cards')
        pp_str += '-' * (longest_name + longest_type + longest_block + longest_code + 17)
        pp_str += '\n'

        for pset in self.sets:
            format_str = '{name:{w1}s}   {code:{w2}s}   {block:{w3}s}   {type:{w4}s}   {cards}\n'
            pp_str += format_str.format(name=pset.name,
                                        w1=longest_name,
                                        code=pset.code,
                                        w2=longest_code,
                                        block=pset.block if pset.block else '',
                                        w3=longest_block,
                                        type=getattr(pset, 'set_type', getattr(pset, 'type', '')),
                                        w4=longest_type,
                                        cards=len(pset))

        return pp_str

    @property
    def api_type(self):
        try:
            return self.sets[0].api_type
        except IndexError:
            return 'unspecified'

    @property
    def json(self):
        pset_json_dicts = []

        for pset in self.sets:
            json_dict = dict(pset.__dict__)
            del json_dict['_cards']
            del json_dict['_sideboard']
            del json_dict['creation_date']
            del json_dict['id']

            if len(pset) > 0:
                json_dict['cards'] = [card.__dict__ for card in pset.cards]
                pset_json_dicts.append(json_dict)

        return json.dumps({'sets': pset_json_dicts}, sort_keys=True, indent=4)

    @property
    def sets(self):
        return self._sets

    @sets.setter
    def sets(self, sets):
        if isinstance(sets, PSetList):
            self._sets = PersistentList(sets.sets)
        elif isinstance(sets, (list, PersistentList, tuple)):
            self._sets = PersistentList(sets)
        elif not sets:
            self._sets = PersistentList()
        else:
            raise TypeError