def test_api_payload_strict_verification(self, app, client): api = restx.Api(app, validate=True) ns = restx.Namespace("apples") api.add_namespace(ns) fields = ns.model( "Person", { "name": restx.fields.String(required=True), "age": restx.fields.Integer, "birthdate": restx.fields.DateTime, }, strict=True, ) @ns.route("/validation/") class Payload(restx.Resource): payload = None @ns.expect(fields) def post(self): Payload.payload = ns.payload return {} data = { "name": "John Doe", "agge": 15, # typo } resp = client.post_json("/apples/validation/", data, status=400) assert re.match( "Additional properties are not allowed \(u*'agge' was unexpected\)", resp["errors"][""])
def test_errorhandler_with_namespace(self, app, client): api = restx.Api(app) ns = restx.Namespace("ExceptionHandler", path="/") class CustomException(RuntimeError): pass @ns.route('/test/', endpoint='test') class TestResource(restx.Resource): def get(self): raise CustomException('error') @ns.errorhandler(CustomException) def handle_custom_exception(error): return {'message': str(error), 'test': 'value'}, 400 api.add_namespace(ns) response = client.get('/test/') assert response.status_code == 400 assert response.content_type == 'application/json' data = json.loads(response.data.decode('utf8')) assert data == { 'message': 'error', 'test': 'value', }
def test_namespace_errorhandler_with_propagate_true(self, app, client): """Exceptions with errorhandler on a namespace should not be returned to client, even if PROPAGATE_EXCEPTIONS is set.""" app.config["PROPAGATE_EXCEPTIONS"] = True api = restx.Api(app) namespace = restx.Namespace('test_namespace') api.add_namespace(namespace) @namespace.route("/test/", endpoint="test") class TestResource(restx.Resource): def get(self): raise RuntimeError("error") @namespace.errorhandler(RuntimeError) def handle_custom_exception(error): return {"message": str(error), "test": "value"}, 400 response = client.get("/test_namespace/test/") assert response.status_code == 400 assert response.content_type == "application/json" data = json.loads(response.data.decode("utf8")) assert data == { "message": "error", "test": "value", }
def test_api_payload(self, app, client): api = restx.Api(app, validate=True) ns = restx.Namespace("apples") api.add_namespace(ns) fields = ns.model( "Person", { "name": restx.fields.String(required=True), "age": restx.fields.Integer, "birthdate": restx.fields.DateTime, }, ) @ns.route("/validation/") class Payload(restx.Resource): payload = None @ns.expect(fields) def post(self): Payload.payload = ns.payload return {} data = { "name": "John Doe", "age": 15, } client.post_json("/apples/validation/", data) assert Payload.payload == data
def test_errorhandler_with_namespace(self, app, client): api = restx.Api(app) ns = restx.Namespace("ExceptionHandler", path="/") class CustomException(RuntimeError): pass @ns.route("/test/", endpoint="test") class TestResource(restx.Resource): def get(self): raise CustomException("error") @ns.errorhandler(CustomException) def handle_custom_exception(error): return {"message": str(error), "test": "value"}, 400 api.add_namespace(ns) response = client.get("/test/") assert response.status_code == 400 assert response.content_type == "application/json" data = json.loads(response.data.decode("utf8")) assert data == { "message": "error", "test": "value", }
def test_api_payload(self, app, client): api = restx.Api(app, validate=True) ns = restx.Namespace('apples') api.add_namespace(ns) fields = ns.model('Person', { 'name': restx.fields.String(required=True), 'age': restx.fields.Integer, 'birthdate': restx.fields.DateTime, }) @ns.route('/validation/') class Payload(restx.Resource): payload = None @ns.expect(fields) def post(self): Payload.payload = ns.payload return {} data = { 'name': 'John Doe', 'age': 15, } client.post_json('/apples/validation/', data) assert Payload.payload == data
def test_multiple_ns_with_authorizations(self, app): api = restx.Api() a1 = {'apikey': {'type': 'apiKey', 'in': 'header', 'name': 'X-API'}} a2 = { 'oauth2': { 'type': 'oauth2', 'flow': 'accessCode', 'tokenUrl': 'https://somewhere.com/token', 'scopes': { 'read': 'Grant read-only access', 'write': 'Grant read-write access', } } } ns1 = restx.Namespace('ns1', authorizations=a1) ns2 = restx.Namespace('ns2', authorizations=a2) @ns1.route('/') class Ns1(restx.Resource): @ns1.doc(security='apikey') def get(self): pass @ns2.route('/') class Ns2(restx.Resource): @ns1.doc(security='oauth2') def post(self): pass api.add_namespace(ns1, path='/ns1') api.add_namespace(ns2, path='/ns2') api.init_app(app) assert { "apikey": [] } in api.__schema__["paths"]["/ns1/"]["get"]["security"] assert { "oauth2": [] } in api.__schema__["paths"]["/ns2/"]["post"]["security"] unified_auth = copy.copy(a1) unified_auth.update(a2) assert api.__schema__["securityDefinitions"] == unified_auth
def test_multiple_ns_with_authorizations(self, app): api = restx.Api() a1 = {"apikey": {"type": "apiKey", "in": "header", "name": "X-API"}} a2 = { "oauth2": { "type": "oauth2", "flow": "accessCode", "tokenUrl": "https://somewhere.com/token", "scopes": { "read": "Grant read-only access", "write": "Grant read-write access", }, } } ns1 = restx.Namespace("ns1", authorizations=a1) ns2 = restx.Namespace("ns2", authorizations=a2) @ns1.route("/") class Ns1(restx.Resource): @ns1.doc(security="apikey") def get(self): pass @ns2.route("/") class Ns2(restx.Resource): @ns1.doc(security="oauth2") def post(self): pass api.add_namespace(ns1, path="/ns1") api.add_namespace(ns2, path="/ns2") api.init_app(app) assert { "apikey": [] } in api.__schema__["paths"]["/ns1/"]["get"]["security"] assert { "oauth2": [] } in api.__schema__["paths"]["/ns2/"]["post"]["security"] unified_auth = copy.copy(a1) unified_auth.update(a2) assert api.__schema__["securityDefinitions"] == unified_auth
def test_ns_path_prefixes(self, app): api = restx.Api() ns = restx.Namespace('test_ns', description='Test namespace') @ns.route('/test/', endpoint='test_resource') class TestResource(restx.Resource): pass api.add_namespace(ns, '/api_test') api.init_app(app) with app.test_request_context(): assert url_for('test_resource') == '/api_test/test/'
def test_ns_path_prefixes(self, app): api = restx.Api() ns = restx.Namespace("test_ns", description="Test namespace") @ns.route("/test/", endpoint="test_resource") class TestResource(restx.Resource): pass api.add_namespace(ns, "/api_test") api.init_app(app) with app.test_request_context(): assert url_for("test_resource") == "/api_test/test/"
import flask_restx from serializers.user_serializer import UserLoginSerializer from core.user_service import UserService from core.response_factory import * login_schema = UserLoginSerializer() auth_namespace = flask_restx.Namespace("Auth") auth_namespace.add_model(login_schema.api_model.name, login_schema.api_model) @auth_namespace.route('/login') class UserLoginApi(flask_restx.Resource): @auth_namespace.expect(login_schema.api_model) @auth_namespace.response(HTTPStatus.CREATED.real, "You are successfully log in") def post(self): """Log in using username and password""" auth_data = login_schema.loads_required(flask.request.data) auth = UserService.get_user_auth(**auth_data) if auth: access_token = UserService.get_token(auth_data['username']) return create_success_response(data=access_token) else: return create_failed_response("Invalid username or password")
from http import HTTPStatus import time import flask import flask_restx from core.response_factory import create_success_response, create_success_plain_response from serializers.measurement_serializer import MeasurementSerializer from service.measurement_service import MeasurementService FOUR_HOURS_IN_SECONDS = 60 * 60 * 4 measurement_schema = MeasurementSerializer() measurement_namespace = flask_restx.Namespace("Measurement") measurement_namespace.add_model(measurement_schema.api_model.name, measurement_schema.api_model) measurement_timestamp_parser = flask_restx.reqparse.RequestParser() measurement_timestamp_parser.add_argument( 'minTimestamp', help="Minimum Read Datetime Default is last 4 hours. ", type=float) measurement_timestamp_parser.add_argument( 'maxTimestamp', help="Maximum Read Datetime. Default is now. ", type=float) @measurement_namespace.route('/') class MeasurementAllApi(flask_restx.Resource): @measurement_namespace.expect(measurement_timestamp_parser) @measurement_namespace.response(HTTPStatus.OK.real, "List of measurement", [measurement_schema.api_model])
from http import HTTPStatus import flask import flask_restx from core.utils import scan_with_pagination from core.request_arguments_parser import core_request_arguments_parser from core.response_factory import create_success_response, create_success_plain_response from serializers.measurement_serializer import MeasurementTypeSerializer from service.measurement_service import MeasurementTypeService measurement_type_schema = MeasurementTypeSerializer() measurement_type_namespace = flask_restx.Namespace("MeasurementType") measurement_type_namespace.add_model(measurement_type_schema.api_model.name, measurement_type_schema.api_model) @measurement_type_namespace.route('/') class MeasurementTypeAllApi(flask_restx.Resource): @measurement_type_namespace.expect(core_request_arguments_parser) @measurement_type_namespace.response(HTTPStatus.OK.real, "List of measurement types", [measurement_type_schema.api_model]) def get(self): """ Returns list of measurement types """ measurement_types = scan_with_pagination(MeasurementTypeService) return create_success_response(data=measurement_type_schema.serialize( measurement_types, many=True)) @measurement_type_namespace.expect(measurement_type_schema.api_model) @measurement_type_namespace.response(
import json import flask_restx import boto3 import botocore.exceptions from core.things_helper import create_thing, get_thing_certificates from core.utils import scan_with_pagination from core.response_factory import * from core.request_arguments_parser import core_request_arguments_parser from serializers.device_serializer import DeviceSerializer from service.device_service import DeviceService from flask_jwt_extended import jwt_required device_schema = DeviceSerializer() device_namespace = flask_restx.Namespace("Device") device_namespace.add_model(device_schema.api_model.name, device_schema.api_model) device_shadow_parser = flask_restx.reqparse.RequestParser() device_shadow_parser.add_argument('region', help="The AWS zone, default is eu-west-2", type=str) @device_namespace.route('/') class DeviceAllApi(flask_restx.Resource): @device_namespace.expect(core_request_arguments_parser) @device_namespace.response(HTTPStatus.OK.real, "List of devices", [device_schema.api_model]) def get(self):
from http import HTTPStatus import flask import flask_restx from core.utils import scan_with_pagination from core.request_arguments_parser import core_request_arguments_parser from core.response_factory import create_success_response, create_success_plain_response from serializers.device_serializer import DeviceTypeSerializer from service.device_service import DeviceTypeService from flask_jwt_extended import jwt_required device_type_schema = DeviceTypeSerializer() device_type_namespace = flask_restx.Namespace("DeviceType") device_type_namespace.add_model(device_type_schema.api_model.name, device_type_schema.api_model) @device_type_namespace.route('/') class DeviceTypeAllApi(flask_restx.Resource): @device_type_namespace.expect(core_request_arguments_parser) @device_type_namespace.response(HTTPStatus.OK.real, "List of device types", [device_type_schema.api_model]) def get(self): """ Returns list of device types """ device_types = scan_with_pagination(DeviceTypeService) return create_success_response( data=device_type_schema.serialize(device_types, many=True)) @device_type_namespace.expect(device_type_schema.api_model) @device_type_namespace.response(HTTPStatus.CREATED.real,
from http import HTTPStatus import flask import flask_restx from core.utils import scan_with_pagination from core.request_arguments_parser import core_request_arguments_parser from core.response_factory import create_success_response, create_success_plain_response from serializers.device_serializer import DeviceGroupSerializer from service.device_service import DeviceGroupService from flask_jwt_extended import jwt_required device_group_schema = DeviceGroupSerializer() device_group_namespace = flask_restx.Namespace("DeviceGroup") device_group_namespace.add_model(device_group_schema.api_model.name, device_group_schema.api_model) @device_group_namespace.route('/') class DeviceGroupAllApi(flask_restx.Resource): @device_group_namespace.expect(core_request_arguments_parser) @device_group_namespace.response(HTTPStatus.OK.real, "List of device groups", [device_group_schema.api_model]) def get(self): """ Returns list of device groups """ device_groups = scan_with_pagination(DeviceGroupService) return create_success_response( data=device_group_schema.serialize(device_groups, many=True)) @device_group_namespace.expect(device_group_schema.api_model)
def add_restful_api(self, name: str, backend: object, endpoint: str = None): """ Creates a flask_restplus restful api from the routines available in given class """ if endpoint is None: endpoint = name.replace(" ", "_").lower() ns = flask_restplus.Namespace(name, inspect.getdoc(backend), '/{}'.format(endpoint)) def isempty(o): "Checks if an annotation or a default value is empty" return o == inspect.Parameter.empty def build_resource(callable, post_parser, get_parser, is_post, is_get): "Instantiates an ApiMember in closure" def invoke_callable(parser, *args, **kwargs): for key, value in parser.parse_args().items(): kwargs[key] = value return callable(*args, **kwargs) if is_post: if is_get: class ApiMember(flask_restplus.Resource): @ns.expect(post_parser) def post(self, *args, **kwargs): return invoke_callable(post_parser, *args, **kwargs) @ns.expect(get_parser) def get(self, *args, **kwargs): return invoke_callable(get_parser, *args, **kwargs) else: class ApiMember(flask_restplus.Resource): @ns.expect(post_parser) def post(self, *args, **kwargs): return invoke_callable(post_parser, *args, **kwargs) else: class ApiMember(flask_restplus.Resource): @ns.expect(get_parser) def get(self, *args, **kwargs): return invoke_callable(get_parser, *args, **kwargs) return ApiMember member_resources = {} for name, value in inspect.getmembers(backend, inspect.ismethod): if name.startswith('_'): continue is_post = hasattr(value, 'is_post') and value.is_post is_get = hasattr(value, 'is_get') and value.is_get if not (is_post or is_get): is_post = True signature = inspect.signature(value) post_parser = ns.parser() get_parser = ns.parser() for p in signature.parameters.values(): param_type = str if isempty(p.annotation) else p.annotation param_action = 'store' param_location = {'get': 'args', 'post': 'form'} param_choices = () if isinstance(param_type, listof): param_type = param_type.subtype param_action = 'append' try: if issubclass(param_type, file): is_get = False is_post = True param_type = FileStorage param_location = {'get': 'files', 'post': 'files'} except: pass if isinstance(param_type, enum): param_choices = tuple(param_type.entries.keys()) param_type = str post_parser.add_argument( p.name, location=param_location['post'], type=param_type, action=param_action, choices=param_choices, required=isempty(p.default), default=None if isempty(p.default) else p.default) get_parser.add_argument( p.name, location=param_location['get'], type=param_type, action=param_action, choices=param_choices, required=isempty(p.default), default=None if isempty(p.default) else p.default) resource = build_resource(value, post_parser, get_parser, is_post, is_get) member_resources[value] = resource ns.route('/' + name)(resource) self.add_namespace(ns) def backend_url_for(backend, method): "Provide the backend with a means to get urls for its methods" if not method in backend.member_resources: return return self.url_for(backend.member_resources[method]) backend.member_resources = member_resources backend.url_for = MethodType(backend_url_for, backend)