From daf3cf012e38543670e238a8cf18873370496bd6 Mon Sep 17 00:00:00 2001 From: Alex Zywicki Date: Wed, 5 Jan 2022 12:42:24 -0600 Subject: [PATCH 1/4] Allow from_spec to accept an AsyncApiSpec object --- asynction/mock_server.py | 6 ++++-- asynction/server.py | 12 +++++++++--- tests/unit/test_mock_server.py | 8 ++++++++ tests/unit/test_server.py | 6 ++++++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/asynction/mock_server.py b/asynction/mock_server.py index 65ad245..d1bf827 100644 --- a/asynction/mock_server.py +++ b/asynction/mock_server.py @@ -17,6 +17,7 @@ from typing import MutableSequence from typing import Optional from typing import Sequence +from typing import Union from faker import Faker from faker.exceptions import UnsupportedFeature @@ -138,7 +139,7 @@ def __init__( @classmethod def from_spec( cls, - spec_path: Path, + spec_path: Union[Path, AsyncApiSpec], validation: bool = True, server_name: Optional[str] = None, docs: bool = True, @@ -160,7 +161,8 @@ def from_spec( * ``custom_formats_sample_size`` - :param spec_path: The path where the AsyncAPI YAML specification is located. + :param spec_path: The path where the AsyncAPI YAML specification is located, + or a pre loaded AsyncApiSpec object. :param validation: When set to ``False``, message payloads, channel bindings and ack callbacks are NOT validated. Defaults to ``True``. diff --git a/asynction/server.py b/asynction/server.py index e9d4c96..e0ab9a9 100644 --- a/asynction/server.py +++ b/asynction/server.py @@ -7,6 +7,7 @@ from typing import Any from typing import Optional from typing import Sequence +from typing import Union from urllib.parse import urlparse import jsonschema @@ -113,7 +114,7 @@ def init_app(self, app: Optional[Flask], **kwargs) -> None: @classmethod def from_spec( cls, - spec_path: Path, + spec_path: Union[Path, AsyncApiSpec], validation: bool = True, server_name: Optional[str] = None, docs: bool = True, @@ -124,7 +125,8 @@ def from_spec( """Create a Flask-SocketIO server from an AsyncAPI spec. This is the single entrypoint to the Asynction server API. - :param spec_path: The path where the AsyncAPI YAML specification is located. + :param spec_path: The path where the AsyncAPI YAML specification is located, + or a pre loaded AsyncApiSpec object. :param validation: When set to ``False``, message payloads, channel bindings and ack callbacks are NOT validated. Defaults to ``True``. @@ -155,7 +157,11 @@ def from_spec( ) """ - spec = load_spec(spec_path=spec_path) + if isinstance(spec_path, AsyncApiSpec): + spec = spec_path + else: + spec = load_spec(spec_path=spec_path) + server_security: Sequence[SecurityRequirement] = [] if ( server_name is not None diff --git a/tests/unit/test_mock_server.py b/tests/unit/test_mock_server.py index bfafcb9..603c78f 100644 --- a/tests/unit/test_mock_server.py +++ b/tests/unit/test_mock_server.py @@ -29,6 +29,7 @@ from asynction.mock_server import task_scheduler from asynction.server import AsynctionSocketIO from asynction.server import _noop_handler +from asynction.server import load_spec from asynction.types import GLOBAL_NAMESPACE from asynction.types import AsyncApiSpec from asynction.types import Channel @@ -140,6 +141,13 @@ def test_mock_asynction_socketio_from_spec(fixture_paths: FixturePaths): assert isinstance(mock_asio.faker, Faker) +def test_mock_asynction_socketio_from_spec_object(fixture_paths: FixturePaths): + spec = load_spec(fixture_paths.simple) + mock_asio = MockAsynctionSocketIO.from_spec(spec_path=spec) + assert isinstance(mock_asio, MockAsynctionSocketIO) + assert isinstance(mock_asio.faker, Faker) + + def new_mock_asynction_socket_io( spec: AsyncApiSpec, app: Optional[Flask] = None, diff --git a/tests/unit/test_server.py b/tests/unit/test_server.py index a419aa7..964174b 100644 --- a/tests/unit/test_server.py +++ b/tests/unit/test_server.py @@ -50,6 +50,12 @@ def test_asynction_socketio_from_spec(fixture_paths: FixturePaths): assert isinstance(asio, AsynctionSocketIO) +def test_asynction_socketio_from_spec_object(fixture_paths: FixturePaths): + spec = load_spec(fixture_paths.simple) + asio = AsynctionSocketIO.from_spec(spec_path=spec) + assert isinstance(asio, AsynctionSocketIO) + + def test_asynction_socketio_from_spec_uses_spec_server_path_as_socketio_path( fixture_paths: FixturePaths, ): From 616d983dfc381b804a932f8ea2086de5d4c4a23c Mon Sep 17 00:00:00 2001 From: Alex Zywicki Date: Wed, 5 Jan 2022 12:43:36 -0600 Subject: [PATCH 2/4] Fix doc comment indentation --- asynction/mock_server.py | 2 +- asynction/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/asynction/mock_server.py b/asynction/mock_server.py index d1bf827..cf6847c 100644 --- a/asynction/mock_server.py +++ b/asynction/mock_server.py @@ -162,7 +162,7 @@ def from_spec( * ``custom_formats_sample_size`` :param spec_path: The path where the AsyncAPI YAML specification is located, - or a pre loaded AsyncApiSpec object. + or a pre loaded AsyncApiSpec object. :param validation: When set to ``False``, message payloads, channel bindings and ack callbacks are NOT validated. Defaults to ``True``. diff --git a/asynction/server.py b/asynction/server.py index e0ab9a9..4760b4d 100644 --- a/asynction/server.py +++ b/asynction/server.py @@ -126,7 +126,7 @@ def from_spec( This is the single entrypoint to the Asynction server API. :param spec_path: The path where the AsyncAPI YAML specification is located, - or a pre loaded AsyncApiSpec object. + or a pre loaded AsyncApiSpec object. :param validation: When set to ``False``, message payloads, channel bindings and ack callbacks are NOT validated. Defaults to ``True``. From cacffc94cae63e8fdfb9f2c7996ef38bb2c87634 Mon Sep 17 00:00:00 2001 From: Alex Zywicki Date: Mon, 17 Jan 2022 10:39:12 -0600 Subject: [PATCH 3/4] Convert from AsyncApiSpec to JsonMapping --- asynction/mock_server.py | 4 ++-- asynction/server.py | 11 ++++++----- tests/unit/test_mock_server.py | 5 +++-- tests/unit/test_server.py | 4 +++- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/asynction/mock_server.py b/asynction/mock_server.py index cf6847c..2a65d7e 100644 --- a/asynction/mock_server.py +++ b/asynction/mock_server.py @@ -139,7 +139,7 @@ def __init__( @classmethod def from_spec( cls, - spec_path: Union[Path, AsyncApiSpec], + spec_path: Union[Path, JSONMapping], validation: bool = True, server_name: Optional[str] = None, docs: bool = True, @@ -162,7 +162,7 @@ def from_spec( * ``custom_formats_sample_size`` :param spec_path: The path where the AsyncAPI YAML specification is located, - or a pre loaded AsyncApiSpec object. + or a pre loaded JSONMapping object. :param validation: When set to ``False``, message payloads, channel bindings and ack callbacks are NOT validated. Defaults to ``True``. diff --git a/asynction/server.py b/asynction/server.py index d222331..d7197f6 100644 --- a/asynction/server.py +++ b/asynction/server.py @@ -114,7 +114,7 @@ def init_app(self, app: Optional[Flask], **kwargs) -> None: @classmethod def from_spec( cls, - spec_path: Union[Path, AsyncApiSpec], + spec_path: Union[Path, JSONMapping], validation: bool = True, server_name: Optional[str] = None, docs: bool = True, @@ -126,7 +126,7 @@ def from_spec( This is the single entrypoint to the Asynction server API. :param spec_path: The path where the AsyncAPI YAML specification is located, - or a pre loaded AsyncApiSpec object. + or a pre loaded JSONMapping object. :param validation: When set to ``False``, message payloads, channel bindings and ack callbacks are NOT validated. Defaults to ``True``. @@ -157,10 +157,11 @@ def from_spec( ) """ - if isinstance(spec_path, AsyncApiSpec): - spec = spec_path - else: + if isinstance(spec_path, Path): spec = load_spec(spec_path=spec_path) + else: + raw_resolved = resolve_references(raw_spec=spec_path) + spec = AsyncApiSpec.from_dict(raw_resolved) server_security: Sequence[SecurityRequirement] = [] if ( diff --git a/tests/unit/test_mock_server.py b/tests/unit/test_mock_server.py index 603c78f..d5b7b7c 100644 --- a/tests/unit/test_mock_server.py +++ b/tests/unit/test_mock_server.py @@ -12,6 +12,7 @@ import jsonschema import pytest +import yaml from faker import Faker from flask.app import Flask from flask_socketio import SocketIO @@ -29,7 +30,6 @@ from asynction.mock_server import task_scheduler from asynction.server import AsynctionSocketIO from asynction.server import _noop_handler -from asynction.server import load_spec from asynction.types import GLOBAL_NAMESPACE from asynction.types import AsyncApiSpec from asynction.types import Channel @@ -142,7 +142,8 @@ def test_mock_asynction_socketio_from_spec(fixture_paths: FixturePaths): def test_mock_asynction_socketio_from_spec_object(fixture_paths: FixturePaths): - spec = load_spec(fixture_paths.simple) + with open(fixture_paths.simple, "r") as simple: + spec = yaml.safe_load(simple) mock_asio = MockAsynctionSocketIO.from_spec(spec_path=spec) assert isinstance(mock_asio, MockAsynctionSocketIO) assert isinstance(mock_asio.faker, Faker) diff --git a/tests/unit/test_server.py b/tests/unit/test_server.py index a7b9c12..a7ead9b 100644 --- a/tests/unit/test_server.py +++ b/tests/unit/test_server.py @@ -2,6 +2,7 @@ from unittest import mock import pytest +import yaml from faker import Faker from flask import Flask @@ -51,7 +52,8 @@ def test_asynction_socketio_from_spec(fixture_paths: FixturePaths): def test_asynction_socketio_from_spec_object(fixture_paths: FixturePaths): - spec = load_spec(fixture_paths.simple) + with open(fixture_paths.simple, "r") as simple: + spec = yaml.safe_load(simple) asio = AsynctionSocketIO.from_spec(spec_path=spec) assert isinstance(asio, AsynctionSocketIO) From db740c8888bd4e14a04d373db6811a29c487f7b3 Mon Sep 17 00:00:00 2001 From: Alex Zywicki Date: Wed, 19 Jan 2022 13:01:40 -0600 Subject: [PATCH 4/4] Handle review items --- asynction/mock_server.py | 2 +- asynction/server.py | 24 +++++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/asynction/mock_server.py b/asynction/mock_server.py index 2a65d7e..0e69e58 100644 --- a/asynction/mock_server.py +++ b/asynction/mock_server.py @@ -162,7 +162,7 @@ def from_spec( * ``custom_formats_sample_size`` :param spec_path: The path where the AsyncAPI YAML specification is located, - or a pre loaded JSONMapping object. + or a dictionary object of the AsyncAPI data structure :param validation: When set to ``False``, message payloads, channel bindings and ack callbacks are NOT validated. Defaults to ``True``. diff --git a/asynction/server.py b/asynction/server.py index d7197f6..53e9de4 100644 --- a/asynction/server.py +++ b/asynction/server.py @@ -68,13 +68,15 @@ def resolve_references(raw_spec: JSONMapping) -> JSONMapping: return deep_resolve(raw_spec, resolver) -def load_spec(spec_path: Path) -> AsyncApiSpec: - with open(spec_path) as f: - serialized = f.read() - raw = yaml.safe_load(serialized) - - raw_resolved = resolve_references(raw_spec=raw) - +def load_spec(spec_path: Union[Path, JSONMapping]) -> AsyncApiSpec: + if isinstance(spec_path, Path): + with open(spec_path) as f: + serialized = f.read() + spec = yaml.safe_load(serialized) + else: + spec = spec_path + + raw_resolved = resolve_references(spec) return AsyncApiSpec.from_dict(raw_resolved) @@ -126,7 +128,7 @@ def from_spec( This is the single entrypoint to the Asynction server API. :param spec_path: The path where the AsyncAPI YAML specification is located, - or a pre loaded JSONMapping object. + or a dictionary object of the AsyncAPI data structure :param validation: When set to ``False``, message payloads, channel bindings and ack callbacks are NOT validated. Defaults to ``True``. @@ -157,11 +159,7 @@ def from_spec( ) """ - if isinstance(spec_path, Path): - spec = load_spec(spec_path=spec_path) - else: - raw_resolved = resolve_references(raw_spec=spec_path) - spec = AsyncApiSpec.from_dict(raw_resolved) + spec = load_spec(spec_path=spec_path) server_security: Sequence[SecurityRequirement] = [] if (