diff --git a/.gitignore b/.gitignore index cbf8643..68efbfc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ __pycache__/ # Distribution / packaging .Python env/ +pypyenv/ build/ develop-eggs/ dist/ diff --git a/config.ini b/config.ini deleted file mode 100644 index e345996..0000000 --- a/config.ini +++ /dev/null @@ -1,4 +0,0 @@ -# config.ini - -[user] -BCRYPT_LOG_ROUNDS : 13 \ No newline at end of file diff --git a/cookie_api/app.py b/cookie_api/app.py index 0b9a768..86252e6 100644 --- a/cookie_api/app.py +++ b/cookie_api/app.py @@ -5,14 +5,17 @@ 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 +import logbook from cookie_api.auth import auth_routes, auth_components from cookie_api.commands import commands from cookie_api.models import Cookie from cookie_api.schema import CookieSchema +from cookie_api import logging cookie_schema = CookieSchema() +logger = logbook.Logger('Cookies') @annotate(authentication=[JWTAuthentication()]) def get_state(injector: Injector, auth: Auth): @@ -24,73 +27,57 @@ def get_state(injector: Injector, auth: Auth): def get_cookies(session: Session): - """Retrieves all Cookies""" + logger.info("Accessing the Cookies resource") cookies = session.query(Cookie).all() return cookie_schema.dump(cookies, many=True).data -def create_cookie(session: Session, json_data: http.RequestData, route: Router): - """Create a Cookie""" - cookie_data, errors = cookie_schema.load(json_data) - if errors: - msg = {"message": "400 Bad Request", "error": errors} - return http.Response(msg, status=400) - cookie = Cookie(**cookie_data) - session.add(cookie) - session.flush() - headers = {'Location': route.reverse_url('get_cookie', dict(id=cookie.id))} - return http.Response(cookie_schema.dump(cookie).data, status=201, headers=headers) - - def get_cookie(session: Session, id): - """Retrieve Cookie by id""" cookie = session.query(Cookie).filter_by(id=id).one_or_none() if cookie is None: - msg = {"message": "404 Not Found"} + logger.warn("Someone keeps requesting bad cookie locations") + msg = {"error": "404 Not Found"} return http.Response(msg, status=404) return cookie_schema.dump(cookie).data -def patch_cookie(session: Session, json_data: http.RequestData, id: int): - """Update Cookie through partial update""" - cookie = session.query(Cookie).filter_by(id=id).one_or_none() - if cookie is None: - msg = {"message": "404 Not Found"} - return http.Response(msg, status=404) - # run schema validation of fields - cookie_data, errors = cookie_schema.load(json_data, partial=True) - if errors: - msg = {"message": "400 Bad Request", "error": errors} - return http.Response(msg, status=400) - - for k, v in cookie_data.items(): - setattr(cookie, k, v) - session.flush() - return cookie_schema.dump(cookie).data +def create_cookie(session: Session, json_data: http.RequestData, route: Router): + cookie_data = cookie_schema.load(json_data) + + #cookie = Cookie(name=json_data['name'], + # recipe_url=json_data['recipe_url'], + # sku=json_data['sku'], + # qoh=json_data['qoh'], + # unit_cost=json_data['unit_cost']) + cookie = Cookie(**cookie_data) + session.add(cookie) + session.commit() + headers = {'Location': route.reverse_url('get_cookie', dict(id=cookie.id))} + return http.Response(cookie_schema.dump(cookie), status=201, headers=headers) def delete_cookie(session: Session, id: int): - """Delete a Cookie""" cookie = session.query(Cookie).filter_by(id=id).one_or_none() if cookie is None: - msg = {"message": "404 Not Found"} + msg = {"error": "404 Not Found"} return http.Response(msg, status=404) session.delete(cookie) return {"message": "200 OK"} - routes = [ Route('/state', 'GET', get_state), Route('/cookies', 'GET', get_cookies), Route('/cookies', 'POST', create_cookie), Route('/cookies/{id}', 'GET', get_cookie), - Route('/cookies/{id}', 'PATCH', patch_cookie), - Route('/cookies/{id}', 'DELETE', delete_cookie), Include('/docs', docs_urls), Include('/static', static_urls) ] -app_settings = {} +app_settings = { + "LOGGING": { + "LEVEL": "DEBUG" + } +} routes = routes + auth_routes @@ -101,7 +88,11 @@ components = sqlalchemy_backend.components + auth_components def application_factory(settings={}): """Returns an instance of Cookie API""" - return App(settings={**app_settings, **settings}, + _settings = {**app_settings, **settings} + + logging.global_init(_settings) + + return App(settings=_settings, commands=commands, components=components, routes=routes) diff --git a/cookie_api/auth.py b/cookie_api/auth.py index cdf1174..8c21a9c 100644 --- a/cookie_api/auth.py +++ b/cookie_api/auth.py @@ -15,7 +15,7 @@ from cookie_api.models import User auth_components = [ Component(JWT, init=get_jwt) ] - +[] # /auth/login def login(settings: Settings, json_data: http.RequestData, session: Session): diff --git a/cookie_api/logging.py b/cookie_api/logging.py new file mode 100644 index 0000000..2da5848 --- /dev/null +++ b/cookie_api/logging.py @@ -0,0 +1,16 @@ +import sys +import logbook + + +def global_init(settings={}): + _logging_setting = settings.get("LOGGING", {"LEVEL": logbook.TRACE}) + + _log_file = _logging_setting.get("LOG_FILE") + _level = _logging_setting.get("LEVEL") + + if _log_file is not None: + logbook.TimedRotatingFileHandler(_log_file, level=_level).push_application() + else: + logbook.StreamHandler(sys.stdout, level=_level).push_application() + + diff --git a/cookie_api/models.py b/cookie_api/models.py index c04cd8c..4a2df8c 100644 --- a/cookie_api/models.py +++ b/cookie_api/models.py @@ -1,5 +1,3 @@ -from configparser import ConfigParser - import bcrypt from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean, Numeric @@ -8,11 +6,7 @@ from sqlalchemy.sql import expression from sqlalchemy.ext.compiler import compiles from sqlalchemy.types import DateTime as DateTimeType -cfg = ConfigParser() -cfg.read('config.ini') - -BCRYPT_LOG_ROUNDS = int(cfg.get('user', 'BCRYPT_LOG_ROUNDS')) - +BCRYPT_LOG_ROUNDS = 13 # can be moved to models util? class utcnow(expression.FunctionElement): diff --git a/cookie_api/schema.py b/cookie_api/schema.py index c6713a1..87c82e2 100644 --- a/cookie_api/schema.py +++ b/cookie_api/schema.py @@ -1,25 +1,12 @@ -from marshmallow import Schema, fields, ValidationError - - -def non_neg(value): - if not value >= 0: - raise ValidationError('Value cannot be negative') - - -def non_neg_non_zero(value): - if not value > 0: - raise ValidationError('Value cannot be negative or zero') +from marshmallow import Schema, fields class CookieSchema(Schema): - id = fields.Int(dump_only=True) - created_date = fields.DateTime(dump_only=True) + id = fields.Int() + created_date = fields.DateTime() modified_date = fields.DateTime() - name = fields.Str(required=True, error_messages={'required': "Cookie name is required"}) + name = fields.Str(required=True) recipe_url = fields.Str() - sku = fields.Str(required=True, error_messages={'required': "Cookie sku is required"}) - qoh = fields.Int(validate=non_neg, required=True, error_messages={'required': "Cookie qoh is required"}) - unit_cost = fields.Decimal(validate=non_neg_non_zero, required=True, error_messages={'required': "Cookie unit_cost is required"}) - - class Meta: - ordered = True + sku = fields.Str(required=True) + qoh = fields.Int(required=True) + unit_cost = fields.Decimal(required=True) diff --git a/requirements.txt b/requirements.txt index cc94d7c..e900d54 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,9 @@ coreschema==0.0.4 idna==2.6 itypes==1.1.0 Jinja2==2.9.6 +Logbook==1.1.0 MarkupSafe==1.0 +marshmallow==2.15.0 psycopg2==2.7.3.1 py==1.4.34 pycparser==2.18 diff --git a/wsgi.py b/wsgi.py index 0110410..7a7bf74 100644 --- a/wsgi.py +++ b/wsgi.py @@ -1,3 +1,10 @@ +try: + import psycopg2 +except ImportError: + # Fall back to psycopg2cffi + from psycopg2cffi import compat + compat.register() + import os import sys from cookie_api import JSONRenderer, Base, application_factory @@ -10,6 +17,10 @@ settings = { 'RENDERERS': [JSONRenderer()], 'JWT': { 'SECRET': 'thisisasecret' + }, + 'LOGGING': { + "LOG_FILE": 'mylogfile.log', + "LEVEL": "DEBUG" } }