def test_json_deserializer(): data = {"a": "test"} assert JSONDeserializer().deserialize(json.dumps(data)) == data data = {} assert JSONDeserializer().deserialize(json.dumps(data)) == {} data = None assert JSONDeserializer().deserialize(json.dumps(data)) is None
class RecordResourceConfig(ResourceConfig): """Record resource config.""" # Blueprint configuration blueprint_name = None url_prefix = "/records" routes = { "list": "", "item": "/<pid_value>", } # Request parsing request_read_args = {} request_view_args = {"pid_value": ma.fields.Str()} request_search_args = SearchRequestArgsSchema request_headers = {"if_match": ma.fields.Int()} request_body_parsers = { "application/json": RequestBodyParser(JSONDeserializer()) } default_content_type = "application/json" # Response handling response_handlers = { "application/json": ResponseHandler(JSONSerializer(), headers=etag_headers) } default_accept_mimetype = "application/json"
class Marc21RecordResourceConfig(RecordResourceConfig): """Marc21 Record resource configuration.""" blueprint_name = "marc21_records" url_prefix = url_prefix default_accept_mimetype = "application/json" response_handlers = record_serializer request_view_args = { "pid_value": ma.fields.Str(), "pid_type": ma.fields.Str(), } links_config = {} routes = record_ui_routes # Request parsing request_args = SearchRequestArgsSchema request_view_args = {"pid_value": ma.fields.Str()} request_headers = {"if_match": ma.fields.Int()} request_body_parsers = { "application/json": RequestBodyParser(JSONDeserializer()) } request_view_args = { "pid_value": ma.fields.Str(), "pid_type": ma.fields.Str(), }
from ..errors import ErrorHandlersMixin from .parser import RequestStreamParser # # Decorator helpers # request_view_args = request_parser( { 'pid_value': ma.fields.Str(required=True), 'key': ma.fields.Str() }, location='view_args') request_data = request_body_parser( parsers={"application/json": RequestBodyParser(JSONDeserializer())}, default_content_type="application/json", ) request_stream = request_body_parser( parsers={"application/octet-stream": RequestStreamParser()}, default_content_type="application/octet-stream", ) # # Resource # class FileResource(ErrorHandlersMixin, Resource): """File resource.""" def __init__(self, config, service):
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