def test_invalid_operation_does_not_stop_application_in_debug_mode(): api = FlaskApi(TEST_FOLDER / "fixtures/op_error_api/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True) assert api.specification['info']['title'] == 'OK' api = FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True) assert api.specification['info']['title'] == 'OK' api = FlaskApi(TEST_FOLDER / "fixtures/module_not_implemented/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True) assert api.specification['info']['title'] == 'OK' api = FlaskApi(TEST_FOLDER / "fixtures/user_module_loading_error/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True) assert api.specification['info']['title'] == 'OK'
def test_invalid_operation_does_stop_application_to_setup(): with pytest.raises(ImportError): FlaskApi(TEST_FOLDER / "fixtures/op_error_api/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}) with pytest.raises(ResolverError): FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}) with pytest.raises(ImportError): FlaskApi(TEST_FOLDER / "fixtures/module_not_implemented/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}) with pytest.raises(ValueError): FlaskApi(TEST_FOLDER / "fixtures/user_module_loading_error/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}) with pytest.raises(ResolverError): FlaskApi(TEST_FOLDER / "fixtures/missing_op_id/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'})
def test_api(): api = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml", base_path="/api/v1.0") assert api.blueprint.name == '/api/v1_0' assert api.blueprint.url_prefix == '/api/v1.0' api2 = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml") assert api2.blueprint.name == '/v1_0' assert api2.blueprint.url_prefix == '/v1.0'
def test_template(): api1 = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'test'}) assert api1.specification['info']['title'] == 'test' api2 = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'other test'}) assert api2.specification['info']['title'] == 'other test'
def test_api(): api = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml", "/api/v1.0", {}) assert api.blueprint.name == '/api/v1_0' assert api.blueprint.url_prefix == '/api/v1.0' # TODO test base_url in spec api2 = FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml") assert api2.blueprint.name == '/v1_0' assert api2.blueprint.url_prefix == '/v1.0'
def test_other_errors_stop_application_to_setup(): # Errors should still result exceptions! with pytest.raises(InvalidSpecification): FlaskApi(TEST_FOLDER / "fixtures/bad_specs/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}) # Debug mode should ignore the error api = FlaskApi(TEST_FOLDER / "fixtures/bad_specs/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True) assert api.specification['info']['title'] == 'OK'
def test_other_errors_stop_application_to_setup(): # The previous tests were just about operationId not being resolvable. # Other errors should still result exceptions! with pytest.raises(InvalidSpecification): FlaskApi(TEST_FOLDER / "fixtures/bad_specs/swagger.yaml", "/api/v1.0", {'title': 'OK'}) # Debug mode should ignore the error api = FlaskApi(TEST_FOLDER / "fixtures/bad_specs/swagger.yaml", "/api/v1.0", {'title': 'OK'}, debug=True) assert api.specification['info']['title'] == 'OK'
def common_error_handler(exception: BaseException) -> flask.Response: """Used to capture connexion exceptions and add link to the type field.""" if isinstance(exception, ProblemException): link = EXCEPTIONS_LINK_MAP.get(exception.status) if link: response = problem( status=exception.status, title=exception.title, detail=exception.detail, type=link, instance=exception.instance, headers=exception.headers, ext=exception.ext, ) else: response = problem( status=exception.status, title=exception.title, detail=exception.detail, type=exception.type, instance=exception.instance, headers=exception.headers, ext=exception.ext, ) else: if not isinstance(exception, werkzeug.exceptions.HTTPException): exception = werkzeug.exceptions.InternalServerError() response = problem(title=exception.name, detail=exception.description, status=exception.code) return FlaskApi.get_response(response)
def register(app): from measurements import api connexion_resolver = Resolver() connexion_api = FlaskApi( specification=pathlib.Path( os.path.join(HERE, "openapi/measurements.yml")), resolver=connexion_resolver, arguments=dict(), swagger_json=True, swagger_ui=False, swagger_path=None, swagger_url=None, resolver_error_handler=None, validate_responses=False, strict_validation=True, auth_all_paths=False, debug=True, validator_map=None, ) app.register_blueprint(api_docs_blueprint, url_prefix="/api") app.register_blueprint(connexion_api.blueprint) app.register_blueprint(api_private_blueprint, url_prefix="/api/_") app.register_blueprint(pages_blueprint, url_prefix="") app.register_error_handler(ProblemException, render_problem_exception) app.register_error_handler(Exception, render_generic_exception) app.errorhandler(404)(page_not_found) app.errorhandler(400)(bad_request)
def common_error_handler(exception): """ :type exception: Exception """ if isinstance(exception, ProblemException): # exception.title show message like 'Bad Request' which is not very helpful response = ErrorHanlder.problem(status=exception.status, title=exception.detail, detail=exception.detail, type=exception.type, instance=exception.instance, headers=exception.headers, ext=exception.ext) elif isinstance(exception, ServiceException): response = ErrorHanlder.problem(status=exception.error_code, title=exception.error_title, detail=exception.error_msg) else: if not isinstance(exception, werkzeug.exceptions.HTTPException): common_exception = werkzeug.exceptions.InternalServerError() if os.getenv('FLASK_CONFIG', 'development') == 'development': raise exception response = ErrorHanlder.problem( title=common_exception.name, detail=common_exception.description, ext=str(exception), status=common_exception.code) else: response = ErrorHanlder.problem(title=exception.name, detail=exception.description, ext=str(exception), status=exception.code) traceback.print_exc() return FlaskApi.get_response(response)
def test_invalid_encoding(): with tempfile.NamedTemporaryFile(mode='wb') as f: f.write( u"swagger: '2.0'\ninfo:\n title: Foo 整\n version: v1\npaths: {}". encode('gbk')) f.flush() FlaskApi(pathlib.Path(f.name), base_path="/api/v1.0")
def test_use_of_safe_load_for_yaml_swagger_specs(): with pytest.raises(YAMLError): with tempfile.NamedTemporaryFile(delete=False) as f: f.write('!!python/object:object {}\n'.encode()) try: FlaskApi(pathlib.Path(f.name), base_path="/api/v1.0") os.unlink(f.name) except InvalidSpecification: pytest.fail("Could load invalid YAML file, use yaml.safe_load!")
def test_use_of_safe_load_for_yaml_swagger_specs(): with pytest.raises(YAMLError): with tempfile.NamedTemporaryFile() as f: f.write('!!python/object:object {}\n'.encode()) f.flush() try: FlaskApi(pathlib.Path(f.name), base_path="/api/v1.0") except SwaggerValidationError: pytest.fail("Could load invalid YAML file, use yaml.safe_load!")
def exception_handler(exception): log.critical("{}:{}:{}-{}".format( getattr(exception, 'name', 'uncaught'), getattr(exception, 'code', 500), getattr(exception, 'description', 'uncaught'), getattr(exception, 'type', 'about:blank'))) trace_exception(exception, log) response = problem(title=getattr(exception, 'name', 'uncaught'), status=getattr(exception, 'code', 500), detail=getattr(exception, 'description', 'uncaught'), type=getattr(exception, 'type', 'about:blank')) return FlaskApi.get_response(response)
def handle_phabricator_api_exception(exc): sentry.captureException() logger.error('phabricator exception', extra={ 'error_code': exc.error_code, 'error_info': exc.error_info, }, exc_info=exc) return FlaskApi.get_response( problem( 500, 'Phabricator Error', 'An unexpected error was received from Phabricator', type='https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500' ))
def handle_phabricator_api_exception(exc): sentry.captureException() logger.error( "phabricator exception", extra={"error_code": exc.error_code, "error_info": exc.error_info}, exc_info=exc, ) return FlaskApi.get_response( problem( 500, "Phabricator Error", "An unexpected error was received from Phabricator", type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500", ) )
def log_error(self, exception): _, exc_val, exc_tb = sys.exc_info() if hasattr(exception, 'name'): resp = problem( title=exception.name, detail=exception.description, status=exception.code, ) else: resp = problem( title=repr(exc_val), detail=''.join(traceback.format_tb(exc_tb)), status=500, ) return FlaskApi.get_response(resp)
def handle_treestatus_exception(exc): sentry.captureException() logger.error("Tree Status exception", exc_info=exc) if current_app.propagate_exceptions: # Mimic the behaviour of Flask.handle_exception() and re-raise the full # traceback in test and debug environments. raise exc return FlaskApi.get_response( problem( 500, "Tree Status Error", "An unexpected error was received from Tree Status", type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500", ))
def _make_error_response(self, exception): exc_info = sys.exc_info() logger.exception("Exception caught", exc_info=exc_info) _, exc_val, exc_tb = exc_info if hasattr(exception, 'to_problem'): resp = exception.to_problem() elif hasattr(exception, 'name'): resp = problem( title=exception.name, detail=exception.description, status=exception.code, ) else: resp = problem( title=repr(exc_val), detail=''.join(traceback.format_tb(exc_tb)), status=500, ) return FlaskApi.get_response(resp)
def render_generic_exception(exception): if not isinstance(exception, werkzeug.exceptions.HTTPException): exc_name = "{}.{}".format( type(exception).__module__, type(exception).__name__) exc_desc = str(exception) if hasattr(exception, "__traceback__"): current_app.logger.error("".join( traceback.format_tb(exception.__traceback__))) current_app.logger.error("Unhandled error occurred, {}: {}".format( exc_name, exc_desc)) exception = werkzeug.exceptions.InternalServerError( description="An unhandled application error occurred: {}".format( exc_name)) response = problem(title=exception.name, detail=exception.description, status=exception.code) return FlaskApi.get_response(response)
def handle_phabricator_api_exception(exc): sentry.captureException() logger.error( "phabricator exception", extra={"error_code": exc.error_code, "error_info": exc.error_info}, exc_info=exc, ) if current_app.propagate_exceptions: # Mimic the behaviour of Flask.handle_exception() and re-raise the full # traceback in test and debug environments. raise exc return FlaskApi.get_response( problem( 500, "Phabricator Error", "An unexpected error was received from Phabricator", type="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500", ) )
def connexion_error_handler(exception: Exception): """Handle exception from request process""" if flask.has_request_context: ext = {"request_id": request_id()} else: ext = None if isinstance(exception, ProblemException): if ext: if exception.ext is None: exception.ext = ext else: exception.ext.update(ext) response = exception.to_problem() extra = { "status": exception.status, "title": exception.title, "detail": exception.detail, } status_code = exception.status log_func = _status2log(exception.status, response_logger) else: if not isinstance(exception, HTTPException): exception = InternalServerError() response = problem( title=exception.name, detail=exception.description, status=exception.code, ext=ext, ) extra = { "status": exception.code, "title": exception.name, "detail": exception.description, } status_code = exception.code log_func = _status2log(status_code, response_logger) log_func("HTTP error (%d)", response.status_code, extra=extra) return FlaskApi.get_response(response)
def test_validation_error_on_completely_invalid_swagger_spec(): with tempfile.NamedTemporaryFile(delete=False) as f: f.write('[1]\n'.encode()) with pytest.raises(InvalidSpecification): FlaskApi(pathlib.Path(f.name), base_path="/api/v1.0") os.unlink(f.name)
def test_api_base_path_slash(): api = FlaskApi(TEST_FOLDER / "fixtures/simple/basepath-slash.yaml") assert api.blueprint.name == '' assert api.blueprint.url_prefix == ''
def test_warn_users_about_base_url_parameter_name_change(mock_api_logger): FlaskApi(TEST_FOLDER / "fixtures/simple/swagger.yaml", base_url="/api/v1") mock_api_logger.warning.assert_called_with( 'Parameter base_url should be no longer used. Use base_path instead.')
def test_validation_error_on_completely_invalid_swagger_spec(): with pytest.raises(SwaggerValidationError): with tempfile.NamedTemporaryFile() as f: f.write('[1]\n'.encode()) f.flush() FlaskApi(pathlib.Path(f.name), base_path="/api/v1.0")
def problem_exception_handler(exception): log.error("{}:{}:{}-{}".format(exception.title, exception.status, exception.detail, exception.type)) trace_exception(exception, log) response = exception.to_problem() return FlaskApi.get_response(response)
def test_invalid_schema_file_structure(): with pytest.raises(SwaggerValidationError): FlaskApi(TEST_FOLDER / "fixtures/invalid_schema/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'}, debug=True)
def render_problem_exception(exception): response = exception.to_problem() return FlaskApi.get_response(response)
def test_other_errors_stop_application_to_setup(): # Errors should still result exceptions! with pytest.raises(InvalidSpecification): FlaskApi(TEST_FOLDER / "fixtures/bad_specs/swagger.yaml", base_path="/api/v1.0", arguments={'title': 'OK'})