From d5c7bb7582a25014e7aa57d6060cdba5dfa2af90 Mon Sep 17 00:00:00 2001 From: Tom Kedem Date: Sat, 23 Jul 2016 23:19:22 +0300 Subject: [PATCH 1/4] Added support for backrefs using `sqlalchemy.orm.backref` which returns a tuple. --- sqlalchemy_utils/listeners.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sqlalchemy_utils/listeners.py b/sqlalchemy_utils/listeners.py index 691578b..3f0f091 100644 --- a/sqlalchemy_utils/listeners.py +++ b/sqlalchemy_utils/listeners.py @@ -227,6 +227,8 @@ def auto_delete_orphans(attr): 'The relationship argument given for auto_delete_orphans needs to ' 'have a backref relationship set.' ) + if isinstance(backref, tuple): + backref = backref[0] @sa.event.listens_for(sa.orm.Session, 'after_flush') def delete_orphan_listener(session, ctx): @@ -249,7 +251,7 @@ def auto_delete_orphans(attr): ( session.query(target_class) .filter( - ~getattr(target_class, attr.property.backref).any() + ~getattr(target_class, backref).any() ) .delete(synchronize_session=False) ) From 84682326aa7b16f799249239c1cca0091fe2efb1 Mon Sep 17 00:00:00 2001 From: Tom Kedem Date: Mon, 25 Jul 2016 13:55:23 +0300 Subject: [PATCH 2/4] Added unit test for backref with keywords for auto_delete_orphans. --- tests/test_auto_delete_orphans.py | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/test_auto_delete_orphans.py b/tests/test_auto_delete_orphans.py index 06daaa1..e7ea867 100644 --- a/tests/test_auto_delete_orphans.py +++ b/tests/test_auto_delete_orphans.py @@ -1,5 +1,6 @@ import pytest import sqlalchemy as sa +from sqlalchemy.orm import backref from sqlalchemy_utils import auto_delete_orphans, ImproperlyConfigured @@ -66,6 +67,22 @@ def EntryWithoutTagsBackref(Base, Tag, tagging_tbl): return EntryWithoutTagsBackref +@pytest.fixture +def EntryWithBackrefKeywords(Base, Tag, tagging_tbl): + class Entry(Base): + __tablename__ = 'entry' + + id = sa.Column(sa.Integer, primary_key=True) + + tags = sa.orm.relationship( + 'Tag', + secondary=tagging_tbl, + backref=backref('entries', lazy='select') + ) + auto_delete_orphans(Entry.tags) + return Entry + + class TestAutoDeleteOrphans(object): @pytest.fixture @@ -106,3 +123,34 @@ class TestAutoDeleteOrphansWithoutBackref(object): def test_orphan_deletion(self, EntryWithoutTagsBackref): with pytest.raises(ImproperlyConfigured): auto_delete_orphans(EntryWithoutTagsBackref.tags) + + +class TestAutoDeleteOrphansBackrefWithKeywords(object): + + @pytest.fixture + def init_models(self, EntryWithBackrefKeywords, Tag): + pass + + def test_orphan_deletion(self, session, EntryWithBackrefKeywords, Tag): + r1 = EntryWithBackrefKeywords() + r2 = EntryWithBackrefKeywords() + r3 = EntryWithBackrefKeywords() + t1, t2, t3, t4 = ( + Tag('t1'), + Tag('t2'), + Tag('t3'), + Tag('t4') + ) + + r1.tags.extend([t1, t2]) + r2.tags.extend([t2, t3]) + r3.tags.extend([t4]) + session.add_all([r1, r2, r3]) + + assert session.query(Tag).count() == 4 + r2.tags.remove(t2) + assert session.query(Tag).count() == 4 + r1.tags.remove(t2) + assert session.query(Tag).count() == 3 + r1.tags.remove(t1) + assert session.query(Tag).count() == 2 From f690addefe26bcf70cabab07bd78f605005c525d Mon Sep 17 00:00:00 2001 From: Tom Kedem Date: Sun, 2 Oct 2016 14:17:04 +0300 Subject: [PATCH 3/4] Use parametrized test fixture to avoid code duplication. --- tests/test_auto_delete_orphans.py | 57 +++---------------------------- 1 file changed, 5 insertions(+), 52 deletions(-) diff --git a/tests/test_auto_delete_orphans.py b/tests/test_auto_delete_orphans.py index e7ea867..db57bfd 100644 --- a/tests/test_auto_delete_orphans.py +++ b/tests/test_auto_delete_orphans.py @@ -37,17 +37,17 @@ def Tag(Base): return Tag -@pytest.fixture -def Entry(Base, Tag, tagging_tbl): +@pytest.fixture(params=['entries', backref('entries', lazy='select')], ids=['backref_string', 'backref_with_keywords']) +def Entry(Base, Tag, tagging_tbl, request): class Entry(Base): __tablename__ = 'entry' id = sa.Column(sa.Integer, primary_key=True) tags = sa.orm.relationship( - 'Tag', + Tag, secondary=tagging_tbl, - backref='entries' + backref=request.param ) auto_delete_orphans(Entry.tags) return Entry @@ -61,28 +61,12 @@ def EntryWithoutTagsBackref(Base, Tag, tagging_tbl): id = sa.Column(sa.Integer, primary_key=True) tags = sa.orm.relationship( - 'Tag', + Tag, secondary=tagging_tbl ) return EntryWithoutTagsBackref -@pytest.fixture -def EntryWithBackrefKeywords(Base, Tag, tagging_tbl): - class Entry(Base): - __tablename__ = 'entry' - - id = sa.Column(sa.Integer, primary_key=True) - - tags = sa.orm.relationship( - 'Tag', - secondary=tagging_tbl, - backref=backref('entries', lazy='select') - ) - auto_delete_orphans(Entry.tags) - return Entry - - class TestAutoDeleteOrphans(object): @pytest.fixture @@ -123,34 +107,3 @@ class TestAutoDeleteOrphansWithoutBackref(object): def test_orphan_deletion(self, EntryWithoutTagsBackref): with pytest.raises(ImproperlyConfigured): auto_delete_orphans(EntryWithoutTagsBackref.tags) - - -class TestAutoDeleteOrphansBackrefWithKeywords(object): - - @pytest.fixture - def init_models(self, EntryWithBackrefKeywords, Tag): - pass - - def test_orphan_deletion(self, session, EntryWithBackrefKeywords, Tag): - r1 = EntryWithBackrefKeywords() - r2 = EntryWithBackrefKeywords() - r3 = EntryWithBackrefKeywords() - t1, t2, t3, t4 = ( - Tag('t1'), - Tag('t2'), - Tag('t3'), - Tag('t4') - ) - - r1.tags.extend([t1, t2]) - r2.tags.extend([t2, t3]) - r3.tags.extend([t4]) - session.add_all([r1, r2, r3]) - - assert session.query(Tag).count() == 4 - r2.tags.remove(t2) - assert session.query(Tag).count() == 4 - r1.tags.remove(t2) - assert session.query(Tag).count() == 3 - r1.tags.remove(t1) - assert session.query(Tag).count() == 2 From 03c58b695eabdbc90794917b8942caf65d632cf5 Mon Sep 17 00:00:00 2001 From: Tom Kedem Date: Sun, 2 Oct 2016 15:25:43 +0300 Subject: [PATCH 4/4] fixed line too long --- tests/test_auto_delete_orphans.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_auto_delete_orphans.py b/tests/test_auto_delete_orphans.py index db57bfd..fc7fd8d 100644 --- a/tests/test_auto_delete_orphans.py +++ b/tests/test_auto_delete_orphans.py @@ -37,7 +37,8 @@ def Tag(Base): return Tag -@pytest.fixture(params=['entries', backref('entries', lazy='select')], ids=['backref_string', 'backref_with_keywords']) +@pytest.fixture(params=['entries', backref('entries', lazy='select')], + ids=['backref_string', 'backref_with_keywords']) def Entry(Base, Tag, tagging_tbl, request): class Entry(Base): __tablename__ = 'entry'