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.
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: