This repository has been archived by the owner on Apr 27, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an example application with dummy sqla model to run tests against.
Add an sqla specific extention with hook on session events to get model modifications.
- Loading branch information
Showing
10 changed files
with
281 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
|
||
|
||
def includeme(config): | ||
config.include('pyramid_caching') | ||
config.include('.model') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
|
||
from sqlalchemy import Column, Integer, String, MetaData, ForeignKey, Text | ||
from sqlalchemy.engine import engine_from_config | ||
from sqlalchemy.ext.declarative import declarative_base | ||
from sqlalchemy.orm import sessionmaker, relationship | ||
|
||
metadata = MetaData() | ||
|
||
Base = declarative_base(metadata=metadata) | ||
Session = sessionmaker() | ||
|
||
|
||
def includeme(config): | ||
config.include('pyramid_caching.ext.sqlalchemy') | ||
engine = engine_from_config(config.registry.settings) | ||
metadata.create_all(engine) | ||
Session.configure(bind=engine) | ||
|
||
config.register_sqla_session_caching_hook(Session) | ||
config.register_sqla_base_class(Base) | ||
|
||
|
||
class User(Base): | ||
|
||
__tablename__ = 'user' | ||
|
||
id = Column('id', Integer, primary_key=True) | ||
|
||
name = Column('name', String(32)) | ||
|
||
notes = relationship('UserNote', backref='user', cascade='delete') | ||
|
||
|
||
class UserNote(Base): | ||
|
||
__tablename__ = 'user_note' | ||
|
||
user_id = Column('user_id', ForeignKey('user.id'), | ||
primary_key=True) | ||
|
||
id = Column('id', Integer, primary_key=True, autoincrement=True) | ||
|
||
content = Column('name', Text()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
|
||
|
||
def includeme(config): | ||
config.include('.versioners') |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
from __future__ import absolute_import | ||
import inspect | ||
|
||
from zope.interface import implementer | ||
|
||
from pyramid_caching.interfaces import IIdentityInspector | ||
|
||
from sqlalchemy import event | ||
|
||
|
||
def includeme(config): | ||
|
||
config.add_directive('register_sqla_session_caching_hook', | ||
register_sqla_session_caching_hook) | ||
|
||
config.add_directive('register_sqla_base_class', | ||
register_sqla_base_class) | ||
|
||
identity_inspector = SqlAlchemyIdentityInspector() | ||
|
||
config.registry.registerUtility(identity_inspector) | ||
|
||
|
||
def register_sqla_base_class(config, base_cls): | ||
registry = config.registry | ||
|
||
identity_inspector = registry.getUtility(IIdentityInspector) | ||
|
||
def identify(model): | ||
return identity_inspector.identify(model) | ||
|
||
registry.registerAdapter(identify, required=[base_cls], | ||
provided=IIdentityInspector) | ||
|
||
registry.registerAdapter(identify, required=[base_cls.__class__], | ||
provided=IIdentityInspector) | ||
|
||
|
||
def register_sqla_session_caching_hook(config, session_cls): | ||
model_versioner = config.get_model_versioner() | ||
|
||
def on_before_commit(session): | ||
dirty = session.dirty | ||
|
||
def incr_models(session): | ||
# XXX: should increment only cacheable models | ||
for model in dirty: | ||
model_versioner.incr(model) | ||
|
||
if dirty: | ||
event.listen(session_cls, 'after_commit', incr_models) | ||
|
||
event.listen(session_cls, 'before_commit', on_before_commit) | ||
|
||
|
||
@implementer(IIdentityInspector) | ||
class SqlAlchemyIdentityInspector(object): | ||
|
||
def identify(self, obj_or_cls): | ||
tablename = obj_or_cls.__tablename__ | ||
|
||
if inspect.isclass(obj_or_cls): | ||
return tablename | ||
|
||
# with a table user_message with a composite primary key user_id and id | ||
# an object user_message(user_id=123, id=456) will give: | ||
# 'user_message:user_id=123:id=456' | ||
|
||
# TODO: if table has no primary keys :-/ | ||
|
||
table = obj_or_cls.__table__ | ||
ids = ':'.join(['%s=%s' % (col_name, getattr(obj_or_cls, col_name)) | ||
for col_name in table.primary_key.columns.keys()]) | ||
|
||
return '%s:%s' % (obj_or_cls.__tablename__, ids) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import unittest | ||
|
||
import webtest | ||
|
||
from pyramid.config import Configurator | ||
from pyramid.decorator import reify | ||
|
||
|
||
class Base(unittest.TestCase): | ||
|
||
@reify | ||
def config(self): | ||
self.addCleanup(delattr, self, 'config') | ||
_config = Configurator(settings={ | ||
'sqlalchemy.url': 'sqlite:///:memory:', | ||
}) | ||
_config.include('example') | ||
_config.commit() | ||
return _config | ||
|
||
@reify | ||
def app(self): | ||
self.addCleanup(delattr, self, 'app') | ||
return webtest.TestApp(self.config.make_wsgi_app()) | ||
|
||
@property | ||
def registry(self): # pragma no cover | ||
return self.config.registry | ||
|
||
@property | ||
def model_versioner(self): | ||
return self.config.get_model_versioner() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
from pyramid.decorator import reify | ||
from . import Base | ||
|
||
|
||
class TestSqlAlchemyExt(Base): | ||
|
||
def setUp(self): | ||
self.app | ||
|
||
def tearDown(self): | ||
from example.model import User | ||
for user in self.sqla_session.query(User).all(): | ||
self.sqla_session.delete(user) | ||
self.sqla_session.commit() | ||
|
||
@reify | ||
def sqla_session(self): | ||
from example.model import Session | ||
session = Session() | ||
self.addCleanup(session.close) | ||
self.addCleanup(delattr, self, 'sqla_session') | ||
return session | ||
|
||
def test_incr(self): | ||
from example.model import User | ||
user = User() | ||
|
||
key_cls_0 = self.model_versioner.get_key(User) | ||
key_obj_0 = self.model_versioner.get_key(user) | ||
|
||
self.model_versioner.incr(user) | ||
|
||
key_cls_1 = self.model_versioner.get_key(User) | ||
key_obj_1 = self.model_versioner.get_key(user) | ||
|
||
self.assertNotEqual(key_cls_1, key_cls_0) | ||
self.assertNotEqual(key_obj_1, key_obj_0) | ||
|
||
def test_unicity_simple_pk(self): | ||
from example.model import User | ||
|
||
user0 = User(name=u'Bob Marley') | ||
user1 = User(name=u'Peter Tosh') | ||
|
||
self.sqla_session.add(user0) | ||
self.sqla_session.add(user1) | ||
self.sqla_session.commit() | ||
|
||
self.assertNotEqual( | ||
self.model_versioner.get_key(user0), | ||
self.model_versioner.get_key(user1), | ||
) | ||
|
||
def test_unicity_composite_pk(self): | ||
from example.model import User, UserNote | ||
|
||
msg0 = UserNote(id=11, content='I ray') | ||
msg1 = UserNote(id=22, content='Jah') | ||
user = User(name=u'Bob Marley') | ||
|
||
user.notes = [msg0, msg1] | ||
|
||
self.sqla_session.add(user) | ||
self.sqla_session.commit() | ||
|
||
self.assertNotEqual( | ||
self.model_versioner.get_key(msg0), | ||
self.model_versioner.get_key(msg1), | ||
) | ||
|
||
def test_auto_incr(self): | ||
from example.model import User, UserNote | ||
|
||
user = User(name=u'Bob') | ||
|
||
self.sqla_session.add(user) | ||
|
||
self.sqla_session.commit() | ||
|
||
key_user_v0 = self.model_versioner.get_key(user) | ||
|
||
user.name = 'Bob Marley' | ||
|
||
self.sqla_session.commit() | ||
|
||
key_user_v1 = self.model_versioner.get_key(user) | ||
|
||
self.assertNotEqual(key_user_v0, key_user_v1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters