Exemplo n.º 1
0
    def test_key_inequality(self):
        ''' Make sure keys don't equal each other when they shouldn't. '''

        conditions = []

        # test with determininstic ID
        k1 = model.Key("TestKind", "blabs")
        k2 = model.Key("TestKind", "blobs")
        conditions.append((k1 != k2))

        # test kinded keys
        k3 = model.Key("TestKind")
        k4 = model.Key("TestOtherKind")
        conditions.append((k3 != k4))

        # test ancestored keys with different IDs
        k5 = model.Key("TestSubkind", "blabs", parent=k1)
        k6 = model.Key("TestSubkind", "blobs", parent=k1)
        conditions.append((k5 != k6))

        # test ancestored keys with different kinds
        k7 = model.Key("TestSubkindOne", "blabs", parent=k1)
        k8 = model.Key("TestSubkindTwo", "blabs", parent=k1)
        conditions.append((k7 != k8))

        # test ancestored keys with different parents
        k9 = model.Key("TestSubkind", "blabs", parent=k1)
        k10 = model.Key("TestSubkind", "blabs", parent=k2)
        conditions.append((k9 != k10))

        [self.assertTrue(condition) for condition in conditions]
Exemplo n.º 2
0
    def test_key_construct_multiple_formats(self):
        ''' Test constuction of a `Key` with multiple formats, which is not supported. '''

        # sample key
        ok = model.Key("Sample", "sample_id")

        # try and make a key with multiple formats
        with self.assertRaises(TypeError):
            model.Key(raw=ok.flatten(False)[1], urlsafe=ok.urlsafe())
Exemplo n.º 3
0
    def test_key_with_overflowing_schema(self):
        ''' Test construction of a `Key` with too many schema items. '''

        # try and make a key with a ton of arguments
        with self.assertRaises(TypeError):
            model.Key("SampleKind", "id", "coolstring", "whatdowedo",
                      "whenwehave", "thismanyarguments")
Exemplo n.º 4
0
    def test_model_setkey(self):
        ''' Test the protected method `_set_key`, which is used by Model API internals. '''

        # sample person
        p = Person(firstname='John')

        # try writing an invalid key
        with self.assertRaises(TypeError):
            p._set_key(5.5)

        # try writing via kwargs
        k = model.Key(Person, "john")

        # try constructing via urlsafe
        p._set_key(urlsafe=k.urlsafe())

        # try constructing via raw
        p._set_key(raw=k.flatten(False)[1])

        # try already-constructed via kwargs
        p._set_key(constructed=k)

        # try providing both a value and formats, which should fail
        with self.assertRaises(TypeError):
            p._set_key(k, urlsafe=k.urlsafe())

        # try providing multiple formats, which should fail
        with self.assertRaises(TypeError):
            p._set_key(urlsafe=k.urlsafe(), raw=k.flatten(False)[1])

        # try passing nothing, which should fail
        with self.assertRaises(TypeError):
            p._set_key(None)
Exemplo n.º 5
0
    def test_key_overwrite_known_attribute(self):
        ''' Try overwriting a known (schema-d) attribute. '''

        k = model.Key("CoolKind", "coolid")
        with self.assertRaises(AttributeError):
            k.kind = "MyKind"
        with self.assertRaises(AttributeError):
            k.id = 10
Exemplo n.º 6
0
    def test_key_with_model_class_kind(self):
        ''' Test making a `Key` via using a model class as the kind. '''

        ## KindedModel
        # Used to test using classes for kinds in `model.Key`.
        class KindedModel(model.Model):
            ''' Sample for testing key creation from model classes. '''

            string = basestring

        # make keys
        k1 = model.Key("KindedModel", "test_id")
        k2 = model.Key(KindedModel, "test_id")
        ko = model.Key(KindedModel)

        # test keys
        self.assertEqual(k1.kind, "KindedModel")
        self.assertEqual(k1.id, "test_id")
        self.assertEqual(k2.kind, k1.kind)
        self.assertEqual(ko.kind, k1.kind)
        self.assertEqual(k2.id, k2.id)
