leveled up, minus auth

deb
androiddrew 7 years ago
parent 7b23ad97d8
commit d0a9b556c6

@ -1,3 +1 @@
from cookie_api.renders import JSONRenderer from cookie_api.app import application_factory
from cookie_api.app import application_factory
from cookie_api.models import Base

@ -1,61 +1,40 @@
import typing import typing
from apistar import Include, Route, http, annotate from apistar import Route, http, App
from apistar.backends import sqlalchemy_backend from apistar_jwt import JWT
from apistar.backends.sqlalchemy_backend import Session
from apistar.frameworks.wsgi import WSGIApp as App
from apistar.handlers import docs_urls, static_urls
from apistar.interfaces import Router, Injector, Auth
from apistar_jwt.authentication import JWTAuthentication
from apistar_alembic_migrations import commands as migrate_commands
from apistar_mail import mail_component
import logbook import logbook
from sqlalchemy import create_engine
from cookie_api.logger import global_init
from cookie_api.auth import auth_routes, auth_components
from cookie_api.commands import commands
from cookie_api.models import Cookie from cookie_api.models import Cookie
#from cookie_api.schema import CookieSchema
from cookie_api.schema import CookieSchema from cookie_api.schema import CookieSchema
from cookie_api import logging from cookie_api.util import SQLAlchemyHook, SQLAlchemySession, Session, ExtJSONResponse
cookie_schema = CookieSchema() engine = create_engine('postgresql://apistar@localhost:5432/apistar')
logger = logbook.Logger('Cookies') logger = logbook.Logger('Cookies')
@annotate(authentication=[JWTAuthentication()])
def get_state(injector: Injector, auth: Auth):
state = injector.state
d = dict()
for k, v in state.items():
d[k] = str(v)
return d
def get_cookies(session: Session) -> typing.List[CookieSchema]: def get_cookies(session: Session) -> typing.List[CookieSchema]:
logger.info("Accessing the Cookies resource")
cookies = session.query(Cookie).all() cookies = session.query(Cookie).all()
return [CookieSchema(cookie) for cookie in cookies] return ExtJSONResponse([CookieSchema(cookie) for cookie in cookies], 200)
def get_cookie(session: Session, id) -> CookieSchema: def get_cookie(session: Session, id) -> CookieSchema:
cookie = session.query(Cookie).filter_by(id=id).one_or_none() cookie = session.query(Cookie).filter_by(id=id).one_or_none()
if cookie is None: if cookie is None:
logger.warn("Someone keeps requesting bad cookie locations")
msg = {"error": "404 Not Found"} msg = {"error": "404 Not Found"}
return http.Response(msg, status=404) return ExtJSONResponse(msg, 404)
return CookieSchema(cookie) return ExtJSONResponse(CookieSchema(cookie), 200)
def create_cookie(session: Session, json_data: http.RequestData, route: Router): def create_cookie(session: Session, cookie_data: CookieSchema, app: App):
cookie_data = cookie_schema.load(json_data)
cookie = Cookie(**cookie_data) cookie = Cookie(**cookie_data)
session.add(cookie) session.add(cookie)
session.commit() session.commit()
headers = {'Location': route.reverse_url('get_cookie', dict(id=cookie.id))} headers = {'Location': app.reverse_url('get_cookie', id=cookie.id)}
return http.Response(cookie_schema.dump(cookie), status=201, headers=headers) return ExtJSONResponse(CookieSchema(cookie), 201, headers=headers)
def delete_cookie(session: Session, id: int): def delete_cookie(session: Session, id: int):
@ -63,17 +42,18 @@ def delete_cookie(session: Session, id: int):
if cookie is None: if cookie is None:
msg = {"error": "404 Not Found"} msg = {"error": "404 Not Found"}
return http.Response(msg, status=404) return http.Response(msg, status=404)
logger.debug("Deleting cookie {} {}".format(cookie.id, cookie.name))
session.delete(cookie) session.delete(cookie)
return {"message": "200 OK"} session.commit()
return ExtJSONResponse({}, 204)
_routes = [ _routes = [
Route('/state', 'GET', get_state),
Route('/cookies', 'GET', get_cookies), Route('/cookies', 'GET', get_cookies),
Route('/cookies', 'POST', create_cookie), Route('/cookies', 'POST', create_cookie),
Route('/cookies/{id}', 'GET', get_cookie), Route('/cookies/{id}', 'GET', get_cookie),
Include('/docs', docs_urls), Route('/cookies/{id}', 'DELETE', delete_cookie)
Include('/static', static_urls)
] ]
app_settings = { app_settings = {
@ -82,21 +62,24 @@ app_settings = {
} }
} }
_routes = _routes + auth_routes _routes = _routes # + auth_routes
_commands = sqlalchemy_backend.commands + commands + migrate_commands _hooks = [SQLAlchemyHook]
_components = [
_components = sqlalchemy_backend.components + auth_components + [mail_component] SQLAlchemySession(engine=engine),
JWT({
'JWT_SECRET': 'thisisasecret',
}),
]
def application_factory(settings={}, routes=_routes, commands=_commands, components=_components): def application_factory(settings={}, routes=_routes, components=_components, hooks=_hooks):
"""Returns an instance of Cookie API""" """Returns an instance of Cookie API"""
_settings = {**app_settings, **settings} _settings = {**app_settings, **settings}
logging.global_init(_settings) global_init(_settings)
return App(settings=_settings, return App(components=components,
commands=commands, event_hooks=hooks,
components=components,
routes=routes) routes=routes)

@ -1,20 +1,15 @@
"""
import datetime as dt import datetime as dt
from apistar import Component, Settings, http, Route, Include, annotate from apistar import Component, http, Route, Include
from apistar.interfaces import Auth
from apistar.backends.sqlalchemy_backend import Session
from apistar_jwt.authentication import get_jwt, JWTAuthentication
from apistar_jwt.token import JWT from apistar_jwt.token import JWT
from apistar_mail import Message, Mail # from apistar_mail import Message, Mail
from sqlalchemy.exc import IntegrityError, InvalidRequestError from sqlalchemy.exc import IntegrityError, InvalidRequestError
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
from cookie_api.models import User from cookie_api.models import User
from cookie_api.schema import UserSchema, UserCreateSchema from cookie_api.schema import
auth_components = [
Component(JWT, init=get_jwt)
]
def login(settings: Settings, json_data: http.RequestData, session: Session): def login(settings: Settings, json_data: http.RequestData, session: Session):
@ -110,3 +105,4 @@ routes = [
] ]
auth_routes = [Include('/auth', routes)] auth_routes = [Include('/auth', routes)]
"""

@ -1,16 +0,0 @@
from apistar import Command
from apistar.backends.sqlalchemy_backend import Session
from cookie_api.models import User
def create_user(session: Session, email, password):
user = User(email, password)
session.add(user)
session.commit()
print('User added')
commands = [
Command('create_user', create_user)
]

@ -13,4 +13,3 @@ def global_init(settings={}):
else: else:
logbook.StreamHandler(sys.stdout, level=_level).push_application() logbook.StreamHandler(sys.stdout, level=_level).push_application()

@ -1,25 +0,0 @@
import datetime as dt
import decimal
import json
from apistar import http
from apistar.renderers import Renderer
def extended_encoder(obj):
"""JSON encoder function with support for ISO 8601 datetime serialization and Decimal to float casting"""
if isinstance(obj, dt.datetime):
return obj.isoformat()
elif isinstance(obj, decimal.Decimal):
return float(obj)
class JSONRenderer(Renderer):
"""JSON Render with support for ISO 8601 datetime serialization and Decimal to float casting"""
media_type = 'application/json'
charset = None
def render(self, data: http.ResponseData) -> bytes:
return json.dumps(data, default=extended_encoder).encode('utf-8')
# TODO add an XML render

