예제 #1
0
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
예제 #3
0
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
예제 #4
0
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,
        )
예제 #5
0
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))
예제 #8
0
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,
    )
예제 #9
0
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
예제 #10
0
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