Skip to content

Commit

Permalink
Feature/support swagger (faust-streaming#206)
Browse files Browse the repository at this point in the history
* Duplicate table_route concept to support topic_route for external topics

* Fix router types

* fix topic_route formatting and add unitests

* Fix copy paste issue with table_route test

* Add support for Swagger

* Add unit test for view get_methods

* Add swagger docs to faust built in endpoints

* Always register HTTP route for HEAD

* rebase back to master

* rebase back to master

* Add CORS routes only for available methods

* Fix tests for swagger support

* Fix order of test operations

Co-authored-by: Ran Katzir <ran.katzir@valerann.com>
  • Loading branch information
ran-ka and ran-ka authored Oct 26, 2021
1 parent 7600f45 commit 5ad8222
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 16 deletions.
9 changes: 8 additions & 1 deletion faust/web/apps/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@

@blueprint.route("/", name="detail")
class Graph(web.View):
"""Render image from graph of running services."""
"""
---
description: Render image from graph of running services.
tags:
- Faust
produces:
- application/json
"""

async def get(self, request: web.Request) -> web.Response:
"""Draw image of the services running in this worker."""
Expand Down
41 changes: 38 additions & 3 deletions faust/web/apps/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@

@blueprint.route("/", name="list")
class TableList(web.View):
"""List routes for all tables."""
"""
---
description: List routes for all tables.
tags:
- Faust
produces:
- application/json
"""

async def get(self, request: web.Request) -> web.Response:
"""Return JSON response with list of all table routes."""
Expand All @@ -19,7 +26,19 @@ async def get(self, request: web.Request) -> web.Response:

@blueprint.route("/{name}/", name="detail")
class TableDetail(web.View):
"""List route for specific table."""
"""
---
description: List route for a specific table.
tags:
- Faust
parameters:
- in: path
name: name
type: string
required: true
produces:
- application/json
"""

async def get(self, request: web.Request, name: str) -> web.Response:
"""Return JSON response with table metadata."""
Expand All @@ -29,7 +48,23 @@ async def get(self, request: web.Request, name: str) -> web.Response:

@blueprint.route("/{name}/{key}/", name="key-detail")
class TableKeyDetail(web.View):
"""List information about key."""
"""
---
description: List information about key.
tags:
- Faust
parameters:
- in: path
name: name
type: string
required: true
- in: path
name: key
type: string
required: true
produces:
- application/json
"""

async def get(self, request: web.Request, name: str, key: str) -> web.Response:
"""Return JSON response after looking up the route of a table key.
Expand Down
18 changes: 16 additions & 2 deletions faust/web/apps/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@

@blueprint.route("/", name="index")
class Stats(web.View):
"""Monitor statistics."""
"""
---
description: Monitor statistics.
tags:
- Faust
produces:
- application/json
"""

async def get(self, request: web.Request) -> web.Response:
"""Return JSON response with sensor information."""
Expand All @@ -26,7 +33,14 @@ async def get(self, request: web.Request) -> web.Response:

@blueprint.route("/assignment/", name="assignment")
class Assignment(web.View):
"""Cluster assignment information."""
"""
---
description: Cluster assignment information.
tags:
- Faust
produces:
- application/json
"""

@classmethod
def _topic_grouped(cls, assignment: Set[TP]) -> TPMap:
Expand Down
41 changes: 38 additions & 3 deletions faust/web/apps/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@ def get_table_value_or_404(self, table: TableT, key: K) -> V:

@blueprint.route("/", name="list")
class TableList(TableView):
"""List available table names."""
"""
---
description: List available table names.
tags:
- Faust
produces:
- application/json
"""

async def get(self, request: web.Request) -> web.Response:
"""Return JSON response with a list of available table names."""
Expand All @@ -56,7 +63,19 @@ async def get(self, request: web.Request) -> web.Response:

@blueprint.route("/{name}/", name="detail")
class TableDetail(TableView):
"""Get details for table by name."""
"""
---
description: Get details for table by name.
tags:
- Faust
parameters:
- in: path
name: name
type: string
required: true
produces:
- application/json
"""

async def get(self, request: web.Request, name: str) -> web.Response:
"""Return JSON response with table information."""
Expand All @@ -66,7 +85,23 @@ async def get(self, request: web.Request, name: str) -> web.Response:

@blueprint.route("/{name}/{key}/", name="key-detail")
class TableKeyDetail(TableView):
"""List information about key."""
"""
---
description: List information about key.
tags:
- Faust
parameters:
- in: path
name: name
type: string
required: true
- in: path
name: key
type: string
required: true
produces:
- application/json
"""

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
Expand Down
10 changes: 5 additions & 5 deletions faust/web/drivers/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,14 @@ def route(
cors_options: Mapping[str, ResourceOptions] = None,
) -> None:
"""Add route for web view or handler."""
async_handler = self._wrap_into_asyncdef(handler)
if cors_options or self.cors_options:
async_handler = self._wrap_into_asyncdef(handler)
for method in NON_OPTIONS_METHODS:
for method in NON_OPTIONS_METHODS & handler.get_methods():
r = self.web_app.router.add_route(method, pattern, async_handler)
self.cors.add(r, _prepare_cors_options(cors_options or {}))
else:
self.web_app.router.add_route(
"*", pattern, self._wrap_into_asyncdef(handler)
)
for method in handler.get_methods():
self.web_app.router.add_route(method, pattern, async_handler)

def _wrap_into_asyncdef(self, handler: Callable) -> Callable:
# get rid of pesky "DeprecationWarning: Bare functions are
Expand All @@ -246,6 +245,7 @@ def _wrap_into_asyncdef(self, handler: Callable) -> Callable:
async def _dispatch(request: base.Request) -> base.Response:
return await handler(request)

_dispatch.__doc__ = handler.__doc__
return _dispatch

def add_static(self, prefix: str, path: Union[Path, str], **kwargs: Any) -> None:
Expand Down
20 changes: 20 additions & 0 deletions faust/web/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Mapping,
MutableMapping,
Optional,
Set,
Type,
Union,
cast,
Expand Down Expand Up @@ -71,12 +72,31 @@ def __init__(self, app: AppT, web: Web) -> None:
"options": self.options,
"search": self.search,
}

self.__post_init__()

def __post_init__(self) -> None:
"""Override this to add custom initialization to your view."""
...

def get_methods(self) -> Set:
"""Return the supported methods for this view"""
methods = set({"HEAD"})
base_methods = {
"head": View.head,
"get": View.get,
"post": View.post,
"patch": View.patch,
"delete": View.delete,
"put": View.put,
"options": View.options,
"search": View.search,
}
for method_name, method in self.methods.items():
if method.__code__ is not base_methods[method_name].__code__:
methods.add(method_name.upper())
return methods

async def __call__(self, request: Any) -> Any:
"""Perform HTTP request."""
return await self.dispatch(request)
Expand Down
9 changes: 7 additions & 2 deletions tests/unit/web/drivers/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,9 @@ def test_add_static(self, *, web):

def test_route__with_cors_options(self, *, web):
handler = Mock()
handler.get_methods = Mock(
name="get_methods", return_value=set({"GET", "PUT", "POST", "DELETE"})
)
cors_options = {
"http://example.com": ResourceOptions(
allow_credentials=True,
Expand All @@ -311,15 +314,17 @@ def test_route__with_cors_options(self, *, web):
)

web.web_app.router.add_route.assert_has_calls(
[call(method, "/foo/", ANY) for method in NON_OPTIONS_METHODS]
[call(method, "/foo/", ANY) for method in NON_OPTIONS_METHODS],
any_order=True,
)
web._cors.add.assert_has_calls(
[
call(
web.web_app.router.add_route(), _prepare_cors_options(cors_options)
)
for _ in NON_OPTIONS_METHODS
]
],
any_order=True,
)

def test__create_site(self, *, web, app):
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/web/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ def test_init(self, *, app, web, view):
"search": view.search,
}

def test_get_methods(self, view):
assert view.get_methods() == set({"GET", "HEAD"})

@pytest.mark.parametrize(
"method",
[
Expand Down

0 comments on commit 5ad8222

Please sign in to comment.