Connexion v3 couldn't find path to file defined in '$ref' section

116 Views Asked by At

Our team created an API for the application based on connextion v3. This API works very well. To test the flow of the application, we have created tests in which we prepare the connexion server in exactly the same way and with the same parameters as the API is launched. The only difference is the path in which we run the prepare_server() method because this file is located in the tests directory. Our API consists of many Endpoints, so the swagger documentation is divided into many reference files, which when building the API are easily composed into one large whole. However, when running tests, FileNotFoundError errors occur when assembling paths.

tests/fixtures.py

"""Setup the test environment connexion server."""

import contextlib
import os
import yaml
from typing import Generator

from connexion import FlaskApp
from connexion.resolver import MethodResolver
from flask.testing import FlaskClient


@contextlib.contextmanager
def prepare_server() -> Generator[FlaskClient, str, None]:
    """Context manager that launches local API instance and lets you send request to it.
    Example usage:
    ```
    with prepare_server() as (server, token):
        server.post(
        "/api/v1/control/my_endpoint",
        data = json.dumps(dict)
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        })
    """

    flask_app: FlaskApp = FlaskApp("main", specification_dir=".")
    flask_app.add_api("openAPI.yml", resolver=MethodResolver("test_api"))

    server: FlaskClient = flask_app.app.test_client()
    yield (server, token)

openAPI.yml

openapi: 3.0.3
info:
  title: My API
  description: Rest api for my application
  version: 0.0.1
servers:
  - url: "/api/v1"
paths:
  /application/endpoint/endpoint1/{Id}:
    $ref: 'paths/to/endpoint1.yaml'
  /application/endpoint/endpoint2:
    $ref: 'paths/to/endpoint2.yaml'
  /application/endpoint/endpoint3:
    $ref: 'paths/to/endpoint3.yaml'
  /application/endpoint/endpoint4:
    $ref: 'paths/to/endpoint4.yaml'
  ...
components:
  schemas:
    SchemaOne:
      $ref: 'components/schemas/schema_one.yaml'
    SchemaTwo:
      $ref: 'components/schemas/schema_two.yaml'
    SchemaThree:
      $ref: 'components/schemas/schema_tree.yaml'

After running the tests on Windows, I get an error:

Error
Traceback (most recent call last):
  File "path\to\my\app\venv\Lib\site-packages\connexion\json_schema.py", line 88, in _do_resolve
    retrieved = deep_get(spec, path)
                ^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\utils.py", line 112, in deep_get
    return deep_get(obj[keys[0]], keys[1:])
                    ~~~^^^^^^^^^
KeyError: 'ths'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "path\to\my\app\venv\Lib\site-packages\jsonschema\validators.py", line 1092, in resolve_from_url
    document = self.store[url]
               ~~~~~~~~~~^^^^^
  File "path\to\my\app\venv\Lib\site-packages\jsonschema\_utils.py", line 20, in __getitem__
    return self.store[self.normalize(uri)]
           ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