Exemplo n.º 7
0
    def test_key_ancestry(self):
        ''' Make a key with ancestry and test it a bunch. '''

        # manufacture keys
        pk = model.Key("ParentKind", "parent_id")
        ck = model.Key("ChildKind", "child_id", parent=pk)
        gk = model.Key("GrandchildKind", "grandchild_id", parent=ck)
        ggk = model.Key("GreatGrandchildKind",
                        "great_grandchild_id",
                        parent=gk)

        # for each key, make sure parent is set
        self.assertEqual(pk.parent, None)
        self.assertEqual(ck.parent, pk)
        self.assertEqual(gk.parent, ck)
        self.assertEqual(ggk.parent, gk)

        # for each key, make sure ancestry works
        pk_ancestry = [i for i in pk.ancestry]
        ck_ancestry = [i for i in ck.ancestry]
        gk_ancestry = [i for i in gk.ancestry]
        ggk_ancestry = [i for i in ggk.ancestry]

        # test ancestry paths
        self.assertEqual(len(pk_ancestry), 1)
        self.assertEqual(len(ck_ancestry), 2)
        self.assertEqual(len(gk_ancestry), 3)
        self.assertEqual(len(ggk_ancestry), 4)

        # len of a key should always be 1 unless it has ancestry, then it's the length of the ancestry chain
        self.assertEqual(len(pk), 1)
        self.assertEqual(len(ck), 2)
        self.assertEqual(len(gk), 3)
        self.assertEqual(len(ggk), 4)

        # ... however all keys should test nonzero-ness (all keys should be nonzero)
        for k in (pk, ck, gk, ggk):
            self.assertTrue(k)
Exemplo n.º 8
0
    def test_key_format(self):
        ''' Make sure there's a proper format spec on `model.Key`. '''

        # build object
        k1 = model.Key("Test", "testkey")

        # test class
        self.assertTrue(hasattr(model.Key, '__schema__'))
        self.assertIsInstance(model.Key.__schema__, tuple)
        self.assertTrue((len(model.Key.__schema__) > 1))

        # test object
        self.assertTrue(hasattr(k1, '__schema__'))
        self.assertIsInstance(model.Key.__schema__, tuple)
        self.assertTrue((len(model.Key.__schema__) > 1))
Exemplo n.º 9
0
    def test_key_adapter(self):
        ''' Make sure the adapter is attached correctly to `model.Key`. '''

        # build test obj
        k = model.Key("TestKind", "test")

        # make sure an adapter is attached at the class level
        self.assertTrue(hasattr(model.Key, '__adapter__'))
        self.assertIsInstance(model.Key.__adapter__,
                              model.adapter.ModelAdapter)

        # make sure the same is true at the object level
        self.assertTrue(hasattr(k, '__adapter__'))
        self.assertIsInstance(model.Key.__adapter__,
                              model.adapter.ModelAdapter)
Exemplo n.º 10
0
  def factory(cls, parent=None, id=None, **kwargs):

    '''  '''

    # figure out key ID and parent
    id, parent = (
      parent if (not id or (isinstance(basestring, parent) and not parent)) else id,
      parent if not isinstance(parent, basestring) else None
    )

    # inject ID if we have one
    if hasattr(cls, 'id'): kwargs['id'] = id

    # factory object
    return cls(key=model.Key(cls, id, parent=parent), **kwargs)
Exemplo n.º 11
0
    def test_model_setvalue(self):
        ''' Test the protected method `_set_value`, which is used by Model API internals. '''

        # sample person
        p = Person(firstname='John')

        # try writing a new key
        x = p._set_value('key', model.Key(Person, "john"))
        self.assertEqual(x, p)

        # try writing to invalid property
        with self.assertRaises(AttributeError):
            p._set_value('invalidproperty', 'value')

        # quick test via descriptor API, which should also raise `AttributeError`
        with self.assertRaises(AttributeError):
            Person.__dict__['firstname'].__set__(None, 'invalid')
Exemplo n.º 12
0
    def test_key_auto_id(self):
        ''' Test an integer-based ID field. '''
        class AutoincrementTest(model.Model):
            ''' Test that keys autoincrement properly when not assigned
        deterministic name values. '''

            message = basestring, {'default': 'Hello, world!'}

        # put deterministic key
        a = AutoincrementTest(
            key=model.Key(AutoincrementTest.kind(), 'testing-string-key'))
        dk = a.put()

        # put nondeterministic key #1
        nk = AutoincrementTest().put()

        # put nondeterministic key #2
        nk2 = AutoincrementTest().put()

        self.assertIsInstance(nk.id, int)  # nondeterministic should be an int
        self.assertIsInstance(dk.id,
                              basestring)  # deterministic should be a string
        self.assertTrue((nk2.id > nk.id))  # should be greater
