From 06ee09940f6d85da18d5a9277b0e75af06e06ca5 Mon Sep 17 00:00:00 2001 From: androiddrew Date: Mon, 13 Nov 2017 14:09:36 -0500 Subject: [PATCH 1/5] Removed .ini configuration experiment --- config.ini | 4 ---- cookie_api/models.py | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 config.ini 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/models.py b/cookie_api/models.py index c04cd8c..3a2823d 100644 --- a/cookie_api/models.py +++ b/cookie_api/models.py @@ -8,11 +8,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): From ed324ec4e379730cb09414e0adf2a1bca694cfe6 Mon Sep 17 00:00:00 2001 From: androiddrew Date: Mon, 13 Nov 2017 15:24:11 -0500 Subject: [PATCH 2/5] Revert "Added Marshmallow validation to views" This reverts commit 4f3ee63 --- cookie_api/app.py | 53 +++++++++++++------------------------------- cookie_api/auth.py | 2 +- cookie_api/schema.py | 27 ++++++---------------- 3 files changed, 23 insertions(+), 59 deletions(-) diff --git a/cookie_api/app.py b/cookie_api/app.py index 0b9a768..be1d43c 100644 --- a/cookie_api/app.py +++ b/cookie_api/app.py @@ -13,7 +13,6 @@ from cookie_api.schema import CookieSchema cookie_schema = CookieSchema() - @annotate(authentication=[JWTAuthentication()]) def get_state(injector: Injector, auth: Auth): state = injector.state @@ -24,68 +23,46 @@ def get_state(injector: Injector, auth: Auth): def get_cookies(session: Session): - """Retrieves all Cookies""" 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"} + 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) ] diff --git a/cookie_api/auth.py b/cookie_api/auth.py index 672921f..5be12a7 100644 --- a/cookie_api/auth.py +++ b/cookie_api/auth.py @@ -14,7 +14,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/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) From 02330ae34881365f3ab86091f286863c23603789 Mon Sep 17 00:00:00 2001 From: androiddrew Date: Mon, 13 Nov 2017 15:26:04 -0500 Subject: [PATCH 3/5] removing .ini config --- config.ini | 4 ---- cookie_api/models.py | 8 +------- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 config.ini 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/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): From 04255085614a7af4759831555b307f0922bb488d Mon Sep 17 00:00:00 2001 From: androiddrew Date: Tue, 14 Nov 2017 19:17:50 -0500 Subject: [PATCH 4/5] Added logging to application --- cookie_api/app.py | 18 ++++++++++++++++-- cookie_api/logging.py | 16 ++++++++++++++++ wsgi.py | 4 ++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 cookie_api/logging.py diff --git a/cookie_api/app.py b/cookie_api/app.py index be1d43c..86252e6 100644 --- a/cookie_api/app.py +++ b/cookie_api/app.py @@ -5,14 +5,18 @@ 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): state = injector.state @@ -23,6 +27,7 @@ def get_state(injector: Injector, auth: Auth): def get_cookies(session: Session): + logger.info("Accessing the Cookies resource") cookies = session.query(Cookie).all() return cookie_schema.dump(cookies, many=True).data @@ -30,6 +35,7 @@ def get_cookies(session: Session): def get_cookie(session: Session, id): cookie = session.query(Cookie).filter_by(id=id).one_or_none() if cookie is None: + 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 @@ -67,7 +73,11 @@ routes = [ Include('/static', static_urls) ] -app_settings = {} +app_settings = { + "LOGGING": { + "LEVEL": "DEBUG" + } +} routes = routes + auth_routes @@ -78,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/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/wsgi.py b/wsgi.py index 0110410..32014bf 100644 --- a/wsgi.py +++ b/wsgi.py @@ -10,6 +10,10 @@ settings = { 'RENDERERS': [JSONRenderer()], 'JWT': { 'SECRET': 'thisisasecret' + }, + 'LOGGING': { + "LOG_FILE": 'mylogfile.log', + "LEVEL": "DEBUG" } } From 6db00d5df6f135528c6038cfa4743081f013213d Mon Sep 17 00:00:00 2001 From: androiddrew Date: Tue, 26 Dec 2017 19:46:31 -0500 Subject: [PATCH 5/5] Added support for pypy Added support for pypy by changing the wsgi.py to import pyscopg2cffi if pyscopg2 is not available. Added missing dependencies for to the requirements.txt file. Added a pypyenv/ to the .gitignore. --- .gitignore | 1 + requirements.txt | 2 ++ wsgi.py | 7 +++++++ 3 files changed, 10 insertions(+) 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/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 32014bf..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