@ -1,48 +1,13 @@
from apistar import typesystem from apistar import types, validators
# from marshmallow import Schema, fields from cookie_api.util import Decimal
from cookie_api.types import Datetime, Decimal
class CookieSchema(types.Type):
id = validators.Integer(allow_null=True)
# class CookieSchema(Schema): created_date = validators.DateTime(allow_null=True)
# id = fields.Int() modified_date = validators.DateTime(allow_null=True)
# created_date = fields.DateTime() name = validators.String(max_length=50)
# modified_date = fields.DateTime() recipe_url = validators.String(max_length=255)
# name = fields.Str(required=True) sku = validators.String(max_length=55)
# recipe_url = fields.Str() qoh = validators.Integer(minimum=0)
# sku = fields.Str(required=True) unit_cost = Decimal(minimum=0.0)
# qoh = fields.Int(required=True)
# unit_cost = fields.Decimal(required=True)
class UserSchema(typesystem.Object):
description = 'A User respresentation'
properties = {
'id': typesystem.integer(),
'created_date': Datetime,
'modified_date': Datetime,
'email': typesystem.string(max_length=255),
'confirmed': typesystem.boolean(),
'admin': typesystem.boolean()
}
class UserCreateSchema(typesystem.Object):
description = 'A User respresentation for creating a user'
properties = {
'email': typesystem.string(max_length=255),
'password': typesystem.string(max_length=255)
}
class CookieSchema(typesystem.Object):
properties = {
'id': typesystem.integer(),
'created_date': Datetime,
'modified_date': Datetime,
'name': typesystem.string(),
'recipe_url': typesystem.string(),
'sku': typesystem.string(),
'qoh': typesystem.integer(),
'unit_cost': Decimal
}

@ -1,25 +0,0 @@
import datetime
import decimal
import dateutil
from apistar import typesystem
class Datetime(datetime.datetime):
native_type = datetime.datetime
def __new__(cls, *args, **kwargs) -> datetime:
if args and isinstance(args[0], cls.native_type):
return args[0]
if args and isinstance(args[0], str):
try:
return dateutil.parser.parse(args[0])
except ValueError:
raise typesystem.TypeSystemError(cls=cls, code='type') from None
return cls.native_type(*args, **kwargs)
class Decimal(typesystem._NumericType, decimal.Decimal):
native_type = decimal.Decimal

@ -0,0 +1,106 @@
import datetime as dt
import decimal
from math import isfinite
import typing
from apistar import types, validators
from apistar.http import JSONResponse, Response
from apistar.server.components import Component
from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker, Session, scoped_session
class Decimal(validators.NumericType):
numeric_type = decimal.Decimal
def validate(self, value, definitions=None, allow_coerce=False):
if value is None and self.allow_null:
return None
elif value is None:
self.error('null')
elif isinstance(value, bool):
self.error('type')
elif self.numeric_type is int and isinstance(value, float) and not value.is_integer():
self.error('integer')
elif not isinstance(value, (int, float, decimal.Decimal)) and not allow_coerce:
self.error('type')
elif isinstance(value, float) and not isfinite(value):
self.error('finite')
try:
value = self.numeric_type(value)
except (TypeError, ValueError):
self.error('type')
if self.enum is not None:
if value not in self.enum:
if len(self.enum) == 1:
self.error('exact')
self.error('enum')
if self.minimum is not None:
if self.exclusive_minimum:
if value <= self.minimum:
self.error('exclusive_minimum')
else:
if value < self.minimum:
self.error('minimum')
if self.maximum is not None:
if self.exclusive_maximum:
if value >= self.maximum:
self.error('exclusive_maximum')
else:
if value > self.maximum:
self.error('maximum')
if self.multiple_of is not None:
if isinstance(self.multiple_of, float):
if not (value * (1 / self.multiple_of)).is_integer():
self.error('multiple_of')
else:
if value % self.multiple_of:
self.error('multiple_of')
return value
class ExtJSONResponse(JSONResponse):
"""JSON Response with support for ISO 8601 datetime serialization and Decimal to float casting"""
def default(self, obj: typing.Any) -> typing.Any:
if isinstance(obj, types.Type):
return dict(obj)
if isinstance(obj, dt.datetime):
return obj.isoformat()
elif isinstance(obj, decimal.Decimal):
return float(obj)
error = "Object of type '%s' is not JSON serializable."
return TypeError(error % type(obj).__name_)
DBSession = scoped_session(sessionmaker())
class SQLAlchemySession(Component):
def __init__(self, engine=None):
if not isinstance(engine, Engine):
raise ValueError('SQLAlchemySession must be instantiated with a sqlalchemy.engine.Engine object')
self.engine = engine
DBSession.configure(bind=self.engine)
def resolve(self) -> Session:
return DBSession()
class SQLAlchemyHook:
def on_request(self, session: Session):
return
def on_response(self, session: Session, response: Response):
DBSession.remove()
return response
def on_error(self, session: Session, response: Response):
session.rollback()
DBSession.remove()
return response

@ -1,66 +1,23 @@
alembic==0.9.6 alembic==0.9.9
apistar==0.3.9 apistar==0.5.10
apistar-alembic-migrations==0.0.6 apistar-jwt==0.4.2
apistar-jwt==0.2.1
bcrypt==3.1.4 bcrypt==3.1.4
certifi==2017.7.27.1 certifi==2018.4.16
cffi==1.11.2 cffi==1.11.5
chardet==3.0.4 chardet==3.0.4
colorama==0.3.9
coreapi==2.3.3
coreschema==0.0.4
idna==2.6 idna==2.6
itypes==1.1.0 Jinja2==2.10
Jinja2==2.9.6 Logbook==1.3.3
Logbook==1.1.0
Mako==1.0.7 Mako==1.0.7
MarkupSafe==1.0 MarkupSafe==1.0
marshmallow==2.15.0 psycopg2==2.7.4
psycopg2==2.7.3.1
py==1.4.34
pycparser==2.18 pycparser==2.18
PyJWT==1.5.3 PyJWT==1.6.1
pytest==3.2.3 python-dateutil==2.7.2
python-dateutil==2.6.1
python-editor==1.0.3 python-editor==1.0.3
requests==2.18.4 requests==2.18.4
six==1.11.0 six==1.11.0
SQLAlchemy==1.1.14 SQLAlchemy==1.2.7
uritemplate==3.0.0
urllib3==1.22 urllib3==1.22
Werkzeug==0.12.2 Werkzeug==0.14.1
whitenoise==3.3.1
alembic==0.9.6
apistar==0.3.9
apistar-alembic-migrations==0.0.6
apistar-jwt==0.2.1
apistar-mail==0.2.0
bcrypt==3.1.4
certifi==2017.7.27.1
cffi==1.11.2
chardet==3.0.4
colorama==0.3.9
coreapi==2.3.3
coreschema==0.0.4
idna==2.6
itsdangerous==0.24
itypes==1.1.0
Jinja2==2.9.6
Logbook==1.1.0
Mako==1.0.7
MarkupSafe==1.0
marshmallow==2.15.0
psycopg2==2.7.3.1
py==1.4.34
pycparser==2.18
PyJWT==1.5.3
pytest==3.2.3
python-dateutil==2.6.1
python-editor==1.0.3
requests==2.18.4
six==1.11.0
SQLAlchemy==1.1.14
uritemplate==3.0.0
urllib3==1.22
Werkzeug==0.12.2
whitenoise==3.3.1 whitenoise==3.3.1

@ -3,12 +3,13 @@ try:
except ImportError: except ImportError:
# Fall back to psycopg2cffi # Fall back to psycopg2cffi
from psycopg2cffi import compat from psycopg2cffi import compat
compat.register() compat.register()
from cookie_api import application_factory from cookie_api import application_factory
from config import settings
app = application_factory(settings) app = application_factory()
if __name__ == "__main__": if __name__ == '__main__':
app.main() app = application_factory()
app.serve('127.0.0.1', 5000, debug=True)

Loading…
Cancel
Save