Merge lp:~free.ekanayaka/storm/zstorm-access into lp:storm

Proposed by Free Ekanayaka
Status: Needs review
Proposed branch: lp:~free.ekanayaka/storm/zstorm-access
Merge into: lp:storm
Diff against target: 216 lines (+94/-9)
4 files modified
storm/zope/testing.py (+16/-7)
storm/zope/zstorm.py (+18/-0)
tests/zope/testing.py (+11/-1)
tests/zope/zstorm.py (+49/-1)
To merge this branch: bzr merge lp:~free.ekanayaka/storm/zstorm-access
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Needs Information
Storm Developers Pending
Review via email: mp+81556@code.launchpad.net

Description of the change

This branch adds a ZStorm.of static method similar to Store.of, that lets Storm object access the ZStorm they belong to without relying on global variables.

It also adds a ZStorm.settings instance variable that application code can use to expose configuration values and resources to Storm objects without using global variables.

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

[1]

What's the reasoning for these features?

[2]

This is adding a reference cycle between every store and zstorm.

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Btw, on second thought, I have a bad feeling about this. This is attaching a blob of data onto ZStorm
without any apparent reason, and ZStorm.of seems to be a way to get to that blob of data if I understand
it correctly. It feels like a workaround to an issue of code organization.

review: Needs Information
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Hi Gustavo, thanks for reviewing.

[1]

The use case is to be able to access the stores managed by a certain ZStorm instance from a Storm object. Let's say you have two stores, with a storm class Foo belonging to the first and a storm class Bar belonging to the second. Now you want to write a Foo.egg() method with some logic that accesses the second store to find some instances of the Bar class. Right now what one would probably do is to run a queryUtility(IZStorm) to get a global ZStorm instance and get the second store object from it.

The problem with this is that I don't see a way to have two ZStorm instances managing separate sets of stores for two entirely different models in the same process (because queryUtility returns you a global per-process object, unless you register your two ZStorm instances as named utilities, but then you introduce some coupling between the two models). Web frameworks like Pyramid let you run multiple applications in the same process, but if one uses ZStorm in the way described, it might be problematic, see also:

http://docs.pylonsproject.org/projects/pyramid/dev/narr/zca.html#using-the-zca-global-api-in-a-pyramid-application

Beside this, it can be useful for parallel testing.

The alternative I see is to pass around the particular ZStorm instance you want and add it as parameter to the Foo.egg() method, but that sounds more laborious than being able to access it via ZStorm.of().

As for the blob of data, maybe it's to open, what I'd use it for is to let storm objects access a non-global Zope registry to query for application components in a non-global way.

[2]

Sure, what problem does the reference cycle bring? If it has bad implications maybe there's a better way.

Unmerged revisions

420. By Free Ekanayaka

Add ZStorm.of and ZStorm.settings

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'storm/zope/testing.py'
2--- storm/zope/testing.py 2011-08-16 10:00:16 +0000
3+++ storm/zope/testing.py 2011-11-08 11:11:03 +0000
4@@ -20,7 +20,11 @@
5 #
6 import transaction
7 from testresources import TestResourceManager
8-from zope.component import provideUtility, getUtility
9+try:
10+ from zope.component import provideUtility, getUtility
11+except ImportError:
12+ # zope.compnent is optional, see ZStormResourceManager.auto_provide_utility
13+ pass
14
15 from storm.zope.zstorm import ZStorm
16 from storm.zope.interfaces import IZStorm
17@@ -40,11 +44,14 @@
18 - 'schema-uri', optionally an alternate URI to use for applying the
19 schema, if not given it defaults to 'uri'.
20
21- @ivar force_delete: If C{True} for running L{Schema.delete} on a L{Store}
22+ @ivar force_delete: If C{True} force running L{Schema.delete} on a L{Store}
23 even if no commit was performed by the test. Useful when running a test
24 in a subprocess that might commit behind our back.
25+ @ivar auto_provide_utility: If C{True} (the default), register the zstorm
26+ resource in the global ZCA registry.
27 """
28 force_delete = False
29+ auto_provide_utility = True
30
31 def __init__(self, databases):
32 super(ZStormResourceManager, self).__init__()
33@@ -88,14 +95,16 @@
34 # with an empty db
35 schema.delete(schema_store)
36
37- provideUtility(zstorm)
38+ if self.auto_provide_utility:
39+ provideUtility(zstorm)
40 self._zstorm = zstorm
41 self._schema_zstorm = schema_zstorm
42
43- elif getUtility(IZStorm) is not self._zstorm:
44- # This probably means that the test code has overwritten our
45- # utility, let's re-register it.
46- provideUtility(self._zstorm)
47+ elif self.auto_provide_utility:
48+ if getUtility(IZStorm) is not self._zstorm:
49+ # This probably means that the test code has overwritten our
50+ # utility, let's re-register it.
51+ provideUtility(self._zstorm)
52
53 return self._zstorm
54
55
56=== modified file 'storm/zope/zstorm.py'
57--- storm/zope/zstorm.py 2010-02-09 12:53:53 +0000
58+++ storm/zope/zstorm.py 2011-11-08 11:11:03 +0000
59@@ -56,6 +56,10 @@
60 from storm.zope.interfaces import IZStorm
61
62 store = getUtility(IZStorm).get('main')
63+
64+ @ivar settings: A C{dict} that user code can use to store configuration
65+ values or resources that Storm objects can access by getting the
66+ L{ZStorm} instance they belong to with L{ZStorm.of}.
67 """
68
69 implements(IZStorm)
70@@ -68,6 +72,7 @@
71 self._local = threading.local()
72 self._default_databases = {}
73 self._default_uris = {}
74+ self.settings = {}
75
76 def _reset(self):
77 for name, store in list(self.iterstores()):
78@@ -135,6 +140,7 @@
79 store._event.hook(
80 "register-transaction", register_store_with_transaction,
81 weakref.ref(self))
82+ store._zstorm = self
83
84 self._stores[id(store)] = store
85 if name is not None:
86@@ -181,6 +187,7 @@
87 store._event.unhook(
88 "register-transaction", register_store_with_transaction,
89 weakref.ref(self))
90+ store._zstorm = None
91
92 def iterstores(self):
93 """Iterate C{name, store} 2-tuples."""
94@@ -202,6 +209,17 @@
95 """
96 return self._default_uris.copy()
97
98+ @staticmethod
99+ def of(context):
100+ """Get the L{ZStorm} the given context is associated with.
101+
102+ If the given context is not associated with any L{ZStorm}, return None.
103+ """
104+ store = context
105+ if not isinstance(store, Store):
106+ store = Store.of(context)
107+ return getattr(store, "_zstorm", None)
108+
109
110 def register_store_with_transaction(store, zstorm_ref):
111 zstorm = zstorm_ref()
112
113=== modified file 'tests/zope/testing.py'
114--- tests/zope/testing.py 2011-09-13 10:43:40 +0000
115+++ tests/zope/testing.py 2011-11-08 11:11:03 +0000
116@@ -28,7 +28,7 @@
117 from storm.exceptions import IntegrityError
118
119 if has_transaction and has_zope_component and has_testresources:
120- from zope.component import provideUtility, getUtility
121+ from zope.component import provideUtility, getUtility, queryUtility
122 from storm.zope.zstorm import ZStorm
123 from storm.zope.interfaces import IZStorm
124 from storm.zope.schema import ZSchema
125@@ -66,6 +66,7 @@
126 self.store = Store(create_database(uri))
127
128 def tearDown(self):
129+ provideUtility(None, IZStorm)
130 del sys.modules["patch_package"]
131 sys.path.remove(self._package_dir)
132 if "patch_1" in sys.modules:
133@@ -117,6 +118,15 @@
134 self.resource.make([])
135 self.assertIs(zstorm, getUtility(IZStorm))
136
137+ def test_make_no_izstorm(self):
138+ """
139+ L{ZStormResourceManager.make} registers its own ZStorm again if a test
140+ has registered a new ZStorm utility overwriting the resource one.
141+ """
142+ self.resource.auto_provide_utility = False
143+ self.resource.make([])
144+ self.assertIs(None, queryUtility(IZStorm))
145+
146 def test_clean_flush(self):
147 """
148 L{ZStormResourceManager.clean} tries to flush the stores to make sure
149
150=== modified file 'tests/zope/zstorm.py'
151--- tests/zope/zstorm.py 2011-09-14 15:27:02 +0000
152+++ tests/zope/zstorm.py 2011-11-08 11:11:03 +0000
153@@ -34,7 +34,7 @@
154 from zope.component import provideUtility, getUtility
155
156 from storm.exceptions import OperationalError
157-from storm.locals import Store
158+from storm.locals import Store, Int
159
160
161 class ZStormTest(TestHelper):
162@@ -130,6 +130,54 @@
163 store = self.zstorm.create("name", "sqlite:")
164 self.assertEquals(self.zstorm.get_name(store), "name")
165
166+ def test_of(self):
167+ """
168+ L{ZStorm.of} returns the L{ZStorm} instance a L{Store} is linked to.
169+ """
170+ store = self.zstorm.create("name", "sqlite:")
171+ self.assertIs(self.zstorm, ZStorm.of(store))
172+
173+ def test_of_with_no_zstorm_associated(self):
174+ """
175+ L{ZStorm.of} returns L{None} of the L{Store} is not linked to
176+ any L{ZStorm} instance.
177+ """
178+ store = self.zstorm.create("name", "sqlite:")
179+ self.zstorm.remove(store)
180+ self.assertIs(None, ZStorm.of(store))
181+
182+ def test_of_with_object(self):
183+ """
184+ L{ZStorm.of} works also when passed a Storm object.
185+ """
186+ store = self.zstorm.create("name", "sqlite:")
187+ store.execute("CREATE TABLE test (id INTEGER)")
188+
189+ class Test(object):
190+ __storm_table__ = "test"
191+ id = Int(primary=True)
192+
193+ test = Test()
194+ store.add(test)
195+ self.assertIs(self.zstorm, ZStorm.of(test))
196+
197+ def test_of_with_object_and_settings(self):
198+ """
199+ L{ZStorm.settings} can be used to conveniently expose application
200+ settings to Storm objects
201+ """
202+ self.zstorm.settings["foo"] = "bar"
203+ store = self.zstorm.create("name", "sqlite:")
204+ store.execute("CREATE TABLE test (id INTEGER)")
205+
206+ class Test(object):
207+ __storm_table__ = "test"
208+ id = Int(primary=True)
209+
210+ test = Test()
211+ store.add(test)
212+ self.assertEqual("bar", ZStorm.of(test).settings["foo"])
213+
214 def test_get_name_with_removed_store(self):
215 store = self.zstorm.create("name", "sqlite:")
216 self.assertEquals(self.zstorm.get_name(store), "name")

Subscribers

People subscribed via source and target branches

to status/vote changes: