示例#1
0
    def test_unit__exception_handled__ok__exception_error_dict(self):
        class MyException(Exception):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.error_dict = {}

        context = AgnosticContext(app=None)
        error_builder = MarshmallowDefaultErrorBuilder()
        wrapper = ExceptionHandlerControllerWrapper(
            MyException,
            context,
            error_builder=error_builder,
            http_code=HTTPStatus.INTERNAL_SERVER_ERROR,
            processor_factory=lambda schema_: MarshmallowProcessor(error_builder.get_schema()),
        )

        @wrapper.get_wrapper
        def func(foo):
            exc = MyException("We are testing")
            exc.error_detail = {"foo": "bar"}
            raise exc

        response = func(42)
        assert response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
        assert {
            "message": "We are testing",
            "details": {"error_detail": {"foo": "bar"}},
            "code": None,
        } == json.loads(response.body)
示例#2
0
    def test_unit__map_binding__ok__mapped_method(self):
        hapic_ = hapic.Hapic(processor_class=MarshmallowProcessor)
        app = bottle.Bottle()
        context = hapic.ext.bottle.BottleContext(
            app=app, default_error_builder=MarshmallowDefaultErrorBuilder()
        )
        hapic_.set_context(context)

        class MyControllers(object):
            def bind(self, app):
                app.route("/", callback=self.controller_a)

            @hapic_.with_api_doc()
            def controller_a(self):
                pass

        my_controllers = MyControllers()
        my_controllers.bind(app)

        assert hapic_.controllers
        decoration = hapic_.controllers[0]
        route = context.find_route(decoration)

        assert route
        # Important note: instance controller_a method is
        # not class controller_a, so no matches with callbacks
        assert route.original_route_object.callback != MyControllers.controller_a
        assert route.original_route_object.callback != decoration.reference.wrapped
        assert route.original_route_object.callback != decoration.reference.wrapper
    def test_func__catch_one_exception__ok__pyramid(self):
        from pyramid.config import Configurator

        configurator = Configurator(autocommit=True)
        context = PyramidContext(
            configurator,
            default_error_builder=MarshmallowDefaultErrorBuilder())
        hapic = Hapic(processor_class=MarshmallowProcessor)
        hapic.set_context(context)

        def my_view(context, request):
            raise ZeroDivisionError("An exception message")

        configurator.add_route("my_view", "/my-view", request_method="GET")
        configurator.add_view(my_view, route_name="my_view", renderer="json")

        # FIXME - G.M - 17-05-2018 - Verify if:
        # - other view that work/raise an other exception do not go
        # into this code for handle this exceptions.
        # - response come clearly from there, not from web framework:
        #  Check not only http code, but also body.
        # see  issue #158 (https://github.com/algoo/hapic/issues/158)
        context.handle_exception(ZeroDivisionError, http_code=400)

        app = configurator.make_wsgi_app()
        test_app = TestApp(app)
        response = test_app.get("/my-view", status="*")

        assert 400 == response.status_code
示例#4
0
    def test_unit__generate_output_stream_doc__ok__nominal_case(
            self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class OuputStreamItemSchema(marshmallow.Schema):
            name = marshmallow.fields.String(required=True)

        @hapic.with_api_doc()
        @hapic.output_stream(OuputStreamItemSchema())
        async def get_users(request, hapic_data):
            pass

        app = web.Application(debug=True)
        app.router.add_get("/", get_users)
        hapic.set_context(
            AiohttpContext(
                app, default_error_builder=MarshmallowDefaultErrorBuilder()))

        doc = hapic.generate_doc("aiohttp", "testing")
        # INFO BS 2019-04-15: Prevent keep of OrderedDict
        doc = json.loads(json.dumps(doc))

        assert "/" in doc.get("paths")
        assert "get" in doc["paths"]["/"]
        assert "200" in doc["paths"]["/"]["get"].get("responses", {})
        assert {
            "items": {
                "$ref": "#/definitions/OuputStreamItemSchema"
            },
            "type": "array"
        } == doc["paths"]["/"]["get"]["responses"]["200"]["schema"]
示例#5
0
    async def test_aiohttp_input_path__error_wrong_input_parameter(
            self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class InputPathSchema(marshmallow.Schema):
            i = marshmallow.fields.Integer()

        @hapic.input_path(InputPathSchema())
        async def hello(request, hapic_data: HapicData):
            i = hapic_data.path.get("i")
            return web.Response(text="integer: {}".format(str(i)))

        app = web.Application(debug=True)
        app.router.add_get("/{i}", hello)
        hapic.set_context(
            AiohttpContext(
                app, default_error_builder=MarshmallowDefaultErrorBuilder()))
        client = await aiohttp_client(app)

        resp = await client.get("/bob")  # NOTE: should be integer here
        assert resp.status == 400

        error = await resp.json()
        assert "Validation error of input data" in error.get("message")
        assert {"i": ["Not a valid integer."]} == error.get("details")
示例#6
0
    async def test_aiohttp_output_body__error__incorrect_output_body(
            self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class OuputBodySchema(marshmallow.Schema):
            i = marshmallow.fields.Integer(required=True)

        @hapic.output_body(OuputBodySchema())
        async def hello(request):
            return {"i": "bob"}  # NOTE: should be integer

        app = web.Application(debug=True)
        app.router.add_get("/", hello)
        hapic.set_context(
            AiohttpContext(
                app, default_error_builder=MarshmallowDefaultErrorBuilder()))
        client = await aiohttp_client(app)

        resp = await client.get("/")
        assert resp.status == 500

        data = await resp.text()
        data = await resp.json()
        assert "Validation error of output data" == data.get("message")
        assert {
            "i": ["Missing data for required field."]
        } == data.get("details")
示例#7
0
    async def test_unit__post_file__ok__put_success(self, aiohttp_client,
                                                    loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class InputFilesSchema(marshmallow.Schema):
            avatar = marshmallow.fields.Raw()

        @hapic.with_api_doc()
        @hapic.input_files(InputFilesSchema())
        async def update_avatar(request: Request, hapic_data: HapicData):
            avatar = hapic_data.files["avatar"]
            assert isinstance(avatar, FileField)
            assert b"text content of file" == avatar.file.read()
            return Response(body="ok")

        app = web.Application(debug=True)
        app.router.add_put("/avatar", update_avatar)
        hapic.set_context(
            AiohttpContext(
                app, default_error_builder=MarshmallowDefaultErrorBuilder()))
        client = await aiohttp_client(app)

        resp = await client.put(
            "/avatar", data={"avatar": io.StringIO("text content of file")})
        assert resp.status == 200
示例#8
0
 def __init__(
     self,
     app=AgnosticApp(),
     default_error_builder=MarshmallowDefaultErrorBuilder(),
     path_parameters=None,
     query_parameters=None,
     body_parameters=None,
     form_parameters=None,
     header_parameters=None,
     files_parameters=None,
     debug=False,
     path_url_regex=PATH_URL_REGEX,
 ) -> None:
     super().__init__(default_error_builder=default_error_builder)
     self.debug = debug
     self._handled_exceptions = []  # type: typing.List[HandledException]
     self.app = app
     self._exceptions_handler_installed = False
     self.path_url_regex = path_url_regex
     self.path_parameters = path_parameters or {}
     self.query_parameters = query_parameters or MultiDict()
     self.body_parameters = body_parameters or {}
     self.form_parameters = form_parameters or MultiDict()
     self.header_parameters = header_parameters or {}
     self.files_parameters = files_parameters or {}
示例#9
0
    async def test_aiohttp_output_stream__ok__nominal_case(self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class AsyncGenerator:
            def __init__(self):
                self._iterator = iter([{"name": "Hello, bob"}, {"name": "Hello, franck"}])

            async def __aiter__(self):
                return self

            async def __anext__(self):
                return next(self._iterator)

        class OuputStreamItemSchema(marshmallow.Schema):
            name = marshmallow.fields.String()

        @hapic.output_stream(OuputStreamItemSchema())
        async def hello(request):
            return AsyncGenerator()

        app = web.Application(debug=True)
        app.router.add_get("/", hello)
        hapic.set_context(
            AiohttpContext(app, default_error_builder=MarshmallowDefaultErrorBuilder())
        )
        client = await aiohttp_client(app)

        resp = await client.get("/")
        assert resp.status == 200

        line = await resp.content.readline()
        assert b'{"name": "Hello, bob"}\n' == line

        line = await resp.content.readline()
        assert b'{"name": "Hello, franck"}\n' == line
示例#10
0
    async def test_unit__post_file__ok__missing_file(self, aiohttp_client,
                                                     loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class InputFilesSchema(marshmallow.Schema):
            avatar = marshmallow.fields.Raw(required=True)

        @hapic.with_api_doc()
        @hapic.input_files(InputFilesSchema())
        async def update_avatar(request: Request, hapic_data: HapicData):
            raise AssertionError("Test should no pass here")

        app = web.Application(debug=True)
        app.router.add_put("/avatar", update_avatar)
        hapic.set_context(
            AiohttpContext(
                app, default_error_builder=MarshmallowDefaultErrorBuilder()))
        client = await aiohttp_client(app)

        resp = await client.put("/avatar")
        assert resp.status == 400
        json_ = await resp.json()
        assert {
            "details": {
                "avatar": ["Missing data for required field"]
            },
            "message": "Validation error of input data",
            "code": None,
        } == json_
示例#11
0
    async def test_aiohttp_output_stream__error__interrupt_py37(
            self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class OuputStreamItemSchema(marshmallow.Schema):
            name = marshmallow.fields.String(required=True)

        from .py37.stream import get_func_with_output_stream_and_error

        hello = get_func_with_output_stream_and_error(hapic,
                                                      OuputStreamItemSchema,
                                                      ignore_on_error=False)

        app = web.Application(debug=True)
        app.router.add_get("/", hello)
        hapic.set_context(
            AiohttpContext(
                app, default_error_builder=MarshmallowDefaultErrorBuilder()))
        client = await aiohttp_client(app)

        resp = await client.get("/")
        assert resp.status == 200

        line = await resp.content.readline()
        assert b'{"name": "Hello, bob"}\n' == line

        line = await resp.content.readline()
        assert b"" == line
示例#12
0
    async def test_unit__general_exception_handling__ok__nominal_case(
            self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        @hapic.with_api_doc()
        async def hello(request):
            return 1 / 0

        app = web.Application(debug=True)
        app.router.add_get("/", hello)
        context = AiohttpContext(
            app, default_error_builder=MarshmallowDefaultErrorBuilder())
        context.handle_exception(ZeroDivisionError, 400)
        hapic.set_context(context)

        client = await aiohttp_client(app)
        resp = await client.get("/")

        json = await resp.json()
        assert resp.status == 400
        assert {
            "details": {
                "error_detail": {}
            },
            "message": "division by zero",
            "code": None,
        } == json
示例#13
0
    async def test_aiohttp_input_body__error__incorrect_input_body(
            self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class InputBodySchema(marshmallow.Schema):
            i = marshmallow.fields.Integer()

        @hapic.input_body(InputBodySchema())
        async def hello(request, hapic_data: HapicData):
            i = hapic_data.body.get("i")
            return web.Response(text="integer, {}".format(i))

        app = web.Application(debug=True)
        app.router.add_post("/", hello)
        hapic.set_context(
            AiohttpContext(
                app, default_error_builder=MarshmallowDefaultErrorBuilder()))
        client = await aiohttp_client(app)

        resp = await client.post("/", data={"i": "bob"})  # NOTE: should be int
        assert resp.status == 400

        error = await resp.json()
        assert "Validation error of input data" in error.get("message")
        assert {"i": ["Not a valid integer."]} == error.get("details")
示例#14
0
 def build_from_exception(self,
                          exception: Exception,
                          include_traceback: bool = False) -> dict:
     error_dict = MarshmallowDefaultErrorBuilder.build_from_exception(
         self, exception, include_traceback)
     code = getattr(exception, "error_code", None)
     error_dict["code"] = code
     return error_dict
示例#15
0
    def test_unit__generate_doc__ok__nominal_case(self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class InputPathSchema(marshmallow.Schema):
            username = marshmallow.fields.String(required=True)

        class InputQuerySchema(marshmallow.Schema):
            show_deleted = marshmallow.fields.Boolean(required=False)

        class UserSchema(marshmallow.Schema):
            name = marshmallow.fields.String(required=True)

        @hapic.with_api_doc()
        @hapic.input_path(InputPathSchema())
        @hapic.input_query(InputQuerySchema())
        @hapic.output_body(UserSchema())
        async def get_user(request, hapic_data):
            pass

        @hapic.with_api_doc()
        @hapic.input_path(InputPathSchema())
        @hapic.output_body(UserSchema())
        async def post_user(request, hapic_data):
            pass

        app = web.Application(debug=True)
        app.router.add_get("/{username}", get_user)
        app.router.add_post("/{username}", post_user)
        hapic.set_context(
            AiohttpContext(app, default_error_builder=MarshmallowDefaultErrorBuilder())
        )

        doc = hapic.generate_doc("aiohttp", "testing")
        # INFO BS 2019-04-15: Prevent keep of OrderedDict
        doc = json.loads(json.dumps(doc))

        assert "UserSchema" in doc.get("definitions")
        assert {"name": {"type": "string"}} == doc["definitions"]["UserSchema"].get("properties")
        assert "/{username}" in doc.get("paths")
        assert "get" in doc["paths"]["/{username}"]
        assert "post" in doc["paths"]["/{username}"]

        assert [
            {"name": "username", "in": "path", "required": True, "type": "string"},
            {"name": "show_deleted", "in": "query", "required": False, "type": "boolean"},
        ] == doc["paths"]["/{username}"]["get"]["parameters"]
        assert {
            "200": {"schema": {"$ref": "#/definitions/UserSchema"}, "description": "200"}
        } == doc["paths"]["/{username}"]["get"]["responses"]

        assert [{"name": "username", "in": "path", "required": True, "type": "string"}] == doc[
            "paths"
        ]["/{username}"]["post"]["parameters"]
        assert {
            "200": {"schema": {"$ref": "#/definitions/UserSchema"}, "description": "200"}
        } == doc["paths"]["/{username}"]["get"]["responses"]
示例#16
0
def get_flask_context():
    from example.fake_api.flask_api import hapic as h

    flask_app = Flask(__name__)
    controllers = FlaskController()
    controllers.bind(flask_app)
    h.reset_context()
    h.set_context(
        FlaskContext(flask_app,
                     default_error_builder=MarshmallowDefaultErrorBuilder()))
    return {"hapic": h, "app": flask_app}
示例#17
0
def get_bottle_context():
    from example.fake_api.bottle_api import hapic as h

    bottle_app = Bottle()
    h.reset_context()
    h.set_context(
        BottleContext(bottle_app,
                      default_error_builder=MarshmallowDefaultErrorBuilder()))
    controllers = BottleController()
    controllers.bind(bottle_app)
    return {"hapic": h, "app": bottle_app}
示例#18
0
def get_pyramid_context():
    from example.fake_api.pyramid_api import hapic as h

    configurator = Configurator(autocommit=True)
    controllers = PyramidController()
    controllers.bind(configurator)
    h.reset_context()
    h.set_context(
        PyramidContext(configurator,
                       default_error_builder=MarshmallowDefaultErrorBuilder()))
    pyramid_app = configurator.make_wsgi_app()
    return {"hapic": h, "app": pyramid_app}
示例#19
0
    def test_unit__exception_handled__ok__nominal_case(self):
        context = AgnosticContext(app=None)
        error_builder = MarshmallowDefaultErrorBuilder()
        wrapper = ExceptionHandlerControllerWrapper(
            ZeroDivisionError,
            context,
            error_builder=error_builder,
            http_code=HTTPStatus.INTERNAL_SERVER_ERROR,
            processor_factory=lambda schema_: MarshmallowProcessor(error_builder.get_schema()),
        )

        @wrapper.get_wrapper
        def func(foo):
            raise ZeroDivisionError("We are testing")

        response = func(42)
        assert HTTPStatus.INTERNAL_SERVER_ERROR == response.status_code
        assert {
            "details": {"error_detail": {}},
            "message": "We are testing",
            "code": None,
        } == json.loads(response.body)
示例#20
0
    def test_unit__general_exception_handling__ok__nominal_case(self):
        hapic_ = hapic.Hapic(processor_class=MarshmallowProcessor)
        app = bottle.Bottle()
        context = BottleContext(app=app, default_error_builder=MarshmallowDefaultErrorBuilder())
        hapic_.set_context(context)

        def my_view():
            raise ZeroDivisionError("An exception message")

        app.route("/my-view", method="GET", callback=my_view)
        context.handle_exception(ZeroDivisionError, http_code=400)

        test_app = TestApp(app)
        response = test_app.get("/my-view", status="*")

        assert 400 == response.status_code
示例#21
0
def get_flask_context():
    h = Hapic(processor_class=MarshmallowProcessor)

    flask_app = Flask(__name__)
    h.reset_context()
    h.set_context(FlaskContext(flask_app, default_error_builder=MarshmallowDefaultErrorBuilder()))

    class MySchema(marshmallow.Schema):
        name = marshmallow.fields.String(required=True)

    @h.with_api_doc()
    @h.input_body(MySchema())
    def my_controller():
        return {"name": "test"}

    flask_app.add_url_rule("/test", view_func=my_controller, methods=["POST"])
    return {"hapic": h, "app": flask_app}
示例#22
0
def get_bottle_context():
    h = Hapic(processor_class=MarshmallowProcessor)

    bottle_app = Bottle()
    h.reset_context()
    h.set_context(BottleContext(bottle_app, default_error_builder=MarshmallowDefaultErrorBuilder()))

    class MySchema(marshmallow.Schema):
        name = marshmallow.fields.String(required=True)

    @h.with_api_doc()
    @h.input_body(MySchema())
    def my_controller():
        return {"name": "test"}

    bottle_app.route("/test", method="POST", callback=my_controller)
    return {"hapic": h, "app": bottle_app}
示例#23
0
    def test_unit__generate_doc_with_wildcard__ok__fixed_methods(self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        @hapic.with_api_doc()
        async def a_proxy(request):
            pass

        app = web.Application(debug=True)
        app.router.add_route(hdrs.METH_ANY, path="/", handler=a_proxy)
        hapic.set_context(
            AiohttpContext(app, default_error_builder=MarshmallowDefaultErrorBuilder())
        )

        doc = hapic.generate_doc("aiohttp", "testing", wildcard_method_replacement=["head"])
        # INFO BS 2019-04-15: Prevent keep of OrderedDict
        doc = json.loads(json.dumps(doc))

        assert 1 == len(doc["paths"]["/"])
        assert "head" in doc["paths"]["/"]
示例#24
0
    async def test_aiohttp_handle_excpetion__ok__nominal_case(self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        @hapic.handle_exception(ZeroDivisionError, http_code=400)
        async def hello(request):
            1 / 0

        app = web.Application(debug=True)
        app.router.add_get("/", hello)
        hapic.set_context(
            AiohttpContext(app, default_error_builder=MarshmallowDefaultErrorBuilder())
        )
        client = await aiohttp_client(app)

        resp = await client.get("/")
        assert resp.status == 400

        data = await resp.json()
        assert "division by zero" == data.get("message")
示例#25
0
def get_aiohttp_context():
    h = Hapic(async_=True, processor_class=MarshmallowProcessor)
    aiohttp_app = web.Application(debug=True)
    h.reset_context()
    h.set_context(
        AiohttpContext(aiohttp_app, default_error_builder=MarshmallowDefaultErrorBuilder())
    )

    class MySchema(marshmallow.Schema):
        name = marshmallow.fields.String(required=True)

    @h.with_api_doc()
    @h.input_body(MySchema())
    def my_controller():
        return {"name": "test"}

    aiohttp_app.router.add_post("/", my_controller)

    return {"hapic": h, "app": aiohttp_app}
示例#26
0
    def test_unit__map_binding__ok__decorated_function(self):
        hapic_ = hapic.Hapic(processor_class=MarshmallowProcessor)
        app = bottle.Bottle()
        context = hapic.ext.bottle.BottleContext(
            app=app, default_error_builder=MarshmallowDefaultErrorBuilder()
        )
        hapic_.set_context(context)

        @hapic_.with_api_doc()
        @app.route("/")
        def controller_a():
            pass

        assert hapic_.controllers
        decoration = hapic_.controllers[0]
        route = context.find_route(decoration)

        assert route
        assert route.original_route_object.callback != controller_a
        assert route.original_route_object.callback == decoration.reference.wrapped
        assert route.original_route_object.callback != decoration.reference.wrapper
示例#27
0
    async def test_aiohttp_output_body__ok__nominal_case(self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class OuputBodySchema(marshmallow.Schema):
            name = marshmallow.fields.String()

        @hapic.output_body(OuputBodySchema())
        async def hello(request):
            return {"name": "bob"}

        app = web.Application(debug=True)
        app.router.add_get("/", hello)
        hapic.set_context(
            AiohttpContext(app, default_error_builder=MarshmallowDefaultErrorBuilder())
        )
        client = await aiohttp_client(app)

        resp = await client.get("/")
        assert resp.status == 200

        data = await resp.json()
        assert "bob" == data.get("name")
示例#28
0
def get_pyramid_context():
    h = Hapic(processor_class=MarshmallowProcessor)

    configurator = Configurator(autocommit=True)

    h.reset_context()
    h.set_context(
        PyramidContext(configurator, default_error_builder=MarshmallowDefaultErrorBuilder())
    )

    class MySchema(marshmallow.Schema):
        name = marshmallow.fields.String(required=True)

    @h.with_api_doc()
    @h.input_body(MySchema())
    def my_controller():
        return {"name": "test"}

    configurator.add_route("test", "/test", request_method="POST")
    configurator.add_view(my_controller, route_name="test")
    pyramid_app = configurator.make_wsgi_app()
    return {"hapic": h, "app": pyramid_app}
示例#29
0
    async def test_aiohttp_input_body__ok_nominal_case(self, aiohttp_client, loop):
        hapic = Hapic(async_=True, processor_class=MarshmallowProcessor)

        class InputBodySchema(marshmallow.Schema):
            name = marshmallow.fields.String()

        @hapic.input_body(InputBodySchema())
        async def hello(request, hapic_data: HapicData):
            name = hapic_data.body.get("name")
            return web.Response(text="Hello, {}".format(name))

        app = web.Application(debug=True)
        app.router.add_post("/", hello)
        hapic.set_context(
            AiohttpContext(app, default_error_builder=MarshmallowDefaultErrorBuilder())
        )
        client = await aiohttp_client(app)

        resp = await client.post("/", data={"name": "bob"})
        assert resp.status == 200

        text = await resp.text()
        assert "Hello, bob" in text
    def test_unit__handle_exception_with_default_error_builder__ok__marshmallow(
            self):
        app = AgnosticApp()
        hapic = Hapic()
        hapic.set_processor_class(MarshmallowProcessor)
        hapic.set_context(
            AgnosticContext(
                app, default_error_builder=MarshmallowDefaultErrorBuilder()))

        @hapic.with_api_doc()
        @hapic.handle_exception(ZeroDivisionError, http_code=400)
        def my_view():
            1 / 0

        response = my_view()
        json_ = json.loads(response.body)
        assert {
            "code": None,
            "details": {
                "error_detail": {}
            },
            "message": "division by zero",
        } == json_