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], )) }
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.", )), }
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), ]
"""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>", }
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
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
# 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>"
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, ) ), }