From e374bebf6095391614425b4e7ceba140dc6148f9 Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Sun, 16 Mar 2014 16:00:10 -0300 Subject: [PATCH 1/5] trying to switch from pony to sqlalchemy --- luncho/blueprints/users.py | 16 ++++++++++------ luncho/database.py | 29 ----------------------------- luncho/server.py | 37 +++++++++++++++---------------------- manage.py | 5 +++++ requirements.txt | 2 +- tests/users_tests.py | 6 +++--- 6 files changed, 34 insertions(+), 61 deletions(-) delete mode 100644 luncho/database.py diff --git a/luncho/blueprints/users.py b/luncho/blueprints/users.py index f4ff0b3..aab6410 100644 --- a/luncho/blueprints/users.py +++ b/luncho/blueprints/users.py @@ -8,7 +8,7 @@ from flask import request from flask import jsonify # from flask import current_app -from pony.orm import commit +# from pony.orm import commit from luncho.helpers import ForceJSON @@ -23,10 +23,14 @@ def create_user(): """Create a new user. Request must be: { "username": "username", "full_name": "Full Name", "password": "hash" }""" json = request.get_json(force=True) - new_user = User(username=json['username'], - fullname=json['full_name'], - passhash=json['password'], - validated=False) - commit() + # new_user = User(username=json['username'], + # fullname=json['full_name'], + # passhash=json['password'], + # validated=False) + User(username=json['username'], + fullname=json['full_name'], + passhash=json['password'], + validated=False) + # commit() return jsonify(status='OK') diff --git a/luncho/database.py b/luncho/database.py deleted file mode 100644 index 2d9dd35..0000000 --- a/luncho/database.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- - -import datetime - -from flask import current_app - -from pony.orm import Database -from pony.orm import PrimaryKey -from pony.orm import Optional -from pony.orm import Required -# from pony.orm import Set - -db = Database("sqlite", current_app.config['SQLITE_FILENAME'], create_db=True) - - -class User(db.Entity): - """Users.""" - username = PrimaryKey(unicode) - fullname = Required(unicode) - passhash = Required(unicode) - token = Optional(unicode) # 1. if the user never logged in, they will - # not have a token. - # 2. This forces the user to have a single - # login everywhere, per day. - issue_date = Optional(datetime.datetime) - validated = Required(bool, default=False) - -db.generate_mapping(create_tables=True) diff --git a/luncho/server.py b/luncho/server.py index d6dad61..9e461f3 100644 --- a/luncho/server.py +++ b/luncho/server.py @@ -2,7 +2,6 @@ # -*- encoding: utf-8 -*- import sys -import datetime import logging from flask import Flask @@ -12,7 +11,8 @@ from flask import Flask # Config # ---------------------------------------------------------------------- class Settings(object): - SQLITE_FILENAME = './luncho.db3' + SQLALCHEMY_DATABASE_URI = 'sqlite://./luncho.db3' + DEBUG = True log = logging.getLogger('luncho.server') @@ -26,29 +26,22 @@ app.config.from_envvar('LUCNHO_CONFIG', True) # ---------------------------------------------------------------------- # Database # ---------------------------------------------------------------------- -from pony.orm import db_session -from pony.orm import Database -from pony.orm import PrimaryKey -from pony.orm import Optional -from pony.orm import Required +from flask.ext.sqlalchemy import SQLAlchemy +db = SQLAlchemy(app) -db = Database("sqlite", app.config['SQLITE_FILENAME'], create_db=True) +class User(db.Model): + username = db.Column(db.String, primary_key=True) + full_name = db.Column(db.String, nullable=False) + passhash = db.Column(db.String, nullable=False) + token = db.Column(db.String) + issued_date = db.Column(db.Date) + validated = db.Column(db.Boolean, default=False) -class User(db.Entity): - """Users.""" - username = PrimaryKey(unicode) - fullname = Required(unicode) - passhash = Required(unicode) - token = Optional(unicode) # 1. if the user never logged in, they will - # not have a token. - # 2. This forces the user to have a single - # login everywhere, per day. - issue_date = Optional(datetime.datetime) - validated = Required(bool, default=False) - -db.generate_mapping(create_tables=True) -app.wsgi_app = db_session(app.wsgi_app) + def __init__(self, username, full_name, passhash): + self.username = username + self.full_name = full_name + self.passhash = passhash # ---------------------------------------------------------------------- # Blueprints diff --git a/manage.py b/manage.py index a7471d8..b64ca0c 100644 --- a/manage.py +++ b/manage.py @@ -9,6 +9,11 @@ from luncho.server import app manager = Manager(app) + +@manager.command +def create_db(): + """Create the database.""" + if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) manager.run() diff --git a/requirements.txt b/requirements.txt index d17b341..42b7a23 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ Flask flask-script -pony +flask-sqlalchemy diff --git a/tests/users_tests.py b/tests/users_tests.py index 74c29a7..68795ca 100644 --- a/tests/users_tests.py +++ b/tests/users_tests.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- +import os import tempfile import unittest import json @@ -20,9 +21,8 @@ class TestUsers(unittest.TestCase): print server.app.config['SQLITE_FILENAME'] self.app = server.app.test_client() - def tearDown(self): - # os.unlink(server.app.config['SQLITE_FILENAME']) - pass + # def tearDown(self): + # os.unlink(server.app.config['SQLITE_FILENAME']) def test_create_user(self): request = {'username': 'username', From 071bef53a29b120213d58d1a9058ec7027cd17a3 Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Sun, 16 Mar 2014 16:34:12 -0300 Subject: [PATCH 2/5] in memory tests --- luncho/server.py | 10 +++++++--- tests/users_tests.py | 9 +++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/luncho/server.py b/luncho/server.py index 9e461f3..20147dd 100644 --- a/luncho/server.py +++ b/luncho/server.py @@ -32,16 +32,20 @@ db = SQLAlchemy(app) class User(db.Model): username = db.Column(db.String, primary_key=True) - full_name = db.Column(db.String, nullable=False) + fullname = db.Column(db.String, nullable=False) passhash = db.Column(db.String, nullable=False) token = db.Column(db.String) issued_date = db.Column(db.Date) validated = db.Column(db.Boolean, default=False) - def __init__(self, username, full_name, passhash): + def __init__(self, username, fullname, passhash, token=None, + issued_date=None, validated=False): self.username = username - self.full_name = full_name + self.fullname = fullname self.passhash = passhash + self.token = token + self.issued_date = issued_date + self.validated = validated # ---------------------------------------------------------------------- # Blueprints diff --git a/tests/users_tests.py b/tests/users_tests.py index 68795ca..8e5280a 100644 --- a/tests/users_tests.py +++ b/tests/users_tests.py @@ -1,8 +1,6 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -import os -import tempfile import unittest import json @@ -13,12 +11,11 @@ class TestUsers(unittest.TestCase): """Test users request.""" def setUp(self): - (_, name) = tempfile.mkstemp() - - server.app.config['SQLITE_FILENAME'] = name + # leave the database blank to make it in memory + server.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' server.app.config['TESTING'] = True - print server.app.config['SQLITE_FILENAME'] + print server.app.config['SQLALCHEMY_DATABASE_URI'] self.app = server.app.test_client() # def tearDown(self): From 9f1753b2c851fc65a0972409ea1a3ebc57ae4ed0 Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Sun, 16 Mar 2014 16:35:58 -0300 Subject: [PATCH 3/5] in memory tests --- tests/users_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/users_tests.py b/tests/users_tests.py index 8e5280a..2e3ee66 100644 --- a/tests/users_tests.py +++ b/tests/users_tests.py @@ -28,7 +28,7 @@ class TestUsers(unittest.TestCase): rv = self.app.put('/user/', data=json.dumps(request), content_type='application/json') - print rv.data + self.assertEqual(json.loads(rv.data), {'status': 'OK'}) if __name__ == '__main__': unittest.main() From c01c39d87f7feff447e487d204393e30e879623d Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Sun, 16 Mar 2014 16:36:46 -0300 Subject: [PATCH 4/5] test status too, since it is important in the response --- tests/users_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/users_tests.py b/tests/users_tests.py index 2e3ee66..0873644 100644 --- a/tests/users_tests.py +++ b/tests/users_tests.py @@ -28,6 +28,7 @@ class TestUsers(unittest.TestCase): rv = self.app.put('/user/', data=json.dumps(request), content_type='application/json') + self.assertEqual(rv.status_code, 200) self.assertEqual(json.loads(rv.data), {'status': 'OK'}) if __name__ == '__main__': From 3c4bf90bc55e56cec95943d267b41bfa2813082e Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Sun, 16 Mar 2014 17:07:10 -0300 Subject: [PATCH 5/5] check for non-JSON requests, missing fields and duplicate usernames --- luncho/blueprints/users.py | 31 ++++++++++++++---------- luncho/helpers.py | 10 +++++--- tests/users_tests.py | 49 +++++++++++++++++++++++++++++++++++--- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/luncho/blueprints/users.py b/luncho/blueprints/users.py index aab6410..9b2b8c4 100644 --- a/luncho/blueprints/users.py +++ b/luncho/blueprints/users.py @@ -6,13 +6,13 @@ from flask import Blueprint from flask import request from flask import jsonify -# from flask import current_app -# from pony.orm import commit +from sqlalchemy.exc import IntegrityError from luncho.helpers import ForceJSON from luncho.server import User +from luncho.server import db users = Blueprint('users', __name__) @@ -23,14 +23,19 @@ def create_user(): """Create a new user. Request must be: { "username": "username", "full_name": "Full Name", "password": "hash" }""" json = request.get_json(force=True) - # new_user = User(username=json['username'], - # fullname=json['full_name'], - # passhash=json['password'], - # validated=False) - User(username=json['username'], - fullname=json['full_name'], - passhash=json['password'], - validated=False) - # commit() - - return jsonify(status='OK') + + try: + new_user = User(username=json['username'], + fullname=json['full_name'], + passhash=json['password'], + validated=False) + + db.session.add(new_user) + db.session.commit() + + return jsonify(status='OK') + except IntegrityError: + resp = jsonify(status='ERROR', + error='username already exists') + resp.status_code = 409 + return resp diff --git a/luncho/helpers.py b/luncho/helpers.py index 2b9cca9..adc3ee6 100644 --- a/luncho/helpers.py +++ b/luncho/helpers.py @@ -18,8 +18,10 @@ class ForceJSON(object): def check_json(*args, **kwargs): json = request.get_json(force=True, silent=True) if not json: - return jsonify(status='ERROR', - error='Request MUST be in JSON format'), 400 + resp = jsonify(status='ERROR', + error='Request MUST be in JSON format') + resp.status_code = 400 + return resp # now we have the JSON, let's check if all the fields are here. missing = [] @@ -30,8 +32,10 @@ class ForceJSON(object): if missing: fields = ', '.join(missing) error = 'Missing fields: {fields}'.format(fields=fields) - return jsonify(status='ERROR', + resp = jsonify(status='ERROR', error=error) + resp.status_code = 400 + return resp return func(*args, **kwargs) return check_json diff --git a/tests/users_tests.py b/tests/users_tests.py index 0873644..36396bc 100644 --- a/tests/users_tests.py +++ b/tests/users_tests.py @@ -15,21 +15,64 @@ class TestUsers(unittest.TestCase): server.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' server.app.config['TESTING'] = True - print server.app.config['SQLALCHEMY_DATABASE_URI'] self.app = server.app.test_client() + server.db.create_all() - # def tearDown(self): - # os.unlink(server.app.config['SQLITE_FILENAME']) + def tearDown(self): + server.db.drop_all(bind=None) def test_create_user(self): + """Simple user creation.""" request = {'username': 'username', 'full_name': 'full name', 'password': 'hash'} rv = self.app.put('/user/', data=json.dumps(request), content_type='application/json') + self.assertEqual(rv.status_code, 200) self.assertEqual(json.loads(rv.data), {'status': 'OK'}) + def test_duplicate_user(self): + """Check the status for trying to create a user that it is already + in the database.""" + self.test_create_user() # create the first user + + # now duplicate + request = {'username': 'username', + 'full_name': 'full name', + 'password': 'hash'} + rv = self.app.put('/user/', + data=json.dumps(request), + content_type='application/json') + + expected = {"status": "ERROR", + "error": "username already exists"} + + self.assertEqual(rv.status_code, 409) + self.assertEqual(json.loads(rv.data), expected) + + def test_no_json(self): + """Check the status when doing a request that it's not JSON.""" + rv = self.app.put('/user/', + data='', + content_type='text/html') + + expected = {"error": "Request MUST be in JSON format", + "status": "ERROR"} + self.assertEqual(rv.status_code, 400) + self.assertEqual(json.loads(rv.data), expected) + + def test_missing_fields(self): + request = {'password': 'hash'} + rv = self.app.put('/user/', + data=json.dumps(request), + content_type='application/json') + + resp = {'error': 'Missing fields: username, full_name', + 'status': 'ERROR'} + self.assertEqual(rv.status_code, 400) + self.assertEqual(json.loads(rv.data), resp) + if __name__ == '__main__': unittest.main()