我们的团队基于 connextion v3 为应用程序创建了一个 API。这个 API 工作得很好。为了测试应用程序的流程,我们创建了测试,在测试中我们以与启动 API 完全相同的方式和相同的参数来准备连接服务器。唯一的区别是我们运行 prepare_server() 方法的路径,因为该文件位于 tests 目录中。我们的 API 由许多 Endpoint 组成,因此 swagger 文档分为许多参考文件,在构建 API 时很容易将其组合成一个大整体。但是,运行测试时,组装路径时会出现 FileNotFoundError 错误。
测试/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'
在 Windows 上运行测试后,出现错误:
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'
在 Linux 上,此错误看起来类似:
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.
我已经尝试添加参考swagger文件的绝对路径,并且尝试以各种方式操作
specification_dir
参数,但不幸的是我没有达到预期的效果。
连接使用的 jsonschema 库似乎有问题,导致路径混乱。我可以通过使用自定义文件处理程序猴子修补文件处理程序来使其工作:
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(),
}
)