Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow pre loaded AsyncAPISpec in from_spec #160

Merged
merged 8 commits into from
Jan 19, 2022
6 changes: 4 additions & 2 deletions asynction/mock_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -138,7 +139,7 @@ def __init__(
@classmethod
def from_spec(
cls,
spec_path: Path,
spec_path: Union[Path, JSONMapping],
validation: bool = True,
server_name: Optional[str] = None,
docs: bool = True,
Expand All @@ -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 JSONMapping object.
:param validation: When set to ``False``, message payloads, channel
bindings and ack callbacks are NOT validated.
Defaults to ``True``.
Expand Down
13 changes: 10 additions & 3 deletions asynction/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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, JSONMapping],
validation: bool = True,
server_name: Optional[str] = None,
docs: bool = True,
Expand All @@ -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 JSONMapping object.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:param spec_path: The path where the AsyncAPI YAML specification is located,
or a pre loaded JSONMapping object.
:param spec_path: The path where the AsyncAPI YAML specification is located,
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``.
Expand Down Expand Up @@ -155,7 +157,12 @@ def from_spec(
)

"""
spec = load_spec(spec_path=spec_path)
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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be neater if we could move this logic within the existing load_spec function:

def load_spec(raw_spec: Union[Path, JSONMapping]) -> AsyncApiSpec:
    if isinstance(spec, Path): 
        with open(spec_path) as f:
            serialized = f.read()
            raw_spec = yaml.safe_load(serialized)

    raw_resolved = resolve_references(raw_spec)
    return AsyncApiSpec.from_dict(raw_resolved)

Note that here, in the load_spec method, we can change the argument name from spec_path to raw_spec since load_spec is not part of Asynction's "public" API.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above line in the from_spec method would then become:

Suggested change
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(raw_spec=spec_path)


server_security: Sequence[SecurityRequirement] = []
if (
server_name is not None
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/test_mock_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import jsonschema
import pytest
import yaml
from faker import Faker
from flask.app import Flask
from flask_socketio import SocketIO
Expand Down Expand Up @@ -140,6 +141,14 @@ 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):
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)


def new_mock_asynction_socket_io(
spec: AsyncApiSpec,
app: Optional[Flask] = None,
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest import mock

import pytest
import yaml
from faker import Faker
from flask import Flask

Expand Down Expand Up @@ -50,6 +51,13 @@ def test_asynction_socketio_from_spec(fixture_paths: FixturePaths):
assert isinstance(asio, AsynctionSocketIO)


def test_asynction_socketio_from_spec_object(fixture_paths: FixturePaths):
with open(fixture_paths.simple, "r") as simple:
spec = yaml.safe_load(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,
):
Expand Down