From f524dd3744c0a86bcbb98a7d13411bfb4a9efa21 Mon Sep 17 00:00:00 2001 From: Pavel Raiskup Date: Oct 02 2019 13:34:10 +0000 Subject: frontend: fix api_2 for marshmallow 3+ Follow-up: #1036 Merges: #1040 --- diff --git a/frontend/coprs_frontend/coprs/rest_api/common.py b/frontend/coprs_frontend/coprs/rest_api/common.py index 352a1ac..60475ad 100644 --- a/frontend/coprs_frontend/coprs/rest_api/common.py +++ b/frontend/coprs_frontend/coprs/rest_api/common.py @@ -37,7 +37,7 @@ def render_build(build, self_params=None): if self_params is None: self_params = {} return { - "build": BuildSchema().dump(build)[0], + "build": mm_serialize_one(BuildSchema, build), "_links": { "self": {"href": url_for(".buildr", build_id=build.id, **self_params)}, "project": {"href": url_for(".projectr", project_id=build.copr_id)}, diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/build.py b/frontend/coprs_frontend/coprs/rest_api/resources/build.py index 942fcfe..132dacd 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/build.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/build.py @@ -79,7 +79,7 @@ class BuildListR(Resource): """ :return: if of the created build or raise Exception """ - build_params = mm_deserialize(BuildCreateFromUrlSchema(), req.data.decode("utf-8")).data + build_params = mm_deserialize(BuildCreateFromUrlSchema(), req.data.decode("utf-8")) project = get_project_safe(build_params["project_id"]) chroot_names = build_params.pop("chroots") @@ -117,7 +117,7 @@ class BuildListR(Resource): raise MalformedRequest("Missing srpm file in the request") srpm_handle = req.files["srpm"] - build_params = mm_deserialize(BuildCreateSchema(), metadata).data + build_params = mm_deserialize(BuildCreateSchema(), metadata) project_id = build_params["project_id"] project = get_project_safe(project_id) @@ -207,7 +207,7 @@ class BuildR(Resource): @rest_api_auth_required def put(self, build_id): build = get_build_safe(build_id) - build_dict = mm_deserialize(BuildSchema(), flask.request.data.decode("utf-8")).data + build_dict = mm_deserialize(BuildSchema(), flask.request.data.decode("utf-8")) try: if not build.canceled and build_dict["state"] == "canceled": BuildsLogic.cancel_build(flask.g.user, build) diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py index a275bd2..35b49e4 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/mock_chroot.py @@ -6,12 +6,12 @@ from flask_restful import Resource from ...logic.coprs_logic import MockChrootsLogic from ..schemas import MockChrootSchema -from ..util import get_one_safe, get_request_parser, arg_bool +from ..util import get_one_safe, get_request_parser, arg_bool, mm_serialize_one def render_mock_chroot(chroot): return { - "chroot": MockChrootSchema().dump(chroot)[0], + "chroot": mm_serialize_one(MockChrootSchema, chroot), "_links": { "self": {"href": url_for(".mockchrootr", name=chroot.name)}, }, diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project.py b/frontend/coprs_frontend/coprs/rest_api/resources/project.py index 6a6a464..212e983 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/project.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/project.py @@ -29,9 +29,7 @@ class ProjectListR(Resource): Creates new copr """ user = flask.g.user - result = mm_deserialize(ProjectCreateSchema(), flask.request.data.decode("utf-8")) - - req = result.data + req = mm_deserialize(ProjectCreateSchema(), flask.request.data.decode("utf-8")) name = req.pop("name") selected_chroots = req.pop("chroots", None) @@ -158,8 +156,7 @@ class ProjectR(Resource): """ project = get_project_safe(project_id) - project_dict = mm_deserialize(ProjectSchema(), flask.request.data.decode("utf-8")).data - # pprint(project_dict) + project_dict = mm_deserialize(ProjectSchema(), flask.request.data.decode("utf-8")) for k, v in project_dict.items(): setattr(project, k, v) diff --git a/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py index ecb5b99..8dff823 100644 --- a/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py +++ b/frontend/coprs_frontend/coprs/rest_api/resources/project_chroot.py @@ -42,10 +42,8 @@ class ProjectChrootListR(Resource): def post(cls, project_id): project = get_project_safe(project_id) - chroot_data = mm_deserialize(CoprChrootCreateSchema(), - flask.request.data.decode("utf-8")) - - req = chroot_data.data + req = mm_deserialize(CoprChrootCreateSchema(), + flask.request.data.decode("utf-8")) name = req.pop("name") try: @@ -111,7 +109,7 @@ class ProjectChrootR(Resource): updated_chroot = CoprChrootsLogic.update_chroot( user=flask.g.user, copr_chroot=chroot, - **chroot_data.data + **chroot_data ) except InsufficientRightsException as err: raise AccessForbidden("Failed to update copr chroot: {}".format(err)) diff --git a/frontend/coprs_frontend/coprs/rest_api/schemas.py b/frontend/coprs_frontend/coprs/rest_api/schemas.py index 7b7f554..0de4499 100644 --- a/frontend/coprs_frontend/coprs/rest_api/schemas.py +++ b/frontend/coprs_frontend/coprs/rest_api/schemas.py @@ -1,10 +1,40 @@ # coding: utf-8 from collections.abc import Iterable -from marshmallow import Schema, fields, ValidationError, validate +from marshmallow import Schema as _Schema, fields, ValidationError, validate from six import string_types +loads_kwargs = {} +try: + from marshmallow.utils import EXCLUDE + loads_kwargs = {"unknown": EXCLUDE} +except ImportError: + pass + + +class Schema(_Schema): + # In marshmallow v3+ there's no data/errors attributes as it was + # in v2. So we need to have compat wrapper. + + def wrap_function(self, name, *args, **kwargs): + super_object = super(Schema, self) + super_result = getattr(super_object, name)(*args, **kwargs) + if hasattr(super_result, 'data') and hasattr(super_result, 'errors'): + # old marshmallow + if super_result.errors: + raise ValidationError(super_result.errors) + return super_result.data + else: + return super_result + + def dump(self, *args, **kwargs): + return self.wrap_function('dump', *args, **kwargs) + + def loads(self, *args, **kwargs): + kwargs.update(loads_kwargs) + return self.wrap_function('loads', *args, **kwargs) + def validate_any(fn_list): """ :param fn_list: list of callable functions, each takes one param @@ -28,12 +58,12 @@ def validate_any(fn_list): class SpaceSeparatedList(fields.Field): - def _serialize(self, value, attr, obj): + def _serialize(self, value, attr, obj, **kwargs): if value is None: return [] return value.split() - def _deserialize(self, value, attr=None, data=None): + def _deserialize(self, value, attr=None, data=None, **kwargs): if value is None: return "" elif not isinstance(value, Iterable) or isinstance(value, string_types): @@ -50,7 +80,7 @@ class BuiltPackages(fields.Field): { name: "pkg", version: "pkg version" } we implement only the serialization, since field is read-only """ - def _serialize(self, value, attr, obj): + def _serialize(self, value, attr, obj, **kwargs): if value is None: return [] result = [] diff --git a/frontend/coprs_frontend/coprs/rest_api/util.py b/frontend/coprs_frontend/coprs/rest_api/util.py index 444bd94..3b2f1a4 100644 --- a/frontend/coprs_frontend/coprs/rest_api/util.py +++ b/frontend/coprs_frontend/coprs/rest_api/util.py @@ -5,6 +5,7 @@ import sqlalchemy.orm.exc from flask_restful.reqparse import Argument, RequestParser +from marshmallow.exceptions import ValidationError from .exceptions import ObjectNotFoundError, MalformedRequest from .schemas import AllowedMethodSchema @@ -18,7 +19,8 @@ class AllowedMethod(object): def render_allowed_method(method, doc, require_auth=True, params=None): - return AllowedMethodSchema().dump(AllowedMethod(method, doc, require_auth, params))[0] + method = AllowedMethod(method, doc, require_auth, params) + return mm_serialize_one(AllowedMethodSchema, method) def get_one_safe(query, msg=None, data=None): @@ -44,26 +46,18 @@ def json_loads_safe(raw, data_on_error=None): def mm_deserialize(schema, json_string): try: - result = schema.loads(json_string) - except ValueError as err: + return schema.loads(json_string) + except (ValueError, ValidationError) as err: raise MalformedRequest( msg="Failed to parse request: {}".format(err), data={"request_string": json_string} ) - if result.errors: - raise MalformedRequest( - msg="Failed to parse request: Validation Error", - data={ - "validation_errors": result.errors - } - ) - - return result - - def mm_serialize_one(schema, obj): - return schema().dump(obj)[0] + try: + return schema().dump(obj) + except ValidationError as err: + raise MalformedRequest("Failed to parse request") class MyArg(Argument):