示例#1
0
    def list_collection_join(self, path, keys):
        """
        Called from API to obtain list of collection items details.
        """

        # Verify that collection parent exists.
        self.model.path_row(path[:-1], keys)

        # Find endpoint for the collection itself.
        endpoint = schema.resolve_path(path)

        if endpoint.parent.table is None:
            # Top-level collections do not have any parents.
            rows = self.model[endpoint.table.name].list()
        else:
            # Filter using the endpoint filter and parent relationship.
            pname = endpoint.parent.table.name
            filter = dict(endpoint.filter)
            filter.update({pname: keys[pname]})
            rows = self.model[endpoint.table.name].list(**filter)
        join = schema.tables[endpoint.table.name].join
        if not join:
            return {}
        else:
            items = {}
            # Return rows keyed by primary key of joined table.
            to_expand = path[-1]
            if to_expand in join:
                for key in join:
                    if key not in keys:
                        for row in rows:
                            items[row.get(key)] = self.model[to_expand][row.get(key)].to_dict()
                            # Add data of the joining object
                            items[row.get(key)]['joined'] = {row.pkey: row.to_dict()}
                        return items
示例#2
0
    def list_collection(self, path, keys):
        """
        Called from API to obtain list of collection items.
        """

        # Verify that collection parent exists.
        self.model.path_row(path[:-1], keys)

        # Find endpoint for the collection itself.
        endpoint = schema.resolve_path(path)

        if endpoint.parent.table is None:
            # Top-level collections do not have any parents.
            # Filter using the endpoint filter
            filter = dict(endpoint.filter)
            rows = self.model[endpoint.table.name].list(**filter)
        else:
            # Filter using the endpoint filter and parent relationship.
            pname = endpoint.parent.table.name
            filter = dict(endpoint.filter)
            filter.update({pname: keys[pname]})
            rows = self.model[endpoint.table.name].list(**filter)

        if isinstance(schema.tables[endpoint.table.name].pkey, basestring):
            # Return rows keyed by the primary key.
            return {row.pkey: row.to_dict() for row in rows}
        else:
            # Return rows keyed by the missing portion of composite
            # primary key.
            for key in schema.tables[endpoint.table.name].pkey:
                if key not in keys:
                    return {row.get(key): row.to_dict() for row in rows}
示例#3
0
文件: api.py 项目: ponycloud/sparkle
    def make_handlers(path):
        endpoint = schema.resolve_path(path)

        def common_patch(creds, prefix):
            """
            PATCH handler for both collection and entity endpoints.
            """

            # Read patch from the client.
            patch = loads(flask.request.data)

            # And make sure it really is a patch and not something
            # totally weird that would blow up later.
            validate_json_patch(patch)

            # Alas, we need to poke into the patch a bit before we allow
            # it to execute.  Namely, we need to rebase paths and consult
            # access control rules.
            for op in patch:
                if 'path' in op:
                    # Determine whether this operation is a mutation.
                    # We have different access rules for reading and writing.
                    write = ('test' != op['op'])

                    # We default to empty dictionary when no value is given.
                    # This is however just for access control checks.
                    value = op.get('value', {})

                    # Adjust the path to include endpoint prefix.
                    op['path'] = prefix + normalize_path(op['path'])

                    # Validate the value at the path.
                    validate_dbdict_fragment(creds, value, op['path'], write)

                if 'from' in op:
                    # Adjust source path the same way as above.
                    op['from'] = prefix + normalize_path(op['from'])

                    # And verify that we can access the source data.
                    # XXX: This could be very broken as we should validate
                    #      access to all child entities and not just the root.
                    validate_dbdict_fragment(creds, {}, op['from'], False)

                # Remove None keys from values of non-merge operations.
                if 'value' in op and 'x-merge' != op['op']:
                    op['value'] = remove_nulls(op['value'])

            # Run the patch and hope for the best?
            return {'uuids': apply_patch(patch)}

        @app.require_credentials(manager)
        @convert_errors
        def collection_handler(credentials={}, **keys):
            jpath = endpoint.to_jpath(keys)[:-2]

            if 'GET' == flask.request.method:
                # Make sure all access control restrictions are applied.
                validate_dbdict_fragment(credentials, {}, jpath, False)

                try:
                    # Let the manager deal with model access and data
                    # retrieval.  XXX: Access control is broken there, BTW.
                    data = call_sync(manager.list_collection, path, keys)
                except KeyError:
                    raise PathError('not found', jpath)

                # We promised not to return any nulls in the output.
                return remove_nulls(data)

            if 'POST' == flask.request.method:
                # Read data from client.
                data = loads(flask.request.data)

                # Make sure it's at least a litle sane.
                if not isinstance(data, Mapping):
                    raise DataError('invalid entity', jpath)

                # Desired state must be present when POSTing.
                if 'desired' not in data:
                    raise DataError('desired state missing', jpath + ['desired'])

                # When primary keys must be specified by the user, make sure
                # that they are present and do not invent them on our own.
                if endpoint.table.user_pkey:
                    djpath = jpath + ['desired']
                    if isinstance(endpoint.table.pkey, basestring):
                        if endpoint.table.pkey not in data['desired']:
                            raise DataError('primary key missing', djpath + [endpoint.table.pkey])
                    else:
                        for key in endpoint.table.pkey:
                            if key not in data['desired']:
                                raise DataError('primary key missing', djpath + [key])

                if 'desired' in data:
                    # Extract the primary key placeholder.
                    pkey = data['desired'].get(endpoint.table.pkey, 'POST')
                else:
                    # Or default to one named 'POST'.
                    pkey = 'POST'

                # Get rid of keys with None values.
                data = remove_nulls(data)

                # Calculate hypothetical destination path.
                post_path = jpath + [pkey]

                # Make sure all access control restrictions are applied.
                validate_dbdict_fragment(credentials, data, post_path, True)

                # This is what the patch should look like:
                patch = [{'op': 'add', 'path': post_path, 'value': data}]

                # Apply the patch and return resulting set of UUIDs.
                return {'uuids': apply_patch(patch)}

            if 'PATCH' == flask.request.method:
                return common_patch(credentials, jpath)

        @app.require_credentials(manager)
        @convert_errors
        def entity_handler(credentials={}, **keys):
            jpath = endpoint.to_jpath(keys)[:-1]

            if 'GET' == flask.request.method:
                # Make sure that we can access the entity.
                validate_dbdict_fragment(credentials, {}, jpath, False)

                try:
                    # Get data from manager who can access the model.
                    data = call_sync(manager.get_entity, path, keys)
                except KeyError:
                    raise PathError('not found', jpath)

                # Strip keys with None values.
                return remove_nulls(data)

            if 'DELETE' == flask.request.method:
                # Make sure that we can write to that entity.
                validate_dbdict_fragment(credentials, {}, jpath, True)

                # Construct patch that would remove it.
                patch = [{'op': 'remove', 'path': jpath}]

                # Execute as usual.
                return {'uuids': apply_patch(patch)}

            if 'PATCH' == flask.request.method:
                return common_patch(credentials, jpath)

        # Rename handlers to satisfy Flask.
        collection_handler.__name__ = 'c_' + '_'.join(path)
        entity_handler.__name__ = 'e_' + '_'.join(path)

        if path[-1] not in endpoint.table.join:
            join_handler = None
        else:
            @app.require_credentials(manager)
            @convert_errors
            def join_handler(credentials={}, **keys):
                jpath = endpoint.to_jpath(keys)[:-2]

                if 'GET' == flask.request.method:
                    # Make sure all access control restrictions are applied.
                    validate_dbdict_fragment(credentials, {}, jpath, False)

                    try:
                        # Let the manager deal with model access and data
                        # retrieval.  XXX: Access control is broken there, BTW.
                        data = call_sync(manager.list_collection_join, path, keys)
                    except KeyError:
                        raise PathError('not found', jpath)

                    # We promised not to return any nulls in the output.
                    return remove_nulls(data)
            join_handler.__name__ = 'j_' + '_'.join(path)

        return collection_handler, entity_handler, join_handler