def setup_rest(app: web.Application): settings: RestSettings = get_plugin_settings(app) is_diagnostics_enabled: bool = (app[APP_SETTINGS_KEY].WEBSERVER_DIAGNOSTICS is not None) spec_path = get_openapi_specs_path(api_version_dir=API_VTAG) # validated openapi specs app[APP_OPENAPI_SPECS_KEY] = specs = load_openapi_specs(spec_path) # version check base_path = openapi.get_base_path(specs) major, *_ = specs.info.version if f"/v{major}" != base_path: raise ValueError( f"REST API basepath {base_path} does not fit openapi.yml version {specs.info.version}" ) if api_version_prefix != f"v{major}": raise ValueError( f"__version__.api_version_prefix {api_version_prefix} does not fit openapi.yml version {specs.info.version}" ) # basic routes app.add_routes(rest_handlers.routes) if not is_diagnostics_enabled: # NOTE: the healthcheck route is normally in diagnostics, but # if disabled, this plugin adds a simple version of it app.add_routes([ web.get( path=f"/{api_version_prefix}/health", handler=rest_handlers.check_running, name="check_health", ) ]) # middlewares # NOTE: using safe get here since some tests use incomplete configs app.middlewares.extend([ error_middleware_factory( api_version_prefix, log_exceptions=not is_diagnostics_enabled, ), envelope_middleware_factory(api_version_prefix), ]) # Adds swagger doc UI # - API doc at /dev/doc (optional, e.g. for testing since it can be heavy) # - NOTE: avoid /api/* since traeffik uses for it's own API # log.debug("OAS loaded from %s ", spec_path) if settings.REST_SWAGGER_API_DOC_ENABLED: setup_swagger( app, swagger_url="/dev/doc", swagger_from_file=str(spec_path), ui_version=3, )
async def test_envelope_middleware(path, expected_data, client, specs): base = openapi.get_base_path(specs) response = await client.get(base + path) payload = await response.json() assert is_enveloped(payload) data, error = unwrap_envelope(payload) assert not error assert data == expected_data
async def test_validate_handlers(path, client, specs): base = openapi.get_base_path(specs) response = await client.get(base + path) payload = await response.json() assert is_enveloped(payload) data, error = unwrap_envelope(payload) assert not error assert data
def setup(app: web.Application, *, swagger_doc_enabled: bool = True): # ---------------------------------------------- # TODO: temporary, just to check compatibility between # trafaret and pydantic schemas cfg = assert_valid_config(app) # --------------------------------------------- api_version_dir = cfg["version"] spec_path = get_openapi_specs_path(api_version_dir) # validated openapi specs app[APP_OPENAPI_SPECS_KEY] = specs = load_openapi_specs(spec_path) # version check base_path = openapi.get_base_path(specs) major, *_ = specs.info.version if f"/v{major}" != base_path: raise ValueError( f"REST API basepath {base_path} does not fit openapi.yml version {specs.info.version}" ) if api_version_prefix != f"v{major}": raise ValueError( f"__version__.api_version_prefix {api_version_prefix} does not fit openapi.yml version {specs.info.version}" ) # diagnostics routes routes = rest_routes.create(specs) app.router.add_routes(routes) # middlewares # NOTE: using safe get here since some tests use incomplete configs is_diagnostics_enabled = get_diagnostics_config(app).get("enabled", False) app.middlewares.extend([ error_middleware_factory( api_version_prefix, log_exceptions=not is_diagnostics_enabled, ), envelope_middleware_factory(api_version_prefix), ]) # Adds swagger doc UI # - API doc at /dev/doc (optional, e.g. for testing since it can be heavy) # - NOTE: avoid /api/* since traeffik uses for it's own API # log.debug("OAS loaded from %s ", spec_path) if swagger_doc_enabled: setup_swagger( app, swagger_url="/dev/doc", swagger_from_file=str(spec_path), ui_version=3, )
def test_prepends_basepath(specs): # not - strict try: handlers = Handlers() routes = create_routes_from_namespace(specs, handlers, strict=False) except Exception: # pylint: disable=W0703 pytest.fail("Non-strict failed", pytrace=True) basepath = openapi.get_base_path(specs) for route in routes: assert route.path.startswith(basepath) assert route.handler.__name__[len("get_"):] in route.path
async def test_404_not_found(client, specs): # see FIXME: in validate_middleware_factory response = await client.get("/some-invalid-address-outside-api") payload = await response.text() assert response.status == 404, payload api_base = openapi.get_base_path(specs) response = await client.get(api_base + "/some-invalid-address-in-api") payload = await response.json() assert response.status == 404, payload assert is_enveloped(payload) data, error = unwrap_envelope(payload) assert error assert not data
def client(event_loop, aiohttp_client, specs): app = web.Application() # routes handlers = Handlers() routes = create_routes_from_namespace(specs, handlers, strict=False) app.router.add_routes(routes) # validators app[APP_OPENAPI_SPECS_KEY] = specs # middlewares base = openapi.get_base_path(specs) app.middlewares.append(error_middleware_factory(base)) app.middlewares.append(envelope_middleware_factory(base)) return event_loop.run_until_complete(aiohttp_client(app))
def setup_rest(app: web.Application): """Setup the rest API module in the application in aiohttp fashion. - loads and validate openapi specs from a remote (e.g. apihub) or local location - connects openapi specs paths to handlers (see rest_routes.py) - enables error, validation and envelope middlewares on API routes IMPORTANT: this is a critical subsystem. Any failure should stop the system startup. It CANNOT be simply disabled & continue """ log.debug("Setting up %s ...", __name__) spec_path = resources.get_path("api/v0/openapi.yaml") with spec_path.open() as fh: spec_dict = yaml.safe_load(fh) api_specs = openapi_core.create_spec(spec_dict, spec_path.as_uri()) # validated openapi specs app[APP_OPENAPI_SPECS_KEY] = api_specs # Connects handlers set_default_names(handlers.routes) app.router.add_routes(handlers.routes) app.router.add_routes(app_handlers.routes) log.debug( "routes:\n %s", "\n".join(f"\t{name}:{resource}" for name, resource in app.router.named_resources().items()), ) # Enable error, validation and envelop middleware on API routes base_path = get_base_path(api_specs) append_rest_middlewares(app, base_path) # Adds swagger doc UI setup_swagger( app, swagger_url="/dev/doc", swagger_from_file=str(spec_path), ui_version=3, )
def create(specs: openapi.Spec) -> List[web.RouteDef]: # TODO: consider the case in which server creates routes for both v0 and v1!!! # TODO: should this be taken from servers instead? base_path = openapi.get_base_path(specs) log.debug("creating %s ", __name__) routes = [] # maintenance -- path, handle = "/", rest_handlers.check_running operation_id = specs.paths[path].operations["get"].operation_id routes.append(web.get(base_path + path, handle, name=operation_id)) path, handle = "/check/{action}", rest_handlers.check_action operation_id = specs.paths[path].operations["post"].operation_id routes.append(web.post(base_path + path, handle, name=operation_id)) path, handle = "/config", rest_handlers.get_config operation_id = specs.paths[path].operations["get"].operation_id routes.append(web.get(base_path + path, handle, name=operation_id)) return routes
def create_routes(specs: openapi.Spec) -> List[web.RouteDef]: """Creates routes mapping operators_id with handler functions :param specs: validated oas :type specs: openapi.Spec :return: list of web routes for auth :rtype: List[web.RouteDef] """ log.debug("Creating %s ", __name__) base_path = openapi.get_base_path(specs) def include_path(tuple_object): _method, path, _operation_id, _tags = tuple_object return path.startswith(base_path + "/auth/") handlers_map = { "auth_register": login_handlers.register, "auth_login": login_handlers.login, "auth_logout": login_handlers.logout, "auth_reset_password": login_handlers.reset_password, "auth_reset_password_allowed": login_handlers.reset_password_allowed, "auth_change_email": login_handlers.change_email, "auth_change_password": login_handlers.change_password, "auth_confirmation": login_handlers.email_confirmation, "create_api_key": api_keys_handlers.create_api_key, "delete_api_key": api_keys_handlers.delete_api_key, "list_api_keys": api_keys_handlers.list_api_keys, } routes = map_handlers_with_operations(handlers_map, filter(include_path, iter_path_operations(specs)), strict=True) log.debug("Mapped auth routes: %s", "\n".join([pformat(r) for r in routes])) return routes