예제 #1
0
    def test_blueprint_pagination_response_tuple(self, app):
        api = Api(app)
        blp = Blueprint('test', __name__, url_prefix='/test')
        client = app.test_client()

        @blp.route('/response')
        @blp.response()
        @blp.paginate(Page)
        def func_response():
            return [1, 2]

        @blp.route('/response_code')
        @blp.response()
        @blp.paginate(Page)
        def func_response_code():
            return [1, 2], 201

        @blp.route('/response_headers')
        @blp.response()
        @blp.paginate(Page)
        def func_response_headers():
            return [1, 2], {'X-header': 'test'}

        @blp.route('/response_code_headers')
        @blp.response()
        @blp.paginate(Page)
        def func_response_code_headers():
            return [1, 2], 201, {'X-header': 'test'}

        @blp.route('/response_wrong_tuple')
        @blp.response()
        @blp.paginate(Page)
        def func_response_wrong_tuple():
            return [1, 2], 201, {'X-header': 'test'}, 'extra'

        @blp.route('/response_tuple_subclass')
        @blp.response()
        @blp.paginate(Page)
        def func_response_tuple_subclass():
            class MyTuple(tuple):
                pass

            return MyTuple((1, 2))

        api.register_blueprint(blp)

        response = client.get('/test/response')
        assert response.status_code == 200
        assert response.json == [1, 2]
        response = client.get('/test/response_code')
        assert response.status_code == 201
        assert response.json == [1, 2]
        response = client.get('/test/response_headers')
        assert response.status_code == 200
        assert response.json == [1, 2]
        assert response.headers['X-header'] == 'test'
        response = client.get('/test/response_code_headers')
        assert response.status_code == 201
        assert response.json == [1, 2]
        assert response.headers['X-header'] == 'test'
        response = client.get('/test/response_wrong_tuple')
        assert response.status_code == 500
        response = client.get('/test/response_tuple_subclass')
        assert response.status_code == 200
        assert response.json == [1, 2]
예제 #2
0
    def test_examples(self, app, blueprint_fixture, schemas):

        blueprint, bp_schema = blueprint_fixture

        api = Api(app)
        api.register_blueprint(blueprint)

        client = app.test_client()

        @contextmanager
        def assert_counters(
            schema_load, schema_dump, etag_schema_load, etag_schema_dump
        ):
            """
            Check number of calls to dump/load methods of schemas.
            """
            schemas.DocSchema.reset_load_count()
            schemas.DocSchema.reset_dump_count()
            schemas.DocEtagSchema.reset_load_count()
            schemas.DocEtagSchema.reset_dump_count()
            yield
            assert schemas.DocSchema.load_count == schema_load
            assert schemas.DocSchema.dump_count == schema_dump
            assert schemas.DocEtagSchema.load_count == etag_schema_load
            assert schemas.DocEtagSchema.dump_count == etag_schema_dump

        # GET collection without ETag: OK
        with assert_counters(0, 1, 0, 1 if bp_schema == 'ETag schema' else 0):
            response = client.get('/test/')
            assert response.status_code == 200
            list_etag = response.headers['ETag']
            assert len(response.json) == 0
            assert json.loads(response.headers['X-Pagination']) == {
                'total': 0,
                'total_pages': 0,
            }

        # GET collection with correct ETag: Not modified
        with assert_counters(0, 1, 0, 1 if bp_schema == 'ETag schema' else 0):
            response = client.get('/test/', headers={'If-None-Match': list_etag})
        assert response.status_code == 304

        # POST item_1
        item_1_data = {'field': 0}
        with assert_counters(1, 1, 0, 1 if bp_schema == 'ETag schema' else 0):
            response = client.post(
                '/test/', data=json.dumps(item_1_data), content_type='application/json'
            )
        assert response.status_code == 200
        item_1_id = response.json['item_id']

        # GET collection with wrong/outdated ETag: OK
        with assert_counters(0, 1, 0, 1 if bp_schema == 'ETag schema' else 0):
            response = client.get('/test/', headers={'If-None-Match': list_etag})
        assert response.status_code == 200
        list_etag = response.headers['ETag']
        assert len(response.json) == 1
        assert response.json[0] == {'field': 0, 'item_id': 1}
        assert json.loads(response.headers['X-Pagination']) == {
            'total': 1,
            'total_pages': 1,
            'page': 1,
            'first_page': 1,
            'last_page': 1,
        }

        # GET by ID without ETag: OK
        with assert_counters(0, 1, 0, 1 if bp_schema == 'ETag schema' else 0):
            response = client.get('/test/{}'.format(item_1_id))
        assert response.status_code == 200
        item_etag = response.headers['ETag']

        # GET by ID with correct ETag: Not modified
        with assert_counters(
            0,
            0 if bp_schema == 'No schema' else 1,
            0,
            1 if bp_schema == 'ETag schema' else 0,
        ):
            response = client.get(
                '/test/{}'.format(item_1_id), headers={'If-None-Match': item_etag}
            )
        assert response.status_code == 304

        # PUT without ETag: Precondition required error
        item_1_data['field'] = 1
        with assert_counters(0, 0, 0, 0):
            response = client.put(
                '/test/{}'.format(item_1_id),
                data=json.dumps(item_1_data),
                content_type='application/json',
            )
        assert response.status_code == 428

        # PUT with correct ETag: OK
        with assert_counters(
            1,
            2 if bp_schema == 'Schema' else 1,
            0,
            2 if bp_schema == 'ETag schema' else 0,
        ):
            response = client.put(
                '/test/{}'.format(item_1_id),
                data=json.dumps(item_1_data),
                content_type='application/json',
                headers={'If-Match': item_etag},
            )
        assert response.status_code == 200
        new_item_etag = response.headers['ETag']

        # PUT with wrong/outdated ETag: Precondition failed error
        item_1_data['field'] = 2
        with assert_counters(
            1,
            1 if bp_schema == 'Schema' else 0,
            0,
            1 if bp_schema == 'ETag schema' else 0,
        ):
            response = client.put(
                '/test/{}'.format(item_1_id),
                data=json.dumps(item_1_data),
                content_type='application/json',
                headers={'If-Match': item_etag},
            )
        assert response.status_code == 412

        # GET by ID with wrong/outdated ETag: OK
        with assert_counters(0, 1, 0, 1 if bp_schema == 'ETag schema' else 0):
            response = client.get(
                '/test/{}'.format(item_1_id), headers={'If-None-Match': item_etag}
            )
        assert response.status_code == 200

        # GET collection with pagination set to 1 element per page
        with assert_counters(0, 1, 0, 1 if bp_schema == 'ETag schema' else 0):
            response = client.get(
                '/test/',
                headers={'If-None-Match': list_etag},
                query_string={'page': 1, 'page_size': 1},
            )
        assert response.status_code == 200
        list_etag = response.headers['ETag']
        assert len(response.json) == 1
        assert response.json[0] == {'field': 1, 'item_id': 1}
        assert json.loads(response.headers['X-Pagination']) == {
            'total': 1,
            'total_pages': 1,
            'page': 1,
            'first_page': 1,
            'last_page': 1,
        }

        # POST item_2
        item_2_data = {'field': 1}
        with assert_counters(1, 1, 0, 1 if bp_schema == 'ETag schema' else 0):
            response = client.post(
                '/test/', data=json.dumps(item_2_data), content_type='application/json'
            )
        assert response.status_code == 200

        # GET collection with pagination set to 1 element per page
        # Content is the same (item_1) but pagination metadata has changed
        # so we don't get a 304 and the data is returned again
        with assert_counters(0, 1, 0, 1 if bp_schema == 'ETag schema' else 0):
            response = client.get(
                '/test/',
                headers={'If-None-Match': list_etag},
                query_string={'page': 1, 'page_size': 1},
            )
        assert response.status_code == 200
        list_etag = response.headers['ETag']
        assert len(response.json) == 1
        assert response.json[0] == {'field': 1, 'item_id': 1}
        assert json.loads(response.headers['X-Pagination']) == {
            'total': 2,
            'total_pages': 2,
            'page': 1,
            'first_page': 1,
            'last_page': 2,
            'next_page': 2,
        }

        # DELETE without ETag: Precondition required error
        with assert_counters(0, 0, 0, 0):
            response = client.delete('/test/{}'.format(item_1_id))
        assert response.status_code == 428

        # DELETE with wrong/outdated ETag: Precondition failed error
        with assert_counters(
            0,
            1 if bp_schema == 'Schema' else 0,
            0,
            1 if bp_schema == 'ETag schema' else 0,
        ):
            response = client.delete(
                '/test/{}'.format(item_1_id), headers={'If-Match': item_etag}
            )
        assert response.status_code == 412

        # DELETE with correct ETag: No Content
        with assert_counters(
            0,
            1 if bp_schema == 'Schema' else 0,
            0,
            1 if bp_schema == 'ETag schema' else 0,
        ):
            response = client.delete(
                '/test/{}'.format(item_1_id), headers={'If-Match': new_item_etag}
            )
        assert response.status_code == 204
예제 #3
0
파일: app.py 프로젝트: Dominikuu/rate-limit
def register_blueprints(app, *blps):
    api = Api(app=app)
    for blp in blps:
        api.register_blueprint(blp)

    return api
예제 #4
0
    def test_blueprint_response_tuple(self, app):
        api = Api(app)
        blp = Blueprint('test', __name__, url_prefix='/test')
        client = app.test_client()

        @blp.route('/response')
        @blp.response()
        def func_response():
            return {}

        @blp.route('/response_code_int')
        @blp.response()
        def func_response_code_int():
            return {}, 201

        @blp.route('/response_code_str')
        @blp.response()
        def func_response_code_str():
            return {}, '201 CREATED'

        @blp.route('/response_headers')
        @blp.response()
        def func_response_headers():
            return {}, {'X-header': 'test'}

        @blp.route('/response_code_int_headers')
        @blp.response()
        def func_response_code_int_headers():
            return {}, 201, {'X-header': 'test'}

        @blp.route('/response_code_str_headers')
        @blp.response()
        def func_response_code_str_headers():
            return {}, '201 CREATED', {'X-header': 'test'}

        @blp.route('/response_wrong_tuple')
        @blp.response()
        def func_response_wrong_tuple():
            return {}, 201, {'X-header': 'test'}, 'extra'

        @blp.route('/response_tuple_subclass')
        @blp.response()
        def func_response_tuple_subclass():
            class MyTuple(tuple):
                pass

            return MyTuple((1, 2))

        api.register_blueprint(blp)

        response = client.get('/test/response')
        assert response.status_code == 200
        assert response.json == {}
        response = client.get('/test/response_code_int')
        assert response.status_code == 201
        assert response.status == '201 CREATED'
        assert response.json == {}
        response = client.get('/test/response_code_str')
        assert response.status_code == 201
        assert response.status == '201 CREATED'
        assert response.json == {}
        response = client.get('/test/response_headers')
        assert response.status_code == 200
        assert response.json == {}
        assert response.headers['X-header'] == 'test'
        response = client.get('/test/response_code_int_headers')
        assert response.status_code == 201
        assert response.status == '201 CREATED'
        assert response.json == {}
        assert response.headers['X-header'] == 'test'
        response = client.get('/test/response_code_str_headers')
        assert response.status_code == 201
        assert response.status == '201 CREATED'
        assert response.json == {}
        assert response.headers['X-header'] == 'test'
        response = client.get('/test/response_wrong_tuple')
        assert response.status_code == 500
        response = client.get('/test/response_tuple_subclass')
        assert response.status_code == 200
        assert response.json == [1, 2]
예제 #5
0
from flask_rest_api import Api

api = Api()
예제 #6
0
def app_with_etag(request, collection, schemas, app):
    """Return a basic API sample with ETag"""

    as_method_view = request.param
    DocSchema = schemas.DocSchema
    DocEtagSchema = schemas.DocEtagSchema
    blp = Blueprint('test', __name__, url_prefix='/test')

    if as_method_view:
        @blp.route('/')
        class Resource(MethodView):

            @blp.etag(DocEtagSchema(many=True))
            @blp.response(
                DocSchema(many=True))
            def get(self):
                return collection.items

            @blp.etag(DocEtagSchema)
            @blp.arguments(DocSchema)
            @blp.response(DocSchema, code=201)
            def post(self, new_item):
                return collection.post(new_item)

        @blp.route('/<int:item_id>')
        class ResourceById(MethodView):

            def _get_item(self, item_id):
                try:
                    return collection.get_by_id(item_id)
                except ItemNotFound:
                    abort(404)

            @blp.etag(DocEtagSchema)
            @blp.response(DocSchema)
            def get(self, item_id):
                return self._get_item(item_id)

            @blp.etag(DocEtagSchema)
            @blp.arguments(DocSchema)
            @blp.response(DocSchema)
            def put(self, new_item, item_id):
                item = self._get_item(item_id)
                blp.check_etag(item, DocEtagSchema)
                return collection.put(item_id, new_item)

            @blp.etag(DocEtagSchema)
            @blp.response(code=204)
            def delete(self, item_id):
                item = self._get_item(item_id)
                blp.check_etag(item, DocEtagSchema)
                del collection.items[collection.items.index(item)]

    else:
        @blp.route('/')
        @blp.etag(DocEtagSchema(many=True))
        @blp.response(DocSchema(many=True))
        def get_resources():
            return collection.items

        @blp.route('/', methods=('POST',))
        @blp.etag(DocEtagSchema)
        @blp.arguments(DocSchema)
        @blp.response(DocSchema, code=201)
        def post_resource(new_item):
            return collection.post(new_item)

        def _get_item(item_id):
            try:
                return collection.get_by_id(item_id)
            except ItemNotFound:
                abort(404)

        @blp.route('/<int:item_id>')
        @blp.etag(DocEtagSchema)
        @blp.response(DocSchema)
        def get_resource(item_id):
            return _get_item(item_id)

        @blp.route('/<int:item_id>', methods=('PUT',))
        @blp.etag(DocEtagSchema)
        @blp.arguments(DocSchema)
        @blp.response(DocSchema)
        def put_resource(new_item, item_id):
            item = _get_item(item_id)
            blp.check_etag(item)
            return collection.put(item_id, new_item)

        @blp.route('/<int:item_id>', methods=('DELETE',))
        @blp.etag(DocEtagSchema)
        @blp.response(code=204)
        def delete_resource(item_id):
            item = _get_item(item_id)
            blp.check_etag(item)
            del collection.items[collection.items.index(item)]

    api = Api(app)
    api.register_blueprint(blp)

    return app
예제 #7
0
    OPENAPI_SWAGGER_UI_PATH = "/swagger"
    OPENAPI_SWAGGER_URL = "/swagger"
    API_SPEC_OPTIONS = {"x-internal-id": "2"}
    CELERY_BROKER_URL = "redis://localhost:6379/0"
    CELERY_RESULT_BACKEND = "redis://localhost:6379/0"


app = Flask(__name__)

# Celery configuration
app.config["CELERY_BROKER_URL"] = "redis://localhost:6379/0"
app.config["CELERY_RESULT_BACKEND"] = "redis://localhost:6379/0"

app.config["UPLOAD_FOLDER"] = "downloads"
app.config["API_TITLE"] = "My API"
app.config["API_VERSION"] = "v1"
app.config["OPENAPI_VERSION"] = "3.0.2"
app.config["OPENAPI_URL_PREFIX"] = "/doc"
app.config["OPENAPI_REDOC_PATH"] = "/redoc"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger"
app.config["OPENAPI_SWAGGER_URL"] = "/swagger"

# Initialize extensions

# Initialize Celery
celery = Celery(app.name, broker=app.config["CELERY_BROKER_URL"])
# celery.autodiscover_tasks()
celery.conf.update(app.config)

api = Api(app)
예제 #8
0
"""REST api extension initialization"""

from flask_rest_api import Api
from flask_rest_api import abort, Blueprint, Page, check_etag, set_etag  # noqa

from .. import marshmallow as ext_ma
from .converters import UUIDConverter
from .hateoas import ma_hateoas
from .custom_fields import FileField
from .pagination import SQLCursorPage  # noqa
from .schemas import ErrorSchema, Float
from .hateoas_apispec_plugin import HateoasPlugin

rest_api = Api()


def init_app(app):
    """Initialize REST api"""

    hateoas_plugin = HateoasPlugin()

    rest_api.init_app(app, spec_kwargs={'plugins': (hateoas_plugin, )})
    ma_hateoas.init_app(app)

    # Register UUIDConverter in Flask and in doc
    rest_api.register_converter(UUIDConverter, 'string', 'UUID', name='uuid')

    # Register hateoas custom Marshmallow fields in doc
    rest_api.register_field(ext_ma.fields.StrictDateTime, 'string',
                            'date-time')
    rest_api.register_field(ext_ma.fields.StringList, 'array', None)
예제 #9
0
def create_app_mock(config_cls=None):
    """Return a basic API sample"""
    class AlbumSchema(ma.Schema):
        """Album resource schema"""
        class Meta:
            """Album schema Meta properties"""
            strict = True

        id = ma.fields.Integer()
        name = ma.fields.String()

        # Smart hyperlinking, hateoas style !
        _links = ma_hateoas.Hyperlinks(
            schema={
                'self':
                ma_hateoas.UrlFor(endpoint='albums.AlbumResourceById',
                                  album_id='<id>'),
                'collection':
                ma_hateoas.UrlFor(endpoint='albums.AlbumResources')
            })
        _embedded = ma_hateoas.Hyperlinks(
            schema={
                'songs': {
                    '_links': {
                        'collection':
                        ma_hateoas.UrlFor(endpoint='songs.SongResources',
                                          album_id='<id>')
                    }
                }
            })

    blp_albums = Blueprint('albums', __name__, url_prefix='/albums')

    @blp_albums.route('/')
    class AlbumResources(MethodView):
        """Album resources endpoints"""
        @blp_albums.arguments(AlbumSchema, location='query')
        @blp_albums.response(AlbumSchema(many=True))
        @blp_albums.paginate(Page)
        def get(self, args):
            """Return a list of resources"""
            album_datas = [{
                'id': 0,
                'name': 'Freak Out!'
            }, {
                'id': 1,
                'name': 'Absolutely Free'
            }]
            return album_datas

        @blp_albums.arguments(AlbumSchema)
        @blp_albums.response(AlbumSchema, code=201)
        def post(self, new_item):
            """Create and return a resource"""
            return new_item

    @blp_albums.route('/<int:album_id>')
    class AlbumResourceById(MethodView):
        """Album resource endpoints"""
        @blp_albums.response(AlbumSchema)
        def get(self, album_id):
            """Return a resource from its ID"""
            album_data = {'id': album_id, 'name': 'Freak Out!'}
            return album_data

    class SongSchema(ma.Schema):
        """Song resource schema"""
        class Meta:
            """Song schema Meta properties"""
            strict = True

        id = ma.fields.Integer()
        name = ma.fields.String()
        album_id = ma.fields.Integer()

        # Smart hyperlinking, hateoas style !
        _links = ma_hateoas.Hyperlinks({
            'self':
            ma_hateoas.UrlFor(endpoint='songs.SongResourceById',
                              song_id='<id>'),
            'collection':
            ma_hateoas.UrlFor(endpoint='songs.SongResources'),
            'parent':
            ma_hateoas.UrlFor(endpoint='albums.AlbumResourceById',
                              album_id='<album_id>')
        })

    blp_songs = Blueprint('songs', __name__, url_prefix='/songs')

    @blp_songs.route('/')
    class SongResources(MethodView):
        """Song resources endpoints"""
        @blp_songs.arguments(SongSchema, location='query')
        @blp_songs.response(SongSchema(many=True))
        @blp_songs.paginate(Page)
        def get(self, args):
            """Return a list of resources"""
            song_datas = [{
                'id': 0,
                'name': 'Hungry Freaks Daddy',
                'album_id': 0
            }, {
                'id': 1,
                'name': 'I Ain\'t Got No Heart',
                'album_id': 0
            }]
            return song_datas

        @blp_songs.arguments(SongSchema)
        @blp_songs.response(SongSchema, code=201)
        def post(self, new_item):
            """Create and return a resource"""
            return new_item

    @blp_songs.route('/<int:song_id>')
    class SongResourceById(MethodView):
        """Song resource endpoints"""
        @blp_songs.response(SongSchema)
        def get(self, song_id):
            """Return a resource from its ID"""
            song_data = {
                'id': song_id,
                'name': 'Hungry Freaks Daddy',
                'album_id': 0
            }
            return song_data

    app = Flask('API Test')
    app.response_class = JSONResponse
    if config_cls:
        app.config.from_object(config_cls)
    api = Api(app)
    api.register_blueprint(blp_albums)
    api.register_blueprint(blp_songs)

    return app