Skip to content
This repository has been archived by the owner on Oct 27, 2019. It is now read-only.

Commit

Permalink
refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
lsbardel committed Apr 4, 2017
1 parent f646a13 commit 9279c77
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 333 deletions.
1 change: 1 addition & 0 deletions lux/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
'route',
'json_message',
'RedirectRouter',
'WebFormRouter',
'LuxContext',
'JSON_CONTENT_TYPES',
'DEFAULT_CONTENT_TYPES',
Expand Down
3 changes: 2 additions & 1 deletion lux/ext/rest/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from .apis import Apis, Api
from .rest import RestRoot, RestRouter, CRUD
from .pagination import Pagination, GithubPagination
from .route import route, api_parameters
from .openapi import route, api_parameters


__all__ = [
'Apis',
Expand Down
17 changes: 13 additions & 4 deletions lux/ext/rest/api/apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

from apispec import APISpec

from .schema import api_schema
from .rest import RestRoot, RestRouter, Rest404
from .spec import Specification
from .openapi import rule2openapi, api_operations, api_schema, Specification
from .cors import cors


LOCAL_API_LOGGER = logging.getLogger('lux.local.api')
Expand Down Expand Up @@ -122,7 +122,7 @@ def get(self, path=None):
def add_child(self, router):
parent = self.get(router.route)
if parent:
parent.router.add_child(router)
parent.add_child(router)


class Api:
Expand All @@ -131,12 +131,14 @@ class Api:
def __init__(self, app, name, spec, spec_path, jwt=None, cors=True):
if name == '*':
name = ''
self.app = app
self.spec = spec
self.route = Route('%s/<path:path>' % name)
self.jwt = jwt
self.cors = cors
self.router = RestRoot(spec.options['basePath'])
self.router.add_child(Specification(spec_path))
if spec_path:
self.router.add_child(Specification(spec_path, api=self))

@classmethod
def from_cfg(cls, app, cfg):
Expand Down Expand Up @@ -172,6 +174,12 @@ def netloc(self):
def match(self, path):
return self.route.match(path)

def add_child(self, router):
if self.cors:
cors(self, router)
path = rule2openapi(router.route.rule)
self.spec.add_path(path, api_operations(self, router))

def url(self, request, path=None):
urlp = list(self.urlp)
if path:
Expand All @@ -181,3 +189,4 @@ def url(self, request, path=None):
urlp[0] = r_url.scheme
urlp[1] = r_url.netloc
return urlunparse(urlp)

4 changes: 4 additions & 0 deletions lux/ext/rest/api/cors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@


def cors(api, router):
pass
187 changes: 141 additions & 46 deletions lux/ext/rest/api/openapi.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,166 @@
import re
from urllib.parse import urljoin

from pulsar.apps.wsgi import Router
from apispec import APISpec
from apispec.utils import load_yaml_from_docstring

from apispec import Path, APISpec
from apispec import utils
from marshmallow import Schema, fields

from .schema import api_schema
from lux.core import JsonRouter

from pulsar.apps import wsgi


default_plugins = ['apispec.ext.marshmallow']
METHODS = ['head', 'get', 'post', 'put', 'patch', 'delete']

RE_URL = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')

class APISchema(Schema):
BASE_URL = fields.String(required=True)
TITLE = fields.String(required=True)
VERSION = fields.String(default='0.1.0')
SPEC_PLUGINS = fields.List(fields.String(), default=default_plugins)
PRODUCES = fields.List(fields.String(), default=['application/json'])
SPEC_PATH = fields.String(default='spec')
MODEL = fields.String(default='*')
CORS = fields.Boolean(default=True)

def register_api_spec(app, cfg):
schema = api_schema.load(cfg)
title = cfg.get('TITLE')
if title:
spec = APISpec(title,
version=cfg.get('VERSION'),
plugins=cfg.get('SPEC_PLUGINS'))
spec = APISpec(cfg.get('TITLE'),
version=cfg.get('VERSION'),
plugins=cfg.get('SPEC_PLUGINS'))
spec.app = app

app.api_spec = spec
api_schema = APISchema()
RE_URL = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')


def api_operations(api, router):
"""Get all API operations"""
operations = {}
for method in METHODS:
handle = getattr(router, method, None)
if not hasattr(handle, '__call__'):
continue

def setup(spec):
"""Setup for the plugin."""
spec.register_path_helper(path_from_view)
doc = load_yaml_from_docstring(handle.__doc__)
parameters = getattr(handle, 'parameters', None)
if parameters:
doc = parameters.add_to(api, doc)
if doc:
operations[method] = doc

return operations

def pulsarpath2openapi(path):

def rule2openapi(path):
"""Convert a Flask URL rule to an OpenAPI-compliant path.
:param str path: Flask path template.
"""
return RE_URL.sub(r'{\1}', path)


def path_from_view(spec, view, **kwargs):
"""Path helper that allows passing a pulsar Routers"""
if not isinstance(view, Router):
return
path = pulsarpath2openapi(view.rule)
app = spec.app
app_root = app.config['API_URL'] or '/'
path = urljoin(app_root.rstrip('/') + '/', path.lstrip('/'))
operations = utils.load_operations_from_docstring(view.__doc__)
path = Path(path=path, operations=operations)
return path

class route(wsgi.route):

class Response:
def __init__(self, *args, responses=None, **kwargs):
super().__init__(*args, **kwargs)
self.api = api_parameters(responses=responses)

def __init__(self, status_code, doc):
self.status_code = status_code
self.doc = doc
def __call__(self, method):
method = super().__call__(method)
return self.api(method)


class apidoc:
response = Response

def __init__(self, doc=None, responses=None):
self.doc = doc or ''
class api_parameters:
"""Inject api parameters to an endpoint handler
"""
def __init__(self, form=None, path=None, query=None, body=None,
responses=None):
self.form = form
self.path = path
self.query = query
self.body = body
self.responses = responses

def __call__(self, handler):
handler.__openapi__ = self
return handler
def __call__(self, f):
f.parameters = self
return f

def add_to(self, api, doc):
doc = doc if doc is not None else {}
parameters = doc.get('parameters', [])
processed = set()
if self.path:
self._extend(api, self.path, parameters, 'path', processed)
if self.query:
self._extend(api, self.query, parameters, 'query', processed)
if self.form:
self._extend(api, self.form, parameters, 'formData', processed)
if isinstance(self.body, as_body):
obj = self.body(api.spec)
processed.add(obj['name'])
parameters.append(obj)
if parameters:
doc['parameters'] = parameters
return doc

def _extend(self, api, schema, parameters, loc, processed):
spec = self._spec(api)
spec.definition('param', schema=schema)
params = spec.to_dict()['definitions']['param']
properties = params['properties']
required = set(params.get('required') or ())
for name, obj in properties.items():
if name in processed:
LOG.error('Parameter "%s" already in api path parameter list',
name)
continue
processed.add(name)
obj['name'] = name
obj['in'] = loc
if name in required:
obj['required'] = True
parameters.append(obj)

def _spec(self, api):
return APISpec('', '', plugins=list(api.spec.plugins))

def _as_body(self, definition, parameters):
if not isinstance(definition, dict):
body = dict(schema={"$ref": "#/definitions/%s" % definition})
if 'name' not in body:
body['name'] = 'body'
body['in'] = 'body'
body['required'] = True
parameters.add(body['name'])
return body


class as_body:

def __init__(self, schema, **obj):
self.obj = obj
self.schema_cls = schema if isinstance(schema, type) else type(schema)

def __call__(self, spec):
definition = None
plg = spec.plugins.get('apispec.ext.marshmallow')
if plg and 'refs' in plg:
definition = plg['refs'].get(self.schema_cls)
if not definition:
LOG.warning('Could not add body parameter %s' % self.schema_cls)
else:
obj = self.obj.copy()
obj['schema'] = {"$ref": "#/definitions/%s" % definition}
obj['in'] = 'body'
if 'name' not in obj:
obj['name'] = definition.lower()
return obj


class Specification(JsonRouter):
api = None

def get(self, request):
if self.api:
pass
spec = self.api.spec.to_dict()
if 'host' not in spec:
spec['host'] = request.get_host()
if 'schemes' not in spec:
spec['schemes'] = [request.scheme]
return request.json_response(spec)
99 changes: 0 additions & 99 deletions lux/ext/rest/api/route.py

This file was deleted.

0 comments on commit 9279c77

Please sign in to comment.