Exemplo n.º 13
0
    def test_key_equality(self):
        ''' Make sure keys equal each other when they should. '''

        conditions = []

        # test with determininstic ID
        k1 = model.Key("TestKind", "blabs")
        k2 = model.Key("TestKind", "blabs")
        conditions.append((k1 == k2))

        # test kinded keys
        k3 = model.Key("TestKind")
        k4 = model.Key("TestKind")
        conditions.append((k3 == k4))

        # test ancestored keys
        k5 = model.Key("TestSubkind", "blobs", parent=k1)
        k6 = model.Key("TestSubkind", "blobs", parent=k2)
        conditions.append((k5 == k6))

        [self.assertTrue(condition) for condition in conditions]
Exemplo n.º 14
0
    def test_key_set_unknown_attribute(self):
        ''' Try setting an unknown and known attribute. '''

        k = model.Key("CoolKind", "coolid")
        with self.assertRaises(AttributeError):
            k.blabble = True  # try writing unknown attribute
Exemplo n.º 15
0
    def make_key(id=None, model=UserSession):
        '''  '''

        return models.Key(
            model, id
            or Session.generate_token(Session.config.get('salt', '')))
Exemplo n.º 16
0
    def execute_query(cls, kind, spec, options, **kwargs):  # pragma: no cover
        ''' Execute a :py:class:`model.Query` across one (or multiple)
      indexed properties.

      :param kind: Kind name (``str``) for which we are querying
      across, or ``None`` if this is a ``kindless`` query.

      :param spec: Tupled pair of ``filter`` and ``sort`` directives
      to apply to this :py:class:`Query` (:py:class:`query.Filter` and
      :py:class:`query.Sort` and their descendents, respectively), like
      ``(<filters>, <sorts>)``.

      :param options: Object descendent from, or directly instantiated
      as :py:class:`QueryOptions`, specifying options for the execution
      of this :py:class:`Query`.

      :param **kwargs: Low-level options for handling this query, such
      as ``pipeline`` (for pipelining support) and ``execute`` (to trigger
      a buffer flush for a generated or constructed pipeline or operation
      buffer).

      :returns: Iterable (``list``) of matching :py:class:`model.Key`
      yielded by execution of the current :py:class:`Query`. Returns
      empty iterable (``[]``) in the case that no results could be found. '''

        # @TODO(sgammon): desparately needs rewriting. absolute utter plebbery.

        from canteen import model
        from canteen.model import query

        if not kind:
            # @TODO(sgammon): implement kindless queries
            raise NotImplementedError(
                'Kindless queries are not yet implemented in `Redis`.')

        # extract filter and sort directives and build ancestor
        filters, sorts = spec
        ancestry_parent = model.Key.from_urlsafe(
            options.ancestor) if options.ancestor else None
        _data_frame = []  # allocate results window

        if filters:
            kinded_key = model.Key(kind, parent=ancestry_parent)

            ## HUGE HACK: use generate_indexes to make index names.
            # fix this plz

            _filters, _filter_i_lookup = {}, set()
            for _f in filters:
                origin, meta, property_map = cls.generate_indexes(
                    kinded_key, {_f.target.name: (_f.target, _f.value.data)})

                for operation, index, config in cls.write_indexes(
                    (origin, [], property_map), None, False):

                    if operation == 'ZADD':
                        _flag, _index_key, value = 'Z', index[1], index[2]
                    else:
                        _flag, _index_key, value = 'S', index[1], index[2]

                if (_flag, _index_key) not in _filter_i_lookup:
                    _filters[(_flag, _index_key)] = []
                    _filter_i_lookup.add((_flag, _index_key))

                _filters[(_flag, _index_key)].append(
                    (_f.operator, value, _f.chain))

            # process sorted sets first: leads to lower cardinality
            sorted_indexes = dict([
                (index, _filters[index])
                for index in filter(lambda x: x[0] == 'Z', _filters.iterkeys())
            ])
            unsorted_indexes = dict([
                (index, _filters[index])
                for index in filter(lambda x: x[0] == 'S', _filters.iterkeys())
            ])

            _and_filters, _or_filters = [], []

            if sorted_indexes:

                for prop, _directives in sorted_indexes.iteritems():

                    _operator, _value, chain = _directives[0]

                    if chain:
                        for subquery in chain:
                            if subquery.sub_operator is query.AND:
                                _and_filters.append(subquery)
                            if subquery.sub_operator is query.OR:
                                _or_filters.append(subquery)

                    # double-filters
                    if len(_filters[prop]) == 2:

                        # extract info
                        _operators = [
                            operator for operator, value in _directives
                        ]
                        _values = [value for operator, value in _directives]
                        _, prop = prop

                        # special case: maybe we can do a sorted range request
                        if (query.GREATER_THAN
                                in _operators) or (query.GREATER_THAN_EQUAL_TO
                                                   in _operators):
                            if (query.LESS_THAN
                                    in _operators) or (query.LESS_THAN_EQUAL_TO
                                                       in _operators):

                                # range value query over sorted index
                                greater, lesser = max(_values), min(_values)
                                _data_frame.append(
                                    cls.execute(
                                        *(cls.Operations.SORTED_RANGE_BY_SCORE,
                                          None, prop, min(_values),
                                          max(_values), options.offset,
                                          options.limit)))

                                continue

                    # single-filters
                    if len(_filters[prop]) == 1:

                        # extract info
                        _operator = [
                            operator for operator, value in _directives
                        ][0]
                        _value = float(
                            [value for operator, value in _directives][0])
                        _, prop = prop

                        if _operator is query.EQUALS:

                            # static value query over sorted index
                            _data_frame.append(
                                cls.execute(
                                    *(cls.Operations.SORTED_RANGE_BY_SCORE,
                                      None, prop, _value, _value,
                                      options.offset, options.limit)))

                            continue

                    ## @TODO(sgammon): build this query branch
                    raise RuntimeError("Specified query is not yet supported.")

            if unsorted_indexes:

                _intersections = set()
                for prop, _directives in unsorted_indexes.iteritems():

                    _operator, _value, chain = _directives[0]

                    if chain:
                        for subquery in chain:
                            if subquery.sub_operator is query.AND:
                                _and_filters.append(subquery)
                            if subquery.sub_operator is query.OR:
                                _or_filters.append(subquery)

                    _flag, index = prop
                    _intersections.add(index)

                # special case: only one unsorted set - pull content instead of a intersection merge
                if _intersections and len(_intersections) == 1:
                    _data_frame.append(
                        cls.execute(*(cls.Operations.SET_MEMBERS, None,
                                      _intersections.pop())))

                # more than one intersection: do an `SINTER` call instead of `SMEMBERS`
                if _intersections and len(_intersections) > 1:
                    _data_frame.append(
                        cls.execute(*(cls.Operations.SET_INTERSECT, None,
                                      _intersections)))

            if _data_frame:  # there were results, start merging
                _result_window = set()
                for frame in _data_frame:

                    if not len(_result_window):
                        _result_window = set(frame)
                        continue  # initial frame: fill background

                    _result_window = (_result_window & set(frame))
                matching_keys = [k for k in _result_window]
            else:
                matching_keys = []

        else:
            if not sorts:

                # grab all entities of this kind
                prefix = model.Key(kind, 0,
                                   parent=ancestry_parent).urlsafe().replace(
                                       '=', '')[0:-3]
                matching_keys = cls.execute(cls.Operations.KEYS, kind.kind(),
                                            prefix +
                                            '*')  # regex search it. why not

                # if we're doing keys only, we're done
                if options.keys_only and not (_and_filters or _or_filters):
                    return [
                        model.Key.from_urlsafe(k, _persisted=True)
                        for k in matching_keys
                    ]

        # otherwise, build entities and return
        result_entities = []

        # fetch keys
        with cls.channel(kind.kind()).pipeline() as pipeline:

            # fill pipeline
            _seen_results = 0
            for key in matching_keys:
                if (not (_and_filters or _or_filters)) and (
                        options.limit > 0 and _seen_results >= options.limit):
                    break
                _seen_results += 1

                cls.execute(cls.Operations.GET,
                            kind.kind(),
                            key,
                            target=pipeline)

            _blob_results = pipeline.execute()

            # execute pipeline, zip keys and build results
            for key, entity in zip([
                    model.Key.from_urlsafe(k, _persisted=True)
                    for k in matching_keys
            ], _blob_results):

                _seen_results = 0

                if not entity:
                    continue

                if (options.limit > 0) and _seen_results >= options.limit:
                    break

                # decode raw entity
                decoded = cls.get(None, None, entity)

                # attach key, decode entity and construct
                decoded['key'] = key

                if _and_filters or _or_filters:

                    if _and_filters and not all(
                        (filter.match(decoded) for filter in _and_filters)):
                        continue  # doesn't match one of the filters

                    if _or_filters and not any(
                        (filter.match(decoded) for filter in _or_filters)):
                        continue  # doesn't match any of the `or` filters

                # matches, by the grace of god
                _seen_results += 1

                result_entities.append(kind(_persisted=True, **decoded))

        return result_entities