Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Refactoring

  • Loading branch information...
commit edde628224dec4840d038088704bb85553cf890e 1 parent 1928f42
@jpscaletti jpscaletti authored
View
6 MANIFEST.in
@@ -1,6 +1,4 @@
-include *.txt *.py
-recursive-include shake_sqlalchemy *
+include *.*
+recursive-include shake_auth *
recursive-include docs *
global-exclude shake *.pyc *.pyo
-prune docs/_build
-prune docs/_themes/.git
View
4 README.txt → README.md
@@ -1,6 +1,4 @@
-
-Shake-SQLAlchemy
-----------------------------------------------
+# Shake-SQLAlchemy
Implements a basic bridge to SQLAlchemy.
View
6 README.rst
@@ -0,0 +1,6 @@
+
+###################
+Shake-SQLAlchemy
+###################
+
+Implements a bridge to SQLAlchemy.
View
91 docs/index.rst
@@ -1,91 +0,0 @@
-
-=======================================
-Shake-SQLAlchemy
-=======================================
-
-Shake.SQLAlchemy es una lugera capa sobre SQLAlchemy, para trabajar más
-comodamente con bases de datos desde tu aplicación Shake.
-
-
-Declaración de una BD
-=======================================
-
- from shake_sqlalchemy import SQLAlchemy
-
- db = SQLAlchemy(SQLALCHEMY_URI)
-
- SQLALCHEMY_URI sigue el formato
-
- 'motor://usuario:password@servidor:basededatos'
-
- (ejemplo: 'postgresql://test:test@127.0.0.1/prezentit'); o
-
- 'sqlite:///basededatos'
-
- (ejemplo: 'sqlite:///db.sqlite').
-
-
-Ahora `db` contiene todos los atributos y funciones definidad en los módulos
-`sqlalchemy` y `sqlalchemy.orm`.
-
-
-Declaración de modelos
-=======================================
-
-Ejemplo:
-
- from .models import db
-
-
- class User(db.Model):
- id = db.Column(db.Integer, primary_key=True)
- login = db.Column(db.String(255), unique=True,
- nullable=False)
- password = db.Column(db.String(300),
- nullable=True)
- fullname = db.Column(db.Unicode(255),
- nullable=False, default=u'')
- date_joined = db.Column(db.DateTime, default=datetime.utcnow,
- nullable=False)
-
-
-Ejemplos de queries
-=======================================
-
- SELECT * FROM TABLA;
- db.query(Tabla).all()
-
- SELECT a, b FROM TABLA;
- db.query(Tabla.a, Tabla.b).all()
-
- SELECT * FROM TABLA WHERE a = 3;
- db.query(Tabla).filter(Tabla.a == 3).all()
-
-Nota:
- .all() para traer todos
- .first() para traer el primero
-
-
- SELECT COUNT(*) FROM TABLA WHERE a = 3;
- db.query(Tabla).filter(Tabla.a == 3).count()
-
-
-
-JSONEncodedType
-=======================================
-Shake-SQLAlchemy tambien viene con una columna para guardar tipos de datos JSON,
-que se encarga automaticamente de la codificacion/decodificación
-
- from shake_sqlalchemy import JSONEncodedType
-
-
- class MyTable(db.Model):
- data = db.Column(JSONEncodedType)
- …
-
-
-
-
-
-
-
View
0  run_tests.py → runtests.py
File renamed without changes
View
10 setup.py
@@ -9,18 +9,18 @@
ROOTDIR = os.path.dirname(__file__)
-README = os.path.join(ROOTDIR, 'README.txt')
+README = os.path.join(ROOTDIR, 'README.rst')
def run_tests():
import sys, subprocess
- errno = subprocess.call([sys.executable, 'run_tests.py'])
+ errno = subprocess.call([sys.executable, 'runtests.py'])
raise SystemExit(errno)
setup(
name='Shake-SQLAlchemy',
- version='0.2',
+ version='0.4',
author='Juan-Pablo Scaletti',
author_email='juanpablo@lucumalabs.com',
packages=['shake_sqlalchemy'],
@@ -30,7 +30,9 @@ def run_tests():
description='Add SQLAlchemy support to your Shake application',
long_description=open(README).read(),
include_package_data=True,
- install_requires=['Shake', 'SQLAlchemy'],
+ install_requires=[
+ 'SQLAlchemy'
+ ],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
View
49 shake_sqlalchemy/__init__.py
@@ -1,20 +1,18 @@
# -*- coding: utf-8 -*-
"""
- Shake-SQLAlchemy
- ----------------------------------------------
+ # Shake-SQLAlchemy
Implements a basic bridge to SQLAlchemy.
- :Copyright © 2010-2011 by Lúcuma labs (http://lucumalabs.com).
- :MIT License. (http://www.opensource.org/licenses/mit-license.php)
+ Copyright © 2011 by Lúcuma labs (http://lucumalabs.com).
+ MIT License. (http://www.opensource.org/licenses/mit-license.php)
"""
-from __future__ import with_statement, absolute_import
+from __future__ import absolute_import
import re
-from threading import Lock
+import threading
-from shake import json
try:
import sqlalchemy
except ImportError:
@@ -23,37 +21,17 @@
' You can get download it from http://www.sqlalchemy.org/'
' If you\'ve already installed SQLAlchemy, then make sure you have '
' it in your PYTHONPATH.')
-from sqlalchemy import orm
from sqlalchemy.orm import scoped_session, sessionmaker
-from sqlalchemy.orm.exc import UnmappedClassError
from sqlalchemy.engine.url import make_url
from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.types import TypeDecorator, Text
-from werkzeug.exceptions import NotFound
-
-_CAMELCASE_RE = re.compile(r'([A-Z]+)(?=[a-z0-9])')
-
-
-class JSONEncodedType(TypeDecorator):
- """Represents an immutable structure as a JSON-encoded string.
- """
- impl = Text
-
- def process_bind_param(self, value, dialect):
- if value is None:
- return None
- return json.dumps(value)
-
- def process_result_value(self, value, dialect):
- if value is None:
- return {}
- return json.loads(value)
+from .query import Query, Future, NotFound
+from .types import JSONEncodedType
def _create_scoped_session(db):
return scoped_session(sessionmaker(autocommit=False, autoflush=True,
- bind=db.engine))
+ bind=db.engine, query_cls=Query))
def _make_table(db):
@@ -78,7 +56,7 @@ def __init__(self, sqlalch):
self._sqlalch = sqlalch
self._engine = None
self._connected_for = None
- self._lock = Lock()
+ self._lock = threading.Lock()
def get_engine(self):
with self._lock:
@@ -93,6 +71,9 @@ def get_engine(self):
return engine
+_CAMELCASE_RE = re.compile(r'([A-Z]+)(?=[a-z0-9])')
+
+
class _ModelTableNameDescriptor(object):
def __get__(self, obj, type):
@@ -163,7 +144,7 @@ def __init__(self, uri='sqlite://', app=None, echo=False, pool_size=None,
self.apply_driver_hacks()
self.connector = None
- self._engine_lock = Lock()
+ self._engine_lock = threading.Lock()
self.session = _create_scoped_session(self)
self.Model = declarative_base(cls=Model, name='Model')
@@ -243,11 +224,11 @@ def engine(self):
connector = _EngineConnector(self)
self.connector = connector
return connector.get_engine()
-
+
def create_all(self):
"""Creates all tables. """
self.Model.metadata.create_all(bind=self.engine)
-
+
def drop_all(self):
"""Drops all tables. """
self.Model.metadata.drop_all(bind=self.engine)
View
89 shake_sqlalchemy/query.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import threading
+
+try:
+ from werkzeug.exceptions import NotFound
+except ImportError:
+ class NotFound(Exception):
+ pass
+from sqlalchemy.orm.query import Query as BaseQuery
+
+
+class Future(object):
+ """Promised future query result.
+
+ :param query: the query to promise
+ :type query: :class:`sqlalchemy.orm.query.Query`
+
+ Copied from SQLAlchemy-Future, written by Hong Minhee.
+ http://lunant.github.com/SQLAlchemy-Future/
+ Used under the MIT license.
+ """
+
+ __slots__ = "query", "_iter", "_head", "_thread"
+
+ def __init__(self, query):
+ self.query = query
+ self._iter = None
+ self._head = None
+ thread_name = "{0}-{1}".format(type(self).__name__, id(self))
+ self._thread = threading.Thread(target=self.execute_promise,
+ name=thread_name)
+ self._thread.start()
+
+ def execute_promise(self):
+ self._iter = iter(self.query)
+ try:
+ val = self._iter.next()
+ except StopIteration:
+ self._head = ()
+ else:
+ self._head = val,
+
+ def __iter__(self):
+ if self._iter is None or self._head is None:
+ self._thread.join()
+ if not self._head:
+ return
+ yield self._head[0]
+ for value in self._iter:
+ yield value
+
+ def all(self):
+ """Returns the results promised as a :class:`list`. This blocks the
+ underlying execution thread until the execution has finished if it
+ is not yet.
+ """
+ return list(self)
+
+
+class Query(BaseQuery):
+ """The subtype of :class:`sqlalchemy.orm.query.Query` class, that provides
+ custom methods.
+ """
+
+ def first_or_notfound(self):
+ """Like :meth:`first` but if no result is found, instead of
+ returning `None` raises a NotFound exception.
+ """
+ result = self.first()
+ if result is None:
+ raise NotFound
+ return result
+
+ def get_or_notfound(self, ident):
+ """Like :meth:`get` but aborts with 404 if not found instead of
+ returning `None`.
+ """
+ result = self.get(ident)
+ if result is None:
+ raise NotFound
+ return result
+
+ def promise(self):
+ """Makes a promise and returns a :class:`Future`.
+ """
+ return Future(self)
+
View
37 shake_sqlalchemy/types.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+try:
+ import simplejson as json
+except ImportError:
+ import json
+from sqlalchemy.types import TypeDecorator, Text
+
+
+class JSONEncodedType(TypeDecorator):
+ """Represents an immutable structure as a JSON-encoded string.
+ """
+
+ impl = Text
+
+ def process_bind_param(self, value, dialect):
+ if value is None:
+ return None
+ try:
+ json_value = json.dumps(value)
+ except ValueError:
+ json_value = {}
+ return json_value
+
+ def process_result_value(self, json_value, dialect):
+ if json_value is None:
+ return {}
+ try:
+ value = json.loads(json_value)
+ except ValueError:
+ value = {}
+ return value
+
+ def copy(self):
+ return self.__class__()
+
View
90 test_sqlalchemy.py
@@ -8,7 +8,7 @@
import os
import pytest
-from shake_sqlalchemy import SQLAlchemy, JSONEncodedType
+from shake_sqlalchemy import SQLAlchemy, JSONEncodedType, NotFound, Future
prefix = 'sqlite:///'
@@ -16,6 +16,17 @@
URI2 = 'db2.sqlite'
+def tear_down():
+ try:
+ os.remove(URI1)
+ except:
+ pass
+ try:
+ os.remove(URI2)
+ except:
+ pass
+
+
def create_test_model(db):
class Todo(db.Model):
@@ -33,18 +44,8 @@ def __init__(self, title, text):
return Todo
-def tear_down():
- try:
- os.remove(URI1)
- except:
- pass
- try:
- os.remove(URI2)
- except:
- pass
-
-
-def test_basic_insert():
+def test_insert():
+ tear_down()
db = SQLAlchemy(prefix + URI1)
Todo = create_test_model(db)
db.create_all()
@@ -58,11 +59,11 @@ def add(title, text=''):
db.commit()
titles = ' '.join(x.title for x in db.query(Todo).all())
assert titles == 'First Second'
-
tear_down()
-def test_request_context():
+def test_select():
+ tear_down()
db = SQLAlchemy(prefix + URI1)
Todo = create_test_model(db)
db.create_all()
@@ -70,12 +71,22 @@ def test_request_context():
data = db.query(Todo).all()
assert len(data) == 0
- todo = Todo('Test', 'test')
- db.add(todo)
+ def add(title, text=''):
+ todo = Todo(title, text)
+ db.add(todo)
+
+ add('First', 'The text')
+ add('Second', 'The text')
db.commit()
+
data = db.query(Todo).all()
+ assert len(data) == 2
+
+ data = db.query(Todo).filter(Todo.title == 'First').all()
assert len(data) == 1
+ data = db.query(Todo).filter(Todo.title == 'First').first()
+ assert data.title == 'First'
tear_down()
@@ -84,7 +95,30 @@ def test_helper_api():
assert db.metadata == db.Model.metadata
+def test_notfound():
+ tear_down()
+ db = SQLAlchemy(prefix + URI1)
+ Todo = create_test_model(db)
+ db.create_all()
+ TITLE = 'test'
+ TEXT = 'The text'
+ todo = Todo(TITLE, TEXT)
+ db.add(todo)
+ db.commit()
+
+ with pytest.raises(NotFound):
+ db.query(Todo).filter(Todo.title== 'foo').first_or_notfound()
+
+ with pytest.raises(NotFound):
+ db.query(Todo).get_or_notfound(999)
+
+ data = db.query(Todo).filter(Todo.title== TITLE).first_or_notfound()
+ assert data.text == TEXT
+ tear_down()
+
+
def test_multiple_databases():
+ tear_down()
db1 = SQLAlchemy(prefix + URI1)
db2 = SQLAlchemy(prefix + URI2)
Todo1 = create_test_model(db1)
@@ -109,11 +143,11 @@ def add2(title, text):
assert db1.query(Todo1).count() == 3
assert db2.query(Todo2).count() == 1
-
tear_down()
-def test_json_type():
+def test_jsontype():
+ tear_down()
db = SQLAlchemy(prefix + URI1)
class Post(db.Model):
@@ -129,6 +163,24 @@ class Post(db.Model):
post = db.query(Post).first()
assert post.content == data
+ tear_down()
+
+
+def test_promises():
+ tear_down()
+ db = SQLAlchemy(prefix + URI1)
+ Todo = create_test_model(db)
+ db.create_all()
+
+ def add(title, text=''):
+ todo = Todo(title, text)
+ db.add(todo)
+
+ add('First', 'The text')
+ add('Second', 'The text')
+ db.commit()
+ data = db.query(Todo).promise()
+ assert isinstance(data, Future)
tear_down()
Please sign in to comment.
Something went wrong with that request. Please try again.