[storm] question about ReferenceSet
Stephan Diehl
stephan.diehl at gmx.net
Thu Dec 13 18:27:50 GMT 2007
Hi Gustavo,
Am Dienstag, 11. Dezember 2007 23:46:30 schrieb Gustavo Niemeyer:
> Hello Stephan,
>
> > I'm new to storm and really like it so far. Until now I've used my own
> > homegrown database wrapper (so I can't compare to SQLAlchemy or
> > SQLObject).
>
> I'm glad that you enjoy it.
>
> > I've a question about ReferenceSet. The name implies some relation to,
> > well, sets. Is it planned that the set interface will be supported
> > sometimes in the future?
>
> It may be, but notice that the "set" naming isn't about being able to do
> Python-style set operations in this case, but rather about the fact that
> this type offers access to "a set of referenced objects".
>
> > For example, if I have some classes 'User' and 'Group', I'd like to be
> > able to do something like this:
> > accessGroups = set([grp1, grp2, grp3])
> > if usr.groups & accessGroups:
> > doSomething
>
> This does look like an interesting feature, and at first sight I can't
> think of any incoveniences about it. It's been added it to the TODO
> file for future consideration.
>
> Thanks for the suggestion.
I thought a bit more about my idea and had a look at storms code. I've now
implemented something a bit different to my first suggestion.
With the patch, the following is now possible (staying with the above
example):
usr.groups = (grp1, grp2) # setting an iterable to a ReferenceSet attribute
usr.groups |= (grp3,) # updating the attribute
... and all the other set related operation.
I believe that this allows quite compact and obvious code.
The patch also includes some doctests and documentation
in "tests/set_feature.txt"
I hope you find this as usefull as I do :-)
Cheers
Stephan
-------------- next part --------------
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: stephan at bach-20071213171952-gyx9j72intk4njjm
# target_branch: http://bazaar.launchpad.net/~storm/storm/trunk
# testament_sha1: 62bc2d1211d315ae03389493279d0de61654e5d5
# timestamp: 2007-12-13 19:11:54 +0100
# source_branch: .
# base_revision_id: gustavo at niemeyer.net-20071118211157-\
# 7mdqxusap4ttpk1b
#
# Begin patch
=== added file 'tests/set_feature.txt'
--- tests/set_feature.txt 1970-01-01 00:00:00 +0000
+++ tests/set_feature.txt 2007-12-13 17:19:52 +0000
@@ -0,0 +1,149 @@
+
+[[TableOfContents]]
+
+==== Storm patch ====
+This document describes a little patch to storm to allow some
+set like operation when using 'ReferenceSet' attributes.
+
+The added interface allows very concise and natural code if one is
+used to the set operation.
+
+==== First Change ====
+add a '__set__' method to storm.references.ReferenceSet.
+This allows to set any iterable to a ReferenceSet attribute
+directly. Any element of that iterable has to be compatible, of course.
+
+Furthermore, this allows to use the augmented set operation on
+such an attribute.
+
+==== Second Change ====
+add the set interface to storm.references.BoundReferenceSetBase.
+They take any iterable as an argument.
+
+Added methods:
+{{{
+update
+__ior__
+intersection_update
+__iand__
+difference_update
+__isub__
+symmetric_difference_update
+__ixor__
+}}}
+
+==== Example ====
+First, let's define basic 'User' and 'Group' classes which have a n:m
+relationship:
+{{{#!python
+>>> from storm.locals import *
+>>> class Group(object):
+... __storm_table__ = "group_table"
+... id = Int(primary=True)
+... name = Unicode()
+>>>
+>>> class UserGroup(object):
+... __storm_table__ = "user_group_table"
+... __storm_primary__ = 'user_id', 'group_id'
+... user_id = Int()
+... group_id = Int()
+>>>
+>>> class User(object):
+... __storm_table__ = "user_table"
+... id = Int(primary=True)
+... name = Unicode()
+... groups = ReferenceSet(id, UserGroup.user_id,
+... UserGroup.group_id, Group.id)
+>>>
+>>> database = create_database("sqlite:")
+>>> store = Store(database)
+>>> store.execute("CREATE TABLE group_table "
+... "(id INTEGER PRIMARY KEY, name VARCHAR)")
+<storm.databases.sqlite.SQLiteResult object at 0x...>
+>>> store.execute("CREATE TABLE user_table "
+... "(id INTEGER PRIMARY KEY, name VARCHAR)")
+<storm.databases.sqlite.SQLiteResult object at 0x...>
+>>> store.execute("CREATE TABLE user_group_table "
+... "(user_id INTEGER, group_id INTEGER)")
+<storm.databases.sqlite.SQLiteResult object at 0x...>
+>>>
+}}}
+
+First, let's define some groups and a user:
+
+{{{#!python
+>>> group_1 = Group()
+>>> group_1.name = u"group_1"
+>>> group_2 = Group()
+>>> group_2.name = u"group_2"
+>>> group_3 = Group()
+>>> group_3.name = u"group_3"
+>>> store.add(group_1)
+<Group object at 0x...>
+>>> store.add(group_2)
+<Group object at 0x...>
+>>> store.add(group_3)
+<Group object at 0x...>
+>>> user = User()
+>>> user.name = u"user"
+>>> store.add(user)
+<User object at 0x...>
+>>> store.commit()
+>>>
+}}}
+
+Then, instead running
+
+{{{
+user.groups.add(group_1)
+user.groups.add(group_2)
+}}}
+
+we can just do
+
+{{{#!python
+>>> user.groups = (group_1, group_2)
+>>>
+}}}
+
+let's check if everything worked as expected:
+{{{#!python
+>>> assert group_1 in user.groups
+>>> assert group_2 in user.groups
+>>> assert len(list(user.groups)) == 2
+>>>
+}}}
+
+Next, make sure that group_3 is contained in users groups
+{{{#!python
+>>> user.groups |= (group_3,)
+>>> assert group_3 in user.groups
+>>> assert len(list(user.groups)) == 3
+>>> assert set(user.groups) & set((group_1,))
+>>>
+}}}
+
+We don't like group_3 after all. Let's remove it again.
+{{{#!python
+>>> user.groups -= (group_3,)
+>>> assert group_3 not in user.groups
+>>> assert len(list(user.groups)) == 2
+>>>
+}}}
+
+And here are the remaining operations:
+
+{{{#!python
+>>> user.groups &= (group_2, group_3)
+>>> assert group_2 in user.groups
+>>> assert len(list(user.groups)) == 1
+>>> user.groups |= (group_1,)
+>>> assert group_1 in user.groups
+>>> assert group_2 in user.groups
+>>> assert len(list(user.groups)) == 2
+>>> user.groups ^= (group_2, group_3)
+>>> assert len(list(user.groups)) == 2
+>>> assert group_1 in user.groups
+>>> assert group_3 in user.groups
+>>>
+}}}
=== modified file 'storm/references.py'
--- storm/references.py 2007-10-24 21:27:46 +0000
+++ storm/references.py 2007-12-13 17:19:52 +0000
@@ -195,6 +195,20 @@
self._relation2, local,
self._order_by)
+
+ def __set__(self, local, value):
+ # value must be an iterable
+ rs = self.__get__(local)
+ if hasattr(rs, '_in_iop') and rs._in_iop:
+ # we are inside one of the __ixx__ operations
+ # the actual action already happened
+ rs._inop = False
+ else:
+ v = set(value)
+ r = set(rs)
+ rs._add_iter(v - r)
+ rs._rem_iter(r - v)
+
def _build_relations(self, used_cls):
resolver = PropertyResolver(self, used_cls)
@@ -236,6 +250,48 @@
def count(self):
return self.find().count()
+ def _add_iter(self, to_add):
+ for item in to_add:
+ self.add(item)
+ return self
+
+ def _rem_iter(self, to_remove):
+ for item in to_remove:
+ self.remove(item)
+ return self
+
+ def update(self, other):
+ return self._add_iter(set(other) - set(self))
+
+ def __ior__(self, other):
+ self._in_iop = True
+ return self.update(other)
+
+ def intersection_update(self, other):
+ me = set(self)
+ return self._rem_iter(me - (me & set(other)))
+
+ def __iand__(self, other):
+ self._in_iop = True
+ return self.intersection_update(other)
+
+ def difference_update(self, other):
+ return self._rem_iter(set(other)&set(self))
+
+ def __isub__(self, other):
+ self._in_iop = True
+ return self.difference_update(other)
+
+ def symmetric_difference_update(self, other):
+ o = set(other)
+ to_remove = o & set(self)
+ self._rem_iter(to_remove)
+ return self._add_iter(o - to_remove)
+
+ def __ixor__(self, other):
+ self._in_iop = True
+ return self.symmetric_difference_update(other)
+
class BoundReferenceSet(BoundReferenceSetBase):
# Begin bundle
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
HIP+LuSKcKEhkd0ISg==
More information about the storm
mailing list