mirror of
https://github.com/LukeHagar/connexion.git
synced 2025-12-06 04:19:26 +00:00
Allow the specification to be specified as a URL. (#1871)
Changes proposed in this pull request: - Allow a specification to be specified as a URL that is downloaded when the App runs. In combination with the existing mock features, this makes it a single command to run a mock server for any published API. --------- Co-authored-by: Robbe Sneyders <robbe.sneyders@gmail.com>
This commit is contained in:
@@ -143,8 +143,8 @@ class AbstractApp:
|
||||
Register an API represented by a single OpenAPI specification on this application.
|
||||
Multiple APIs can be registered on a single application.
|
||||
|
||||
:param specification: OpenAPI specification. Can be provided either as dict, or as path
|
||||
to file.
|
||||
:param specification: OpenAPI specification. Can be provided either as dict, a path
|
||||
to file, or a URL.
|
||||
:param base_path: Base path to host the API. This overrides the basePath / servers in the
|
||||
specification.
|
||||
:param name: Name to register the API with. If no name is passed, the base_path is used
|
||||
|
||||
@@ -117,6 +117,9 @@ def create_app(args: t.Optional[argparse.Namespace] = None) -> AbstractApp:
|
||||
|
||||
logging.basicConfig(level=logging_level)
|
||||
|
||||
if args.spec_file.startswith("http") or args.spec_file.startswith("https"):
|
||||
spec_file_full_path = args.spec_file
|
||||
else:
|
||||
spec_file_full_path = os.path.abspath(args.spec_file)
|
||||
py_module_path = args.base_module_path or os.path.dirname(spec_file_full_path)
|
||||
sys.path.insert(1, os.path.abspath(py_module_path))
|
||||
|
||||
@@ -371,8 +371,8 @@ class ConnexionMiddleware:
|
||||
Register een API represented by a single OpenAPI specification on this middleware.
|
||||
Multiple APIs can be registered on a single middleware.
|
||||
|
||||
:param specification: OpenAPI specification. Can be provided either as dict, or as path
|
||||
to file.
|
||||
:param specification: OpenAPI specification. Can be provided either as dict, a path
|
||||
to file, or a URL.
|
||||
:param base_path: Base path to host the API. This overrides the basePath / servers in the
|
||||
specification.
|
||||
:param name: Name to register the API with. If no name is passed, the base_path is used
|
||||
@@ -408,7 +408,11 @@ class ConnexionMiddleware:
|
||||
if self.middleware_stack is not None:
|
||||
raise RuntimeError("Cannot add api after an application has started")
|
||||
|
||||
if isinstance(specification, (pathlib.Path, str)):
|
||||
if isinstance(specification, str) and (
|
||||
specification.startswith("http://") or specification.startswith("https://")
|
||||
):
|
||||
pass
|
||||
elif isinstance(specification, (pathlib.Path, str)):
|
||||
specification = t.cast(pathlib.Path, self.specification_dir / specification)
|
||||
|
||||
# Add specification as file to watch for reloading
|
||||
|
||||
@@ -19,7 +19,7 @@ from jsonschema import Draft4Validator
|
||||
from jsonschema.validators import extend as extend_validator
|
||||
|
||||
from .exceptions import InvalidSpecification
|
||||
from .json_schema import NullableTypeValidator, resolve_refs
|
||||
from .json_schema import NullableTypeValidator, URLHandler, resolve_refs
|
||||
from .operations import AbstractOperation, OpenAPIOperation, Swagger2Operation
|
||||
from .utils import deep_get
|
||||
|
||||
@@ -158,6 +158,14 @@ class Specification(Mapping):
|
||||
spec = cls._load_spec_from_file(arguments, specification_path)
|
||||
return cls.from_dict(spec, base_uri=base_uri)
|
||||
|
||||
@classmethod
|
||||
def from_url(cls, spec, *, base_uri=""):
|
||||
"""
|
||||
Takes in a path to a YAML file, and returns a Specification
|
||||
"""
|
||||
spec = URLHandler()(spec)
|
||||
return cls.from_dict(spec, base_uri=base_uri)
|
||||
|
||||
@staticmethod
|
||||
def _get_spec_version(spec):
|
||||
try:
|
||||
@@ -200,6 +208,10 @@ class Specification(Mapping):
|
||||
|
||||
@classmethod
|
||||
def load(cls, spec, *, arguments=None):
|
||||
if isinstance(spec, str) and (
|
||||
spec.startswith("http://") or spec.startswith("https://")
|
||||
):
|
||||
return cls.from_url(spec)
|
||||
if not isinstance(spec, dict):
|
||||
base_uri = f"{pathlib.Path(spec).parent}{os.sep}"
|
||||
return cls.from_file(spec, arguments=arguments, base_uri=base_uri)
|
||||
|
||||
@@ -30,7 +30,8 @@ The basic usage of this command is:
|
||||
|
||||
Where:
|
||||
|
||||
- SPEC_FILE: Your OpenAPI specification file in YAML format.
|
||||
- SPEC_FILE: Your OpenAPI specification file in YAML format. Can also be given
|
||||
as a URL, which will be automatically downloaded.
|
||||
- BASE_MODULE_PATH (optional): filesystem path where the API endpoints
|
||||
handlers are going to be imported from. In short, where your Python
|
||||
code is saved.
|
||||
@@ -52,3 +53,5 @@ Your API specification file is not required to have any ``operationId``.
|
||||
.. code-block:: bash
|
||||
|
||||
$ connexion run your_api.yaml --mock=all
|
||||
|
||||
$ connexion run https://raw.githubusercontent.com/spec-first/connexion/main/examples/helloworld_async/spec/openapi.yaml --mock=all
|
||||
|
||||
@@ -51,6 +51,42 @@ def test_api_base_path_slash():
|
||||
assert api.blueprint.url_prefix == ""
|
||||
|
||||
|
||||
def test_remote_api():
|
||||
api = FlaskApi(
|
||||
Specification.load(
|
||||
"https://raw.githubusercontent.com/spec-first/connexion/165a915/tests/fixtures/simple/swagger.yaml"
|
||||
),
|
||||
base_path="/api/v1.0",
|
||||
)
|
||||
assert api.blueprint.name == "/api/v1_0"
|
||||
assert api.blueprint.url_prefix == "/api/v1.0"
|
||||
|
||||
api2 = FlaskApi(
|
||||
Specification.load(
|
||||
"https://raw.githubusercontent.com/spec-first/connexion/165a915/tests/fixtures/simple/swagger.yaml"
|
||||
)
|
||||
)
|
||||
assert api2.blueprint.name == "/v1_0"
|
||||
assert api2.blueprint.url_prefix == "/v1.0"
|
||||
|
||||
api3 = FlaskApi(
|
||||
Specification.load(
|
||||
"https://raw.githubusercontent.com/spec-first/connexion/165a915/tests/fixtures/simple/openapi.yaml"
|
||||
),
|
||||
base_path="/api/v1.0",
|
||||
)
|
||||
assert api3.blueprint.name == "/api/v1_0"
|
||||
assert api3.blueprint.url_prefix == "/api/v1.0"
|
||||
|
||||
api4 = FlaskApi(
|
||||
Specification.load(
|
||||
"https://raw.githubusercontent.com/spec-first/connexion/165a915/tests/fixtures/simple/openapi.yaml"
|
||||
)
|
||||
)
|
||||
assert api4.blueprint.name == "/v1_0"
|
||||
assert api4.blueprint.url_prefix == "/v1.0"
|
||||
|
||||
|
||||
def test_template():
|
||||
api1 = FlaskApi(
|
||||
Specification.load(
|
||||
|
||||
Reference in New Issue
Block a user