Example #1
0
class RDMRecordResourceConfig(RecordResourceConfig):
    """Record resource configuration."""

    blueprint_name = "records"
    url_prefix = "/records"

    routes = RecordResourceConfig.routes

    # PIDs
    routes["item-pids-reserve"] = "/<pid_value>/draft/pids/<scheme>"
    # Review
    routes["item-review"] = "/<pid_value>/draft/review"
    routes["item-actions-review"] = "/<pid_value>/draft/actions/submit-review"
    # Community records
    routes["community-records"] = "/communities/<pid_value>/records"

    request_view_args = {
        "pid_value": ma.fields.Str(),
        "scheme": ma.fields.Str(),
    }

    request_read_args = {
        "style": ma.fields.Str(),
        "locale": ma.fields.Str(),
    }

    request_search_args = RDMSearchRequestArgsSchema

    response_handlers = record_serializers

    error_handlers = {
        StyleNotFoundError:
        create_error_handler(
            HTTPJSONException(
                code=400,
                description=_("Citation string style not found."),
            )),
        ReviewNotFoundError:
        create_error_handler(
            HTTPJSONException(
                code=404,
                description=_("Review for draft not found"),
            )),
        ReviewStateError:
        create_error_handler(lambda e: HTTPJSONException(
            code=400,
            description=str(e),
        )),
        ReviewExistsError:
        create_error_handler(lambda e: HTTPJSONException(
            code=400,
            description=str(e),
        )),
        InvalidRelationValue:
        create_error_handler(lambda exc: HTTPJSONException(
            code=400,
            description=exc.args[0],
        ))
    }
Example #2
0
class RDMRecordResourceConfig(RecordResourceConfig):
    """Record resource configuration."""

    blueprint_name = "records"
    url_prefix = "/records"

    routes = RecordResourceConfig.routes

    # PIDs
    routes["item-pids-reserve"] = "/<pid_value>/draft/pids/<pid_type>"

    request_view_args = {
        "pid_value": ma.fields.Str(),
        "pid_type": ma.fields.Str(),
    }

    request_read_args = {
        "style": ma.fields.Str(),
        "locale": ma.fields.Str(),
    }

    request_search_args = RDMSearchRequestArgsSchema

    response_handlers = record_serializers

    error_handlers = {
        StyleNotFoundError:
        create_error_handler(
            HTTPJSONException(
                code=400,
                description="Citation string style not found.",
            )),
    }
class ErrorHandlersMixin:
    """Mixin to define common error handlers."""

    error_handlers = {
        ma.ValidationError:
        create_error_handler(lambda e: HTTPJSONValidationException(e)),
        RevisionIdMismatchError:
        create_error_handler(lambda e: HTTPJSONException(
            code=412,
            description=e.description,
        )),
        QuerystringValidationError:
        create_error_handler(
            HTTPJSONException(
                code=400,
                description="Invalid querystring parameters.",
            )),
        PermissionDeniedError:
        create_error_handler(
            HTTPJSONException(
                code=403,
                description="Permission denied.",
            )),
        PIDDeletedError:
        create_error_handler(
            HTTPJSONException(
                code=410,
                description="The record has been deleted.",
            )),
        PIDAlreadyExists:
        create_error_handler(
            HTTPJSONException(
                code=400,
                description="The persistent identifier is already registered.",
            )),
        PIDDoesNotExistError:
        create_error_handler(
            HTTPJSONException(
                code=404,
                description="The persistent identifier does not exist.",
            )),
        PIDUnregistered:
        create_error_handler(
            HTTPJSONException(
                code=404,
                description="The persistent identifier is not registered.",
            )),
        PIDRedirectedError:
        create_pid_redirected_error_handler(),
        NoResultFound:
        create_error_handler(
            HTTPJSONException(
                code=404,
                description="Not found.",
            )),
    }
Example #4
0
    class TestResource(Resource):
        error_handlers = {
            ma.ValidationError:
            create_error_handler(HTTPJSONException(code=400))
        }

        @request_parser({"id": ma.fields.Int()}, location="args")
        def args(self):
            return resource_requestctx.args, 200

        @request_parser({"id": ma.fields.List(ma.fields.Int())},
                        location="args",
                        unknown=ma.RAISE)
        def multiargs(self):
            return resource_requestctx.args, 200

        @request_parser({"id": ma.fields.Int()}, location="view_args")
        def view_args(self):
            return resource_requestctx.view_args, 200

        @request_parser({"if_match": ma.fields.Int(required=True)},
                        location="headers")
        def header(self):
            return resource_requestctx.headers, 200

        @request_parser(
            {"if_match": ma.fields.Int(required=True)},
            location="headers",
            unknown=ma.INCLUDE,
        )
        def header_unknown(self):
            return resource_requestctx.headers, 200

        def create_url_rules(self):
            return [
                route("GET", "/args", self.args),
                route("GET", "/multiargs", self.multiargs),
                route("GET", "/viewargs/<id>", self.view_args),
                route("GET", "/header", self.header),
                route("GET", "/header-unknown", self.header_unknown),
            ]
