Source code for aioriak.datatypes.map

from .datatype import Datatype
from . import TYPES
from collections import Mapping
from riak.util import lazy_property


class TypedMapView(Mapping):
    '''
    Implements a sort of view over a :class:`Map`, filtered by the embedded
    datatype.
    '''

    def __init__(self, parent, datatype):
        self.map = parent
        self.datatype = datatype

    # Mapping API
    def __getitem__(self, key):
        '''
        Fetches an item from the parent :class:`Map` scoped by this view's
        datatype.

        :param key: the key of the item
        :type key: str
        :rtype: :class:`~aioriak.datatypes.Datatype`
        '''
        return self.map[(key, self.datatype)]

    def __iter__(self):
        '''
        Iterates over all keys in the :class:`Map` scoped by this view's
        datatype.
        '''
        for key in self.map.value:
            name, datatype = key
            if datatype == self.datatype:
                yield name

    def __len__(self):
        '''
        Returns the number of keys in this map scoped by this view's datatype.
        '''
        return len(iter(self))

    def __contains__(self, key):
        '''
        Determines whether the given key with this view's datatype is in the
        parent :class:`Map`.
        '''
        return (key, self.datatype) in self.map

    # From the MutableMapping API
    def __delitem__(self, key):
        '''
        Removes the key with this view's datatype from the parent :class:`Map`.
        '''
        del self.map[(key, self.datatype)]


[docs]class Map(Mapping, Datatype): ''' A convergent datatype that acts as a key-value datastructure. Keys are pairs of ``(name, datatype)`` where ``name`` is a string and ``datatype`` is the datatype name. Values are other convergent datatypes, represented by any concrete type in this module. You cannot set values in the map directly (it does not implement ``__setitem__``), but you may add new empty values or access non-existing values directly via bracket syntax. If a key is not in the original value of the map when accessed, fetching the key will cause its associated value to be created.:: map[('name', 'register')] Keys and their associated values may be deleted from the map as you would in a dict:: del map[('emails', 'set')] Convenience accessors exist that partition the map's keys by datatype and implement the collections.Mapping behavior as well as supporting deletion:: map.sets['emails'] map.registers['name'] del map.counters['likes'] ''' type_name = 'map' _type_error_msg = "Map must be a dict with (name, type) keys" def _default_value(self): return dict() def _post_init(self): self._removes = set() self._updates = {} @lazy_property def counters(self): ''' Filters keys in the map to only those of counter types. Example:: map.counters['views'].increment() del map.counters['points'] ''' return TypedMapView(self, 'counter') @lazy_property def flags(self): ''' Filters keys in the map to only those of flag types. Example:: map.flags['confirmed'].enable() del map.flags['attending'] ''' return TypedMapView(self, 'flag') @lazy_property def maps(self): ''' Filters keys in the map to only those of map types. Example:: map.maps['emails'].registers['home'].set("user@example.com") del map.maps['spam'] ''' return TypedMapView(self, 'map') @lazy_property def registers(self): ''' Filters keys in the map to only those of register types. Example:: map.registers['username'].set_value("riak-user") del map.registers['access_key'] ''' return TypedMapView(self, 'register') @lazy_property def sets(self): ''' Filters keys in the map to only those of set types. Example:: map.sets['friends'].add("brett") del map.sets['favorites'] ''' return TypedMapView(self, 'set') def __contains__(self, key): ''' A map contains a key if that key exists in the original value or has been added or mutated. :rtype: bool ''' self._check_key(key) return (key in self._value) or (key in self._updates) # collections.Mapping API def __getitem__(self, key): ''' Fetches a convergent datatype at the given key. .. note: If the key is not in the map, a new empty datatype will be inserted at that key and returned. If the key was previously deleted, that mutation will be discarded. :param key: the key of the value to fetch :type key: tuple :rtype: :class:`Datatype` matching the datatype in the key ''' self._check_key(key) if key in self._value: return self._value[key] else: # If the key does not exist, we assume they are wanting to # create a new one with that name/type. if key not in self._updates: self._updates[key] = TYPES[key[1]](context=self.context) return self._updates[key] def __iter__(self): ''' Iterates over the *immutable* original value of the map. ''' return iter(self.value) def __len__(self): ''' Returns the size of the original value of the map. ''' return len(self._value) def __delitem__(self, key): ''' Deletes a key from the map. If you have previously mutated the datatype associated with this key, those mutations will be discarded. .. note: You may delete keys that are not entries in the map. If the Riak server does not find the entry in the set, an error may be returned to the client. For safety, always submit removal operations with a context. :param key: the key to remove :type key: tuple ''' # NB: deleting a key only marks it deleted, and you can delete # things that don't appear in the value! self._check_key(key) self._require_context() self._removes.add(key) def _check_key(self, key): ''' Ensures well-formedness of a key. ''' if not len(key) == 2: raise TypeError('invalid key: %r' % key) elif key[1] not in TYPES: raise TypeError('invalid datatype: %s' % key[1]) # Datatype API @Datatype.value.getter def value(self): ''' Returns a copy of the original map's value. Nested values are pure Python values as returned by :attr:`Datatype.value` from the nested types. :rtype: dict ''' pvalue = {} for key in self._value: pvalue[key] = self._value[key].value return pvalue @Datatype.modified.getter def modified(self): ''' Whether the map has staged local modifications. ''' if self._removes: return True for v in self._value: if self._value[v].modified: return True for v in self._updates: if self._updates[v].modified: return True return False def to_op(self): ''' Extracts the modification operation(s) from the map. :rtype: list, None ''' removes = [('remove', r) for r in self._removes] value_updates = list(self._extract_updates(self._value)) new_updates = list(self._extract_updates(self._updates)) all_updates = removes + value_updates + new_updates if all_updates: return all_updates else: return None def _check_type(self, value): for key in value: try: self._check_key(key) except: return False return True def _coerce_value(self, new_value): cvalue = {} for key in new_value: cvalue[key] = TYPES[key[1]](value=new_value[key], context=self._context) return cvalue def _extract_updates(self, d): for key in d: if d[key].modified: yield ('update', key, d[key].to_op())
TYPES[Map.type_name] = Map