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

manage Falcon ASGI #24

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,49 @@ This router inherits from the default Falcon CompiledRouter class, so it support

Supports json files, yaml files, raw json strings, and raw yaml strings. If no params are specified the plugin will attempt to find `openapi-spec.yml` or `openapi-spec.yaml` in the current working directory (see example for structure).

Sync Example
-------------
```python

import falcon
import json
import yaml
from falcon_openapi import OpenApiSyncRouter

spec = {
'paths': {
'/foo': {
'get': {
'operationId': 'controllers.foo.Foo.on_get'
}
}
}
}

# load from file
app = falcon.API(
router=OpenApiSyncRouter(file_path='openapi-spec.yml')
)

# load from raw json
app = falcon.API(
router=OpenApiSyncRouter(raw_json=json.dumps(spec))
)

# load from raw yaml
app = falcon.API(
router=OpenApiSyncRouter(raw_yaml=yaml.dump(spec))
)
```

Async Example
-------------
```python

import falcon
import json
import yaml
from falcon_openapi import OpenApiRouter
from falcon_openapi import OpenApiAsyncRouter

spec = {
'paths': {
Expand All @@ -34,17 +72,17 @@ spec = {

# load from file
app = falcon.API(
router=OpenApiRouter(file_path='openapi-spec.yml')
router=OpenApiAsyncRouter(file_path='openapi-spec.yml')
)

# load from raw json
app = falcon.API(
router=OpenApiRouter(raw_json=json.dumps(spec))
router=OpenApiAsyncRouter(raw_json=json.dumps(spec))
)

# load from raw yaml
app = falcon.API(
router=OpenApiRouter(raw_yaml=yaml.dump(spec))
router=OpenApiAsyncRouter(raw_yaml=yaml.dump(spec))
)
```

Expand Down
4 changes: 2 additions & 2 deletions example/look/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import falcon

from falcon_openapi import OpenApiRouter
from falcon_openapi import OpenApiSyncRouter

api = application = falcon.API(router=OpenApiRouter(file_path="openapi-spec.yaml"))
api = application = falcon.API(router=OpenApiSyncRouter(file_path="openapi-spec.yaml"))

if __name__ == "__main__":
httpd = simple_server.make_server("127.0.0.1", 8000, api)
Expand Down
4 changes: 2 additions & 2 deletions falcon_openapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .router import OpenApiRouter
from .router import OpenApiSyncRouter, OpenApiAsyncRouter

__version__ = "0.5.1"
__version__ = "0.5.2"
114 changes: 76 additions & 38 deletions falcon_openapi/router.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import json
from importlib.util import module_from_spec, spec_from_file_location
from inspect import stack
from logging import getLogger
from os.path import abspath, dirname
from os.path import basename, abspath, dirname
from pathlib import Path
from urllib.parse import urlparse

Expand All @@ -16,42 +15,49 @@ def __init__(self, file_path="", raw_json="", raw_yaml=""):
super().__init__()

(self.openapi, self.base_path) = self.__load_spec(file_path, raw_json, raw_yaml)

for path, http_methods in self.openapi["paths"].items():
path = self.base_path + path
openapi_map = {}

for http_method, definition in http_methods.items():
try:
(dest_module, dest_method, dest_class, dest_file) = self.__get_destination_info(
definition, http_method
)
except:
continue

http_method = http_method.upper()
class_name = dest_module + dest_class

if class_name not in openapi_map:
mod_spec = spec_from_file_location(dest_module, dest_file)
module = module_from_spec(mod_spec)
mod_spec.loader.exec_module(module)
Class = getattr(module, dest_class)()

openapi_map[class_name] = {}
openapi_map[class_name]["class"] = Class
openapi_map[class_name]["method_map"] = {}

method_map = openapi_map[class_name]["method_map"]
Class = openapi_map[class_name]["class"]
Method = getattr(Class, dest_method)
method_map[http_method] = Method

for router_map in openapi_map.values():
Class = router_map["class"]
method_map = router_map["method_map"]
routing.set_default_responders(method_map)
self.add_route(path, Class)

def _process_http_methods(self, http_methods):
"""
Process http methods from OpenAPI sprecifications
"""
openapi_map = {}
for http_method, definition in http_methods.items():
try:
(dest_module, dest_method, dest_class, dest_file) = self.__get_destination_info(
definition, http_method
)
except:
continue

http_method = http_method.upper()
class_name = dest_module + dest_class

if class_name not in openapi_map:
mod_spec = spec_from_file_location(dest_module, dest_file)
module = module_from_spec(mod_spec)
mod_spec.loader.exec_module(module)
Class = getattr(module, dest_class)()

openapi_map[class_name] = {}
openapi_map[class_name]["class"] = Class
openapi_map[class_name]["method_map"] = {}

method_map = openapi_map[class_name]["method_map"]
Class = openapi_map[class_name]["class"]
Method = getattr(Class, dest_method)
method_map[http_method] = Method

return openapi_map

def _add_route(self, openapi_map, path, **kwargs):
"""
Add route from OpenAPI map
"""
for router_map in openapi_map.values():
Class = router_map["class"]
method_map = router_map["method_map"]
routing.set_default_responders(method_map)
self.add_route(path, Class, **kwargs)

@staticmethod
def __load_spec(file_path="", raw_json="", raw_yaml=""):
Expand Down Expand Up @@ -105,9 +111,13 @@ def __get_destination_info(definition, fallback):
# gets the file and dir of whomever instantiated this object
caller_file = abspath((stack()[2])[1])
caller_dir = dirname(caller_file) + "/"
caller_module = basename(dirname(caller_file))

if "operationId" in definition:
operationId = definition["operationId"]
if operationId[0] == '.':
operationId = caller_module + operationId
caller_dir = caller_dir[:-len(caller_module)-1]
parts = operationId.split(".")
op_method = parts.pop()
op_class = parts.pop()
Expand All @@ -130,3 +140,31 @@ def __get_destination_info(definition, fallback):
raise ValueError("No operationId or x-falcon found in definition")

return (op_module, op_method, op_class, op_file)


class OpenApiSyncRouter(OpenApiRouter):

def __init__(self, file_path="", raw_json="", raw_yaml=""):
super().__init__(file_path=file_path, raw_json=raw_json, raw_yaml=raw_yaml)

for path, http_methods in self.openapi["paths"].items():
path = self.base_path + path
openapi_map = self._process_http_methods(http_methods)
self._add_route(openapi_map, path)


class OpenApiAsyncRouter(OpenApiRouter):

def __init__(self, file_path="", raw_json="", raw_yaml=""):
super().__init__(file_path=file_path, raw_json=raw_json, raw_yaml=raw_yaml)

for path, http_methods in self.openapi["paths"].items():
path = self.base_path + path
openapi_map = self._process_http_methods(http_methods)
# Inject an extra kwarg so that the compiled router
# will know to validate the responder methods to make sure they
# are async coroutines.
kwargs = {
'_asgi': True
}
self._add_route(openapi_map, path, **kwargs)
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
setup(
name="falcon-openapi",
python_requires=">3.5.0",
version="0.5.1",
version="0.5.2",
description="Falcon router to map openapi spec to resources",
long_description=readme,
long_description_content_type="text/markdown",
author="Sam Kleiner",
author_email="[email protected]",
author="Sam Kleiner, Adil MOUHSSINE",
author_email="[email protected], [email protected]",
license="MIT",
url="https://github.com/StoicPerlman/falcon-openapi/",
keywords=["falcon", "openapi", "api"],
Expand Down