KeyError: 'paths/to/endpoint1.yaml'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "path\to\my\app\venv\Lib\site-packages\jsonschema\validators.py", line 1095, in resolve_from_url
    document = self.resolve_remote(url)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\jsonschema\validators.py", line 1192, in resolve_remote
    result = self.handlers[scheme](uri)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\json_schema.py", line 41, in __call__
    with open(filepath) as fh:
         ^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '\\\\\\paths\\to\\endpoint1.yaml'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "path\to\my\app\venv\Lib\site-packages\responses\__init__.py", line 218, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\app\tests\decorators.py", line 67, in wrap
    func(self, *args, **kwargs)
  File "path\to\my\app\venv\Lib\site-packages\responses\__init__.py", line 218, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\app\tests\decorators.py", line 35, in wrap
    func(self, *args, **kwargs)
  File "path\to\my\app\venv\Lib\site-packages\responses\__init__.py", line 218, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\app\tests\decorators.py", line 19, in wrap
    func(self, *args, **kwargs)
  File "path\to\my\app\app\tests\bb_api_main\cmdb\test_mail_verification.py", line 117, in test_happy_flow
    with prepare_server() as (server, token):
  File "C:\Users\HW36WN\AppData\Local\Programs\Python\Python311\Lib\contextlib.py", line 137, in __enter__
    return next(self.gen)
           ^^^^^^^^^^^^^^
  File "path\to\my\app\app\tests\fixtures.py", line 41, in prepare_server
    flask_app.add_api("openAPI.yml", resolver=MethodResolver("test_api"))
  File "path\to\my\app\venv\Lib\site-packages\connexion\apps\abstract.py", line 180, in add_api
    return self.middleware.add_api(
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\middleware\main.py", line 420, in add_api
    specification = Specification.load(specification, arguments=arguments)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\spec.py", line 205, in load
    return cls.from_file(spec, arguments=arguments, base_uri=base_uri)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\spec.py", line 159, in from_file
    return cls.from_dict(spec, base_uri=base_uri)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\spec.py", line 196, in from_dict
    return OpenAPISpecification(spec, base_uri=base_uri)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\spec.py", line 83, in __init__
    self._spec = resolve_refs(raw_spec, base_uri=base_uri)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\json_schema.py", line 106, in resolve_refs
    res = _do_resolve(spec)
          ^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\json_schema.py", line 100, in _do_resolve
    node[k] = _do_resolve(v)
              ^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\json_schema.py", line 100, in _do_resolve
    node[k] = _do_resolve(v)
              ^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\connexion\json_schema.py", line 96, in _do_resolve
    with resolver.resolving(node["$ref"]) as resolved:
  File "C:\Users\HW36WN\AppData\Local\Programs\Python\Python311\Lib\contextlib.py", line 137, in __enter__
    return next(self.gen)
           ^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\jsonschema\validators.py", line 1034, in resolving
    url, resolved = self.resolve(ref)
                    ^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\jsonschema\validators.py", line 1081, in resolve
    return url, self._remote_cache(url)
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "path\to\my\app\venv\Lib\site-packages\jsonschema\validators.py", line 1097, in resolve_from_url
    raise exceptions._RefResolutionError(exc)
jsonschema.exceptions._RefResolutionError: [Errno 2] No such file or directory: '\\\\\\paths\\to\\endpoint1.yaml'

On Linux this error looks similar:

 Traceback (most recent call last):
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
     rv = self.dispatch_request()
          ^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
     self.raise_routing_exception(req)
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
     raise request.routing_exception  # type: ignore
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
     result = self.url_adapter.match(return_rule=True)  # type: ignore
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
     raise NotFound() from None
 werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
 
 During handling of the above exception, another exception occurred:
 
 Traceback (most recent call last):
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/responses/__init__.py", line 232, in wrapper
     return func(*args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/app/tests/decorators.py", line 67, in wrap
     func(self, *args, **kwargs)
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/responses/__init__.py", line 232, in wrapper
     return func(*args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/app/tests/decorators.py", line 35, in wrap
     func(self, *args, **kwargs)
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/responses/__init__.py", line 232, in wrapper
     return func(*args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/app/tests/decorators.py", line 19, in wrap
     func(self, *args, **kwargs)
   File "/home/vsts/work/1/repo/app/tests/application/endpoint/test_endpoint_one.py", line 118, in test_happy_flow
     response: werkzeug.Response = server.post(
                                   ^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/werkzeug/test.py", line 1165, in post
     return self.open(*args, **kw)
            ^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/flask/testing.py", line 232, in open
     response = super().open(
                ^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/werkzeug/test.py", line 1114, in open
     response = self.run_wsgi_app(request.environ, buffered=buffered)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/werkzeug/test.py", line 986, in run_wsgi_app
     rv = run_wsgi_app(self.application, environ, buffered=buffered)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/werkzeug/test.py", line 1262, in run_wsgi_app
     app_rv = app(environ, start_response)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/flask/app.py", line 1478, in __call__
     return self.wsgi_app(environ, start_response)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/flask/app.py", line 1458, in wsgi_app
     response = self.handle_exception(e)
                ^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/flask/app.py", line 1455, in wsgi_app
     response = self.full_dispatch_request()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/flask/app.py", line 869, in full_dispatch_request
     rv = self.handle_user_exception(e)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/flask/app.py", line 759, in handle_user_exception
     return self.ensure_sync(handler)(e)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/vsts/work/1/repo/venv/lib/python3.11/site-packages/connexion/apps/flask.py", line 245, in _http_exception
     raise starlette.exceptions.HTTPException(exc.code, detail=exc.description)
 starlette.exceptions.HTTPException: 404: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
 

I have already tried to add the absolute path to the reference swagger files and I have tried to manipulate the specification_dir parameter in various ways but unfortunately I did not achieve the expected effect.

1

There are 1 best solutions below

0
Maddin On

It seems like there is something broken with the jsonschema library used by connexion which messes the path up. I could get it working by monkey patching the FileHandlers with a custom Filehandler:

from pathlib import Path
import yaml
import connexion.json_schema
from connexion.json_schema import ExtendedSafeLoader

class CustomFileHandler:
    """Handler to resolve file refs."""
    def __call__(self, uri):
        path = Path("your_corret_path_to_spec").resolve()
        with open(path) as fh:
            return yaml.load(fh, ExtendedSafeLoader)


connexion.json_schema.handlers.update(
    {
        "file": CustomFileHandler(),
        "": CustomFileHandler(),
    }
)