Example #5
0
    """Bibliographic record files resource config."""

    blueprint_name = "draft_files"
    url_prefix = "/records/<pid_value>/draft"


#
# Parent Record Links
#
record_links_error_handlers = RecordResourceConfig.error_handlers.copy()

record_links_error_handlers.update({
    LookupError:
    create_error_handler(
        HTTPJSONException(
            code=404,
            description="No secret link found with the given ID.",
        )),
})


class RDMParentRecordLinksResourceConfig(RecordResourceConfig):
    """User records resource configuration."""

    blueprint_name = "record_access"

    url_prefix = "/records/<pid_value>/access"

    routes = {
        "list": "/links",
        "item": "/links/<link_id>",
    }
Example #6
0
    def image_api(self):
        """IIIF API Implementation.

        .. note::
            * IIF IMAGE API v1.0
                * For more infos please visit <http://iiif.io/api/image/>.
            * IIIF Image API v2.0
                * For more infos please visit <http://iiif.io/api/image/2.0/>.
            * The API works only for GET requests
            * The image process must follow strictly the following workflow:
                * Region
                * Size
                * Rotation
                * Quality
                * Format
        """
        image_format = resource_requestctx.view_args["image_format"]
        uuid = resource_requestctx.view_args["uuid"]
        region = resource_requestctx.view_args["region"]
        size = resource_requestctx.view_args["size"]
        rotation = resource_requestctx.view_args["rotation"]
        quality = resource_requestctx.view_args["quality"]
        to_serve = self.service.image_api(
            identity=g.identity,
            uuid=uuid,
            region=region,
            size=size,
            rotation=rotation,
            quality=quality,
            image_format=image_format,
        )
        # decide the mime_type from the requested image_format
        mimetype = self.config.supported_formats.get(
            image_format, "image/jpeg"
        )
        # TODO: get from cache on the service image.last_modified
        last_modified = None
        send_file_kwargs = {"mimetype": mimetype}
        # last_modified is not supported before flask 0.12
        if last_modified:
            send_file_kwargs.update(last_modified=last_modified)

        if "dl" in request.args:
            filename = secure_filename(request.args.get("dl", ""))
            if filename.lower() in {"", "1", "true"}:
                filename = "{0}-{1}-{2}-{3}-{4}.{5}".format(
                    uuid, region, size, quality, rotation, image_format
                )
            send_file_kwargs.update(
                as_attachment=True,
                attachment_filename=secure_filename(filename),
            )
        if_modified_since_raw = request.headers.get("If-Modified-Since")
        if if_modified_since_raw:
            if_modified_since = datetime.datetime(
                *parsedate(if_modified_since_raw)[:6]
            )
            if if_modified_since and if_modified_since >= last_modified:
                raise HTTPJSONException(code=304)
        response = send_file(to_serve, **send_file_kwargs)
        return response
Example #7
0
class TodoResource(Resource):
    def __init__(self, config, service):
        super().__init__(config)
        # The service layer is injected into the resource, so that the resource
        # have a service instance to perform it's task with.
        self.service = service

    #
    # Resource API
    #
    error_handlers = {
        # Here we map data and service level exceptions into errors for the
        # user. This dictionary is passed directly to
        # Blueprint.register_error_handler().
        NoResultError: create_error_handler(
            # The HTTPJSONException is responsible for creating an HTTP
            # response with a JSON-formatted body. We do not do content
            # negotiation on errors.
            HTTPJSONException(code=404, description="Not found"),
        ),
        ma.exceptions.ValidationError: create_error_handler(
            HTTPJSONException(code=400, description="Bad request"),
        ),
        PermissionDenied: create_error_handler(
            HTTPJSONException(code=403, description="Forbidden"),
        ),
    }

    def create_url_rules(self):
        # Here we define the RESTful routes. The return value is passed
        # directly to Blueprint.add_url_rule().
        return [
            # The "route()" does a couple of things:
            # - it enforces one HTTP method = one class method (similar to
            #   flask.MethodView, however it allows many methods)
            # - it puts more emphasis on the HTTP method
            # - it wraps the resource method (e.g. self.create) in a >>resource
            #   request context<<. More on that below.
            # You are not required to use the "route()".
            route("POST", "", self.create),
            route("GET", "", self.search),
            route("GET", "/<item_id>", self.read),
        ]

    #
    # Internals
    #
    def _make_identity(self, user_id):
        # This method is a replacement for having proper login system etc.
        if user_id is not None:
            i = Identity(user_id)
            i.provides.add(UserNeed(user_id))
            i.provides.add(RoleNeed("authenticated_user"))
            return i
        else:
            return AnonymousIdentity()

    #
    # View methods
    #
    # Most view methods looks like below:
    # - A couple of decorators to extract arguments from the HTTP request,
    # - A single call to a service method
    # - Returns a simple dict representation of their object with a HTTP status
    #   code

    # The user request parser is a decorator defined above (because we use it
    # multiple times DRY).
    @user_request_parser
    # The request body parser allows the client to send data in many different
    # data formats by sending the Content-Type header (e.g.
    # "Content-Type: application/json"). This can e.g be used for versioning
    # the REST API.
    @request_body_parser(parsers={
        "application/json": RequestBodyParser(JSONDeserializer())
    })
    # The response handler, is the decorator which allows the view to return
    # an dict object instead of a HTTP response. The response handler works in
    # conjunction with the HTTP content negotiation. That is, if a client
    # sends a "Accept: application/json" header, the response handler will
    # choose the appropriate serializer (e.g. return XML, JSON, plain text, ..)
    @response_handler()
    def create(self):
        # The view method itself, does not take any arguments. This is because
        # we ensure that all data is validated and passed through the resource
        # request context (i.e. anything that ends up in "resource_requestctx"
        # has been validated according to the rules defined).
        identity = self._make_identity(resource_requestctx.args['user'])

        item = self.service.create(
            identity,
            resource_requestctx.data,
        )
        # A view may return a dict if the @response_handler decorator was used.
        # Alternatively, the view can also simply return a normal
        # Flask.Response.
        return item.to_dict(), 201

    @user_request_parser
    @request_parser(
        # This request parser extracts the item id from the URL. The name
        # "item_id" is the one we used in create_url_rules().
        {'item_id': ma.fields.Int(required=True)},
        location='view_args',
        unknown=ma.RAISE,
    )
    @response_handler()
    def read(self):
        identity = self._make_identity(resource_requestctx.args['user'])

        item = self.service.read(
            identity,
            resource_requestctx.view_args['item_id'],
        )

        return item.to_dict(), 200

    @user_request_parser
    @request_parser(
        {
            'page': ma.fields.Int(
                missing=1,
                validate=ma.validate.Range(min=1),
            ),
            'size': ma.fields.Int(
                missing=10,
                validate=ma.validate.Range(min=1)
            ),
        },
        location='args',
        unknown=ma.EXCLUDE,
    )
    # When return a list results, we add the "many=True". This is because the
    # serializer may need special handling for lists - e.g. enveloping the
    # results. For the default JSONSerializer which we use in this example,
    # there's no difference be between many=True/many=False.
    @response_handler(many=True)
    def search(self):
        identity = self._make_identity(resource_requestctx.args['user'])

        item_list = self.service.search(
            identity,
            page=resource_requestctx.args['page'],
            size=resource_requestctx.args['size'],
        )

        return item_list.to_dict(), 200
Example #8
0
# Copyright (C) 2020-2021 CERN.
#
# Invenio-Records-Resources is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see LICENSE file for more
# details.
"""Invenio Communities Resource API config."""

from flask_resources import HTTPJSONException, create_error_handler
from invenio_records_resources.resources import RecordResourceConfig

community_error_handlers = RecordResourceConfig.error_handlers.copy()
community_error_handlers.update({
    FileNotFoundError:
    create_error_handler(
        HTTPJSONException(
            code=404,
            description="No logo exists for this community.",
        )),
})


class CommunityResourceConfig(RecordResourceConfig):
    """Communities resource configuration."""

    blueprint_name = "communities"
    url_prefix = ""

    routes = {
        "communities-prefix": "/communities",
        "user-prefix": "/user/communities",
        "list": "",
        "item": "/<pid_value>"
Example #9
0
import marshmallow as ma
from flask_babelex import lazy_gettext as _
from flask_resources import HTTPJSONException, ResourceConfig, \
    create_error_handler
from invenio_records_resources.resources.errors import ErrorHandlersMixin
from invenio_records_resources.resources.records.args import \
    SearchRequestArgsSchema

from ..services.errors import OAIPMHError, OAIPMHSetDoesNotExistError, \
    OAIPMHSetIDDoesNotExistError

oaipmh_error_handlers = {
    **ErrorHandlersMixin.error_handlers,
    OAIPMHSetDoesNotExistError: create_error_handler(
        lambda e: HTTPJSONException(
            code=404,
            description=e.description,
        )
    ),
    OAIPMHSetIDDoesNotExistError: create_error_handler(
        lambda e: HTTPJSONException(
            code=404,
            description=e.description,
        )
    ),
    OAIPMHError: create_error_handler(
        lambda e: HTTPJSONException(
            code=400,
            description=e.description,
        )
    ),
}