Merge lp:~cjwatson/storm/py3-listify-resultset into lp:storm

Proposed by Colin Watson
Status: Merged
Merged at revision: 553
Proposed branch: lp:~cjwatson/storm/py3-listify-resultset
Merge into: lp:storm
Diff against target: 147 lines (+72/-2)
5 files modified
NEWS (+8/-0)
setup.py (+1/-0)
storm/store.py (+3/-0)
storm/tests/zope/README.txt (+53/-0)
storm/zope/configure.zcml (+7/-2)
To merge this branch: bzr merge lp:~cjwatson/storm/py3-listify-resultset
Reviewer Review Type Date Requested Status
Simon Poirier (community) Approve
Review via email: mp+382961@code.launchpad.net

Commit message

Fix list() on security-proxied ResultSets on Python 3.

Description of the change

On Python 3, list() calls len() internally to preallocate the correct amount of space if possible. If __len__ is unimplemented, then it falls back to guessing, but ForbiddenAttribute confuses it and results in failing to create the list. Allowing __len__ through the security proxy, even though it's unimplemented, avoids this.

(We don't want to implement ResultSet.__len__ in terms of ResultSet.count because it would make it too easy to write code that implicitly issued extra SQL queries. On the other hand, EmptyResultSet.__len__ can trivially be implemented, so do so here.)

To post a comment you must log in.
555. By Colin Watson

Allow SQLObjectResultSet.__len__ too.

Revision history for this message
Simon Poirier (simpoir) wrote :

+1 LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2020-03-18 17:30:00 +0000
3+++ NEWS 2020-04-25 18:16:55 +0000
4@@ -1,3 +1,11 @@
5+0.24
6+====
7+
8+Bug fixes
9+---------
10+
11+- Fix list() on security-proxied ResultSets on Python 3.
12+
13 0.23 (2020-03-18)
14 =================
15
16
17=== modified file 'setup.py'
18--- setup.py 2019-11-21 01:57:01 +0000
19+++ setup.py 2020-04-25 18:16:55 +0000
20@@ -37,6 +37,7 @@
21 "transaction >= 1.0.0",
22 "twisted >= 10.0.0",
23 "zope.component >= 3.8.0",
24+ "zope.configuration",
25 "zope.interface >= 4.0.0",
26 "zope.security >= 3.7.2",
27 ]
28
29=== modified file 'storm/store.py'
30--- storm/store.py 2020-03-18 16:31:08 +0000
31+++ storm/store.py 2020-04-25 18:16:55 +0000
32@@ -1548,6 +1548,9 @@
33 return
34 yield None
35
36+ def __len__(self):
37+ return 0
38+
39 def __getitem__(self, index):
40 return self.copy()
41
42
43=== modified file 'storm/tests/zope/README.txt'
44--- storm/tests/zope/README.txt 2019-08-11 09:58:50 +0000
45+++ storm/tests/zope/README.txt 2020-04-25 18:16:55 +0000
46@@ -193,6 +193,59 @@
47 >>> type(get_obj_info(ProxyFactory(person))) is ObjectInfo
48 True
49
50+Security-wrapped result sets can be used in the same way as unwrapped ones.
51+
52+ >>> from zope.component.testing import (
53+ ... setUp,
54+ ... tearDown,
55+ ... )
56+ >>> from zope.configuration import xmlconfig
57+ >>> from zope.security.protectclass import protectName
58+ >>> import storm.zope
59+
60+ >>> setUp()
61+ >>> _ = xmlconfig.file("configure.zcml", package=storm.zope)
62+ >>> protectName(Person, "name", "zope.Public")
63+
64+ >>> another_person = Person(u"Jane Doe")
65+ >>> store.add(another_person)
66+ <...Person object at ...>
67+ >>> result = ProxyFactory(store.find(Person).order_by(Person.name))
68+ >>> for person in result:
69+ ... print(person.name)
70+ Jane Doe
71+ John Doe
72+ >>> print(result[0].name)
73+ Jane Doe
74+ >>> another_person in result
75+ True
76+ >>> result.is_empty()
77+ False
78+ >>> result.any()
79+ <...Person object at ...>
80+ >>> print(result.first().name)
81+ Jane Doe
82+ >>> print(result.last().name)
83+ John Doe
84+ >>> print(result.count())
85+ 2
86+
87+Check ``list()`` as well as ordinary iteration: on Python 3, this tries to
88+call ``__len__`` first (which doesn't exist, but is nevertheless allowed by
89+the security wrapper).
90+
91+ >>> for person in list(result):
92+ ... print(person.name)
93+ Jane Doe
94+ John Doe
95+
96+ >>> result = ProxyFactory(
97+ ... store.find(Person, Person.name.startswith(u"John")))
98+ >>> print(result.one().name)
99+ John Doe
100+
101+ >>> tearDown()
102+
103
104 ResultSet interfaces
105 --------------------
106
107=== modified file 'storm/zope/configure.zcml'
108--- storm/zope/configure.zcml 2020-03-18 17:04:22 +0000
109+++ storm/zope/configure.zcml 2020-04-25 18:16:55 +0000
110@@ -4,6 +4,9 @@
111 i18n_domain="zope"
112 >
113
114+ <include package="zope.component" file="meta.zcml" />
115+ <include package="zope.security" file="meta.zcml" />
116+
117 <utility
118 component=".zstorm.global_zstorm"
119 provides=".interfaces.IZStorm"
120@@ -11,16 +14,17 @@
121
122 <class class="storm.store.ResultSet">
123 <allow interface=".interfaces.IResultSet" />
124- <allow attributes="__getslice__" />
125+ <allow attributes="__getslice__ __len__" />
126 </class>
127
128 <class class="storm.store.EmptyResultSet">
129 <allow interface=".interfaces.IResultSet" />
130- <allow attributes="__getslice__" />
131+ <allow attributes="__getslice__ __len__" />
132 </class>
133
134 <class class="storm.sqlobject.SQLObjectResultSet">
135 <allow interface=".interfaces.ISQLObjectResultSet" />
136+ <allow attributes="__len__" />
137 </class>
138
139 <class class="storm.references.BoundReferenceSet">
140@@ -29,6 +33,7 @@
141 <allow attributes="
142 find
143 __iter__
144+ __len__
145 __getitem__
146 __contains__
147 is_empty

Subscribers

People subscribed via source and target branches

to status/vote changes: