def test_apispec_write_openapi_doc(self, app, tmp_path): output_file = tmp_path / 'openapi.json' api = Api(app) result = app.test_cli_runner().invoke(args=('openapi', 'write', str(output_file))) assert result.exit_code == 0 with open(output_file) as output: assert json.loads(output.read()) == api.spec.to_dict()
def create_app(): app = Flask(__name__) app.config.from_object("backend.core.config.ProductionConfig") mongo.init_app(app) api = Api(app) register_api(api) return app
def configure_routes(app): app.config['API_TITLE'] = 'API' app.config['API_VERSION'] = 'v1' app.config['OPENAPI_VERSION'] = '3.0.2' from app.resources.product_resource import product_blp api = Api(app) api.register_blueprint(product_blp)
def test_blueprint_arguments_files_multipart(self, app, schemas, openapi_version): app.config['OPENAPI_VERSION'] = openapi_version api = Api(app) blp = Blueprint('test', __name__, url_prefix='/test') client = app.test_client() class MultipartSchema(ma.Schema): file_1 = Upload() file_2 = Upload() @blp.route('/', methods=['POST']) @blp.arguments(MultipartSchema, location='files') def func(files): return { 'file_1': files['file_1'].read().decode(), 'file_2': files['file_2'].read().decode(), } api.register_blueprint(blp) spec = api.spec.to_dict() files = { 'file_1': (io.BytesIO('Test 1'.encode()), 'file_1.txt'), 'file_2': (io.BytesIO('Test 2'.encode()), 'file_2.txt'), } response = client.post('/test/', data=files) assert response.json == {'file_1': 'Test 1', 'file_2': 'Test 2'} if openapi_version == '2.0': for param in spec['paths']['/test/']['post']['parameters']: assert param['in'] == 'formData' assert param['type'] == 'file' else: assert ( spec['paths']['/test/']['post']['requestBody']['content'] == { 'multipart/form-data': { 'schema': { '$ref': '#/components/schemas/Multipart' } } }) assert (spec['components']['schemas']['Multipart'] == { 'type': 'object', 'properties': { 'file_1': { 'type': 'string', 'format': 'binary' }, 'file_2': { 'type': 'string', 'format': 'binary' }, } })
def create_app(): app = Flask(__name__) Bootstrap(app) @app.route('/') def index(): user_agent = request.headers.get('User-Agent') return f"<p> Your browser is {user_agent}</p>\n<h1>ECM Bonjour</h1>" @app.route('/user/<name>') def user(name): return render_template('user.html', name=name) @app.route('/professor') def profesor_api_route(): return { "name": "Adrien", "birthday": "02 January", "age": 85, "sex": None, "friends": ["Amadou", "Mariam"] } app.config[ "SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{os.path.join(basedir, 'data.sqlite')}" app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = True app.config["OPENAPI_VERSION"] = "3.0.2" app.config["OPENAPI_URL_PREFIX"] = "openapi" app.config["API_VERSION"] = "1" app.config["OPENAPI_SWAGGER_UI_PATH"] = "api" app.config["OPENAPI_SWAGGER_UI_VERSION"] = "3.23.11" db.init_app(app) api = Api(app) ma.init_app(app) from tasks.models import Task migrate = Migrate(app, db) # @app.route('/todoz') # def my_api_route(): # tasks = Task.query.all() # return {"results": [{field: getattr(task, field) for field in Task.__table__.columns.keys()}for task in tasks]} @app.route('/todoz') def my_api_route(): from tasks.serializers import TaskSchema tasks = Task.query.all() return {"results": TaskSchema(many=True).dump(tasks)} from tasks.views import task_blueprint api.register_blueprint(task_blueprint) return app
def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) db.init_app(app) migrate.init_app(app, db) CORS(app) mail.init_app(app) api = Api() api.init_app(app) from app.errors import bp as errors_bp app.register_blueprint(errors_bp) from app.api import bp as api_bp api.register_blueprint(api_bp, url_prefix='/api') from app.main import bp as main_bp app.register_blueprint(main_bp) if not app.debug and not app.testing: if app.config['MAIL_SERVER']: auth = None if app.config['MAIL_USERNAME'] or app.config['MAIL_PASSWORD']: auth = (app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD']) secure = None if app.config['MAIL_USE_TLS']: secure = () mail_handler = SMTPHandler( mailhost=(app.config['MAIL_SERVER'], app.config['MAIL_PORT']), fromaddr='no-reply@' + app.config['MAIL_SERVER'], toaddrs=app.config['ADMINS'], subject='Starterflask Failure', credentials=auth, secure=secure) mail_handler.setLevel(logging.ERROR) app.logger.addHandler(mail_handler) if not os.path.exists('logs'): os.mkdir('logs') file_handler = RotatingFileHandler('logs/Starterflask.log', maxBytes=10240, backupCount=10) file_handler.setFormatter( logging.Formatter('%(asctime)s %(levelname)s: %(message)s ' '[in %(pathname)s:%(lineno)d]')) file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) app.logger.info('Starterflask startup') return app
def configure_routes(app): app.config['API_TITLE'] = 'API' app.config['API_VERSION'] = 'v1' app.config['OPENAPI_VERSION'] = '3.0.2' from app.resources.auth_resource import auth_blp from app.resources.user_resource import user_blp api = Api(app) api.register_blueprint(auth_blp) api.register_blueprint(user_blp)
def test_blueprint_multiple_registrations(self, app, openapi_version): """Check blueprint can be registered multiple times The internal doc structure is modified during the reigistration process. If it is not deepcopied, the second registration fails. """ app.config['OPENAPI_VERSION'] = openapi_version blp = Blueprint('test', __name__, url_prefix='/test') @blp.route('/') def func(): pass api = Api(app) api.register_blueprint(blp) spec_1 = api.spec.to_dict() api = Api(app) api.register_blueprint(blp) spec_2 = api.spec.to_dict() assert spec_1 == spec_2
def test_api_extra_spec_plugins(self, app, schemas, openapi_version): """Test extra plugins can be passed to internal APISpec instance""" app.config["OPENAPI_VERSION"] = openapi_version class MyPlugin(apispec.BasePlugin): def schema_helper(self, name, definition, **kwargs): return {"dummy": "whatever"} api = Api(app, spec_kwargs={"extra_plugins": (MyPlugin(), )}) api.spec.components.schema("Pet", schema=schemas.DocSchema) assert get_schemas(api.spec)["Pet"]["dummy"] == "whatever"
def test_api_extra_spec_plugins(self, app, schemas, openapi_version): """Test extra plugins can be passed to internal APISpec instance""" app.config['OPENAPI_VERSION'] = openapi_version class MyPlugin(apispec.BasePlugin): def schema_helper(self, name, definition, **kwargs): return {'dummy': 'whatever'} api = Api(app, spec_kwargs={'extra_plugins': (MyPlugin(), )}) api.spec.components.schema('Pet', schema=schemas.DocSchema) assert get_schemas(api.spec)['Pet']['dummy'] == 'whatever'
def test_response_payload_wrapping(self, app, schemas, openapi_version): """Demonstrates how to wrap response payload in a data field""" class WrapperBlueprint(Blueprint): # Wrap payload data @staticmethod def _prepare_response_content(data): if data is not None: return {"data": data} return None # Document data wrapper # The schema is not used to dump the payload, only to generate doc @staticmethod def _make_doc_response_schema(schema): if schema: return type( "Wrap" + schema.__class__.__name__, (ma.Schema, ), {"data": ma.fields.Nested(schema)}, ) return None app.config["OPENAPI_VERSION"] = openapi_version api = Api(app) client = app.test_client() blp = WrapperBlueprint("test", __name__, url_prefix="/test") @blp.route("/") @blp.response(200, schemas.DocSchema) def func(): return {"item_id": 1, "db_field": 42} api.register_blueprint(blp) spec = api.spec.to_dict() # Test data is wrapped resp = client.get("/test/") assert resp.json == {"data": {"item_id": 1, "field": 42}} # Test wrapping is correctly documented if openapi_version == "3.0.2": content = spec["paths"]["/test/"]["get"]["responses"]["200"][ "content"]["application/json"] else: content = spec["paths"]["/test/"]["get"]["responses"]["200"] assert content["schema"] == build_ref(api.spec, "schema", "WrapDoc") assert get_schemas(api.spec)["WrapDoc"] == { "type": "object", "properties": { "data": build_ref(api.spec, "schema", "Doc") }, } assert "Doc" in get_schemas(api.spec)
def create_app(config_filename): conn = connect( 'tm', host=MONGO_HOST, port=MONGO_PORT, username=MONGO_USERNAME, password=MONGO_PASSWORD ) # dependency injection containers token_user_container = auth.TokenUserContainer() token_user_container.wire(packages=[tm]) app = Flask(__name__) app.db = conn app.config['API_TITLE'] = 'Tuition Management API' app.config['API_VERSION'] = 'v1' app.config['OPENAPI_VERSION'] = '3.0.2' app.config['OPENAPI_JSON_PATH'] = 'api-spec.json' app.config['OPENAPI_URL_PREFIX'] = '/' app.config['OPENAPI_REDOC_PATH'] = '/redoc' app.config['OPENAPI_REDOC_URL'] = 'https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js' app.config['OPENAPI_SWAGGER_UI_PATH'] = '/docs' app.config['OPENAPI_SWAGGER_UI_URL'] = 'https://cdn.jsdelivr.net/npm/swagger-ui-dist/' app.config['API_SPEC_OPTIONS'] = { 'components': { "securitySchemes": { "OAuth2PasswordBearer": { "type": "oauth2", "flows": { "password": { "scopes": {}, "tokenUrl": "/auth/" } } } } } } app.config['CELERY_BROKER_URL'] = REDIS_BROKER_URL app.config['MONGODB_CONNECT'] = False celery.conf.update(app.config) # * cli commands app.cli.add_command(create_super_admin) api = Api(app) api.register_blueprint(auth_blp) return app
def test_api_gets_apispec_parameters_from_app(self, app, openapi_version): app.config['API_VERSION'] = 'v42' app.config['OPENAPI_VERSION'] = openapi_version api = Api(app) spec = api.spec.to_dict() assert spec['info'] == {'title': 'API Test', 'version': 'v42'} if openapi_version == '2.0': assert spec['swagger'] == '2.0' else: assert spec['openapi'] == '3.0.2'
def test_response_payload_wrapping(self, app, schemas, openapi_version): """Demonstrates how to wrap response payload in a data field""" class WrapperBlueprint(Blueprint): # Wrap payload data @staticmethod def _prepare_response_content(data): if data is not None: return {'data': data} return None # Document data wrapper # The schema is not used to dump the payload, only to generate doc @staticmethod def _make_doc_response_schema(schema): if schema: return type( 'Wrap' + schema.__class__.__name__, (ma.Schema, ), {'data': ma.fields.Nested(schema)}, ) return None app.config['OPENAPI_VERSION'] = openapi_version api = Api(app) client = app.test_client() blp = WrapperBlueprint('test', __name__, url_prefix='/test') @blp.route('/') @blp.response(schemas.DocSchema) def func(): return {'item_id': 1, 'db_field': 42} api.register_blueprint(blp) spec = api.spec.to_dict() # Test data is wrapped resp = client.get('/test/') assert resp.json == {'data': {'item_id': 1, 'field': 42}} # Test wrapping is correctly documented if openapi_version == '3.0.2': content = spec['paths']['/test/']['get']['responses']['200'][ 'content']['application/json'] else: content = spec['paths']['/test/']['get']['responses']['200'] assert content['schema'] == build_ref(api.spec, 'schema', 'WrapDoc') assert get_schemas(api.spec)['WrapDoc'] == { 'type': 'object', 'properties': { 'data': build_ref(api.spec, 'schema', 'Doc') } } assert 'Doc' in get_schemas(api.spec)
def test_api_apispec_sets_base_path(self, app, openapi_version, base_path): app.config['OPENAPI_VERSION'] = openapi_version if base_path is not None: app.config['APPLICATION_ROOT'] = base_path api = Api(app) spec = api.spec.to_dict() if openapi_version == '2.0': assert spec['basePath'] == base_path or '/' else: assert 'basePath' not in spec
def test_apispec_serve_spec_rapidoc_config(self, app): class NewAppConfig(AppConfig): OPENAPI_URL_PREFIX = "/" OPENAPI_RAPIDOC_PATH = "/" OPENAPI_RAPIDOC_URL = "https://domain.tld/rapidoc" OPENAPI_RAPIDOC_CONFIG = {"theme": "dark"} app.config.from_object(NewAppConfig) Api(app) client = app.test_client() response_rapidoc = client.get("/") assert "theme = dark" in response_rapidoc.get_data(True)
def test_apispec_serve_spec_preserve_order(self, app): app.config["OPENAPI_URL_PREFIX"] = "/api-docs" api = Api(app) client = app.test_client() # Add ordered stuff. This is invalid, but it will do for the test. paths = {f"/path_{i}": str(i) for i in range(20)} api.spec._paths = paths response_json_docs = client.get("/api-docs/openapi.json") assert response_json_docs.status_code == 200 assert response_json_docs.json["paths"] == paths
def test_apispec_serve_spec_preserve_order(self, app): app.config['OPENAPI_URL_PREFIX'] = '/api-docs' api = Api(app) client = app.test_client() # Add ordered stuff. This is invalid, but it will do for the test. paths = OrderedDict([('/path_{}'.format(i), str(i)) for i in range(20)]) api.spec._paths = paths response_json_docs = client.get('/api-docs/openapi.json') assert response_json_docs.status_code == 200 assert response_json_docs.json['paths'] == paths
def create_app(): app = Flask(__name__) Bootstrap(app) @app.route("/") def index(): return render_template("index.html") @app.route("/user/<name>") def user(name): return render_template("user.html", name=name) @app.route("/professor") def prof_api_route(): return { "name": "Adrien", "birthday": "02 January", "age": 85, "sex": None, "friends": ["Amadou", "Mariam"], } @app.route("/todoz") def my_api_route(): from tasks.serializers import TaskSchema tasks = Task.query.all() return {"results": TaskSchema(many=True).dump(tasks)} app.config[ "SQLALCHEMY_DATABASE_URI" ] = f"sqlite:///{os.path.join(basedir, 'data.sqlite')}" app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = True app.config["OPENAPI_VERSION"] = "3.0.2" app.config["OPENAPI_URL_PREFIX"] = "openapi" app.config["API_VERSION"] = "1" app.config["OPENAPI_SWAGGER_UI_PATH"] = "api" app.config["OPENAPI_SWAGGER_UI_VERSION"] = "3.23.11" db.init_app(app) api = Api(app) ma.init_app(app) from tasks.models import Task from tasks.views import task_blueprint migrate = Migrate(app, db) api.register_blueprint(task_blueprint) return app
def app_fixture(request, collection, schemas, app): """Return an app client for each configuration - pagination in function / post-pagination - function / method view - default / custom pagination parameters """ blp_factory, as_method_view, custom_params = request.param blueprint = blp_factory(collection, schemas, as_method_view, custom_params) api = Api(app) api.register_blueprint(blueprint) return namedtuple('AppFixture', ('client', 'custom_params'))(app.test_client(), custom_params)
def test_blueprint_response_documents_default_error_response(self, app): api = Api(app) blp = Blueprint('test', 'test', url_prefix='/test') @blp.route('/') @blp.response() def func(): pass api.register_blueprint(blp) get = api.spec.to_dict()['paths']['/test/']['get'] assert get['responses']['default'] == build_ref( api.spec, 'response', 'DEFAULT_ERROR')
def test_error_handler_on_unhandled_error(self, app): client = app.test_client() @app.route('/uncaught') def test_uncaught(): raise Exception('Oops, something really bad happened.') Api(app) response = client.get('/uncaught') assert response.status_code == 500 assert response.json['code'] == 500 assert response.json['status'] == InternalServerError().name
def test_error_handler_on_abort(self, app, code): client = app.test_client() @app.route('/abort') def test_abort(): abort(code) Api(app) response = client.get('/abort') assert response.status_code == code assert response.json['code'] == code assert response.json['status'] == default_exceptions[code]().name
def create_app(env=None): from .config import config_by_name from .routers import register_routes_api_v1 app = Flask(__name__) app.config.from_object(config_by_name[env]) app.config['OPENAPI_VERSION'] = '3.0.2' api = Api(app) # regitster api routers register_routes_api_v1(api) db.init_app(app) return app
def test_api_extra_spec_kwargs(self, app, step): """Test APISpec kwargs can be passed in Api init or app config""" app.config["API_SPEC_OPTIONS"] = {"basePath": "/v2"} if step == "at_once": api = Api(app, spec_kwargs={ "basePath": "/v1", "host": "example.com" }) elif step == "init": api = Api(spec_kwargs={"basePath": "/v1", "host": "example.com"}) api.init_app(app) elif step == "init_app": api = Api() api.init_app(app, spec_kwargs={ "basePath": "/v1", "host": "example.com" }) spec = api.spec.to_dict() assert spec["host"] == "example.com" # app config overrides Api spec_kwargs parameters assert spec["basePath"] == "/v2"
def test_blueprint_arguments_required(self, app, schemas, required, location_map, openapi_version): app.config['OPENAPI_VERSION'] = openapi_version api = Api(app) blp = Blueprint('test', __name__, url_prefix='/test') location, _ = location_map if required is None: @blp.route('/') @blp.arguments(schemas.DocSchema, location=location) def func(): pass else: @blp.route('/') @blp.arguments(schemas.DocSchema, required=required, location=location) def func(): pass api.register_blueprint(blp) get = api.spec.to_dict()['paths']['/test/']['get'] # OAS3 / json, form, files if (openapi_version == '3.0.2' and location in REQUEST_BODY_CONTENT_TYPE): # Body parameter in 'requestBody' assert 'requestBody' in get # Check required defaults to True assert get['requestBody']['required'] == (required is not False) # OAS2 / json elif location == 'json': parameters = get['parameters'] # One parameter: the schema assert len(parameters) == 1 assert 'schema' in parameters[0] assert 'requestBody' not in get # Check required defaults to True assert parameters[0]['required'] == (required is not False) # OAS2-3 / all else: parameters = get['parameters'] # One parameter: the 'field' field in DocSchema assert len(parameters) == 1 assert parameters[0]['name'] == 'field' assert 'requestBody' not in get # Check the required parameter has no impact. # Only the required attribute of the field matters assert parameters[0]['required'] is False
def test_api_extra_spec_kwargs(self, app, step): """Test APISpec kwargs can be passed in Api init or app config""" app.config['API_SPEC_OPTIONS'] = {'basePath': '/v2'} if step == 'at_once': api = Api(app, spec_kwargs={ 'basePath': '/v1', 'host': 'example.com' }) elif step == 'init': api = Api(spec_kwargs={'basePath': '/v1', 'host': 'example.com'}) api.init_app(app) elif step == 'init_app': api = Api() api.init_app(app, spec_kwargs={ 'basePath': '/v1', 'host': 'example.com' }) spec = api.spec.to_dict() assert spec['host'] == 'example.com' # app config overrides Api spec_kwargs parameters assert spec['basePath'] == '/v2'
def test_apispec_serve_spec(self, app, prefix, json_path, redoc_path, redoc_url, swagger_ui_path, swagger_ui_url): """Test default values and leading/trailing slashes issues""" class NewAppConfig(AppConfig): if prefix is not None: OPENAPI_URL_PREFIX = prefix if json_path is not None: OPENAPI_JSON_PATH = json_path if redoc_path is not None: OPENAPI_REDOC_PATH = redoc_path if redoc_url is not None: OPENAPI_REDOC_URL = redoc_url if swagger_ui_path is not None: OPENAPI_SWAGGER_UI_PATH = swagger_ui_path if swagger_ui_url is not None: OPENAPI_SWAGGER_UI_URL = swagger_ui_url title_tag = '<title>API Test</title>' app.config.from_object(NewAppConfig) Api(app) client = app.test_client() response_json_docs = client.get('/docs_url_prefix/openapi.json') response_redoc = client.get('/docs_url_prefix/redoc') response_swagger_ui = client.get('/docs_url_prefix/swagger-ui') if app.config.get('OPENAPI_URL_PREFIX') is None: assert response_json_docs.status_code == 404 assert response_redoc.status_code == 404 assert response_swagger_ui.status_code == 404 else: assert response_json_docs.json['info'] == { 'version': '1', 'title': 'API Test' } if (app.config.get('OPENAPI_REDOC_PATH') is None or app.config.get('OPENAPI_REDOC_URL') is None): assert response_redoc.status_code == 404 else: assert response_redoc.status_code == 200 assert (response_redoc.headers['Content-Type'] == 'text/html; charset=utf-8') assert title_tag in response_redoc.get_data(True) if (app.config.get('OPENAPI_SWAGGER_UI_PATH') is None or app.config.get('OPENAPI_SWAGGER_UI_URL') is None): assert response_swagger_ui.status_code == 404 else: assert response_swagger_ui.status_code == 200 assert (response_swagger_ui.headers['Content-Type'] == 'text/html; charset=utf-8') assert title_tag in response_swagger_ui.get_data(True)
def test_error_handler_payload(self, app): client = app.test_client() errors = { "dimensions": ["Too tall", "Too wide"], "color": ["Too bright"] } messages = {"name": ["Too long"], "age": ["Too young"]} @app.route("/message") def test_message(): abort(404, message="Resource not found") @app.route("/messages") def test_messages(): abort(422, messages=messages, message="Validation issue") @app.route("/errors") def test_errors(): abort(422, errors=errors, messages=messages, message="Wrong!") @app.route("/headers") def test_headers(): abort( 401, message="Access denied", headers={"WWW-Authenticate": 'Basic realm="My Server"'}, ) Api(app) response = client.get("/message") assert response.status_code == 404 assert response.json["message"] == "Resource not found" response = client.get("/messages") assert response.status_code == 422 assert response.json["errors"] == messages response = client.get("/errors") assert response.status_code == 422 assert response.json["errors"] == errors response = client.get("/headers") assert response.status_code == 401 assert response.headers[ "WWW-Authenticate"] == 'Basic realm="My Server"' assert response.json["message"] == "Access denied"
def test_blueprint_doc_called_twice(self, app): api = Api(app) blp = Blueprint('test', __name__, url_prefix='/test') @blp.route('/') @blp.doc(summary='Dummy func') @blp.doc(description='Do dummy stuff') def view_func(): pass api.register_blueprint(blp) spec = api.spec.to_dict() path = spec['paths']['/test/'] assert path['get']['summary'] == 'Dummy func' assert path['get']['description'] == 'Do dummy stuff'