Merge lp:~leonardr/lazr.restful/must-specify-version into lp:lazr.restful
- must-specify-version
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Aaron Bentley |
Approved revision: | 191 |
Merged at revision: | 180 |
Proposed branch: | lp:~leonardr/lazr.restful/must-specify-version |
Merge into: | lp:lazr.restful |
Diff against target: |
742 lines (+326/-138) 6 files modified
src/lazr/restful/declarations.py (+70/-37) src/lazr/restful/docs/webservice-declarations.txt (+50/-45) src/lazr/restful/interfaces/_rest.py (+7/-0) src/lazr/restful/testing/helpers.py (+1/-0) src/lazr/restful/testing/webservice.py (+19/-7) src/lazr/restful/tests/test_declarations.py (+179/-49) |
To merge this branch: | bzr merge lp:~leonardr/lazr.restful/must-specify-version |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Aaron Bentley (community) | Approve | ||
Review via email: mp+52705@code.launchpad.net |
Commit message
Description of the change
This is the first in a series of branches that will let us be much stricter about how the multi-version Launchpad web service is defined. Basically, we will set 'require_
This first branch implements require_
field = exported(Field())
field = exported(Field(), exported_
field = exported(Field(), ('2.0', exported_
You must explicitly state which version is the first to contain this field, by using the 'as_of' keyword argument to exported():
field = exported(Field(), as_of='2.0')
field = exported(Field(), exported_
field = exported(Field(), ('2.0', exported_
= Effect on keyword arguments to exported() =
Previously, the keyword arguments such as 'as' affected the first version of the web service. We don't know the name of this version when exported() is called, because the IWebServiceConf
With 'as_of' in place, keyword arguments such as 'as' affect the first _published_ version of the web service, and we do know the name of that version--it's the value of 'as_of'. This lets us skip a confusing consolidation step later on, when we have both an implicit and an explicit definition of the web service.
The code for the consolidation step has been refactored to make it clearer (because I originally thought I'd have to use it), but its functionality should not have changed. I left the clearer code in place because, well, it's clearer.
= Test case refactoring =
A big chunk of code is me refactoring some (but not all) web service setup from WebServiceTestCase into the new class TestCaseWithWeb
I used this class in my new TestRequireExpl
As noted in the code, I couldn't get testtools.TestCase to play nicely with zope's Cleanup class, so I ended up writing a little helper to simulate testtools' version of assertRaises (which returns the raised exception so you can make more assertions about it).
Preview Diff
1 | === modified file 'src/lazr/restful/declarations.py' | |||
2 | --- src/lazr/restful/declarations.py 2011-03-02 20:08:58 +0000 | |||
3 | +++ src/lazr/restful/declarations.py 2011-03-09 16:02:29 +0000 | |||
4 | @@ -212,17 +212,14 @@ | |||
5 | 212 | The dictionary may contain the key 'exported', which controls | 212 | The dictionary may contain the key 'exported', which controls |
6 | 213 | whether or not to publish this field at all in the given | 213 | whether or not to publish this field at all in the given |
7 | 214 | version, and the key 'exported_as', which controls the name to | 214 | version, and the key 'exported_as', which controls the name to |
10 | 215 | use when publishing the field. as=None means to use the | 215 | use when publishing the field. exported_as=None means to use |
11 | 216 | field's internal name. | 216 | the field's internal name. |
12 | 217 | |||
13 | 218 | :param as_of: The name of the earliest version to contain this field. | ||
14 | 217 | 219 | ||
15 | 218 | :param exported_as: the name under which the field is published in | 220 | :param exported_as: the name under which the field is published in |
23 | 219 | the entry in the earliest version of the web service. By | 221 | the entry the first time it shows up (ie. in the 'as_of' |
24 | 220 | default, the field's internal name is used. This is a simpler | 222 | version). By default, the field's internal name is used. |
18 | 221 | alternative to the 'versioned_annotations' parameter, for fields | ||
19 | 222 | whose names don't change in different versions. | ||
20 | 223 | |||
21 | 224 | :param exported: Whether or not the field should be published in | ||
22 | 225 | the earliest version of the web service. | ||
25 | 226 | 223 | ||
26 | 227 | :raises TypeError: if called on an object which doesn't provide IField. | 224 | :raises TypeError: if called on an object which doesn't provide IField. |
27 | 228 | :returns: The field with an added tagged value. | 225 | :returns: The field with an added tagged value. |
28 | @@ -233,24 +230,37 @@ | |||
29 | 233 | if IObject.providedBy(field) and not IReference.providedBy(field): | 230 | if IObject.providedBy(field) and not IReference.providedBy(field): |
30 | 234 | raise TypeError("Object exported; use Reference instead.") | 231 | raise TypeError("Object exported; use Reference instead.") |
31 | 235 | 232 | ||
36 | 236 | # The first step is to turn the arguments into a | 233 | # The first step is to turn the arguments into a VersionedDict |
37 | 237 | # VersionedDict. We'll start by collecting annotations for the | 234 | # describing the different ways this field is exposed in different |
38 | 238 | # earliest version, which we refer to as None because we don't | 235 | # versions. |
35 | 239 | # know any of the real version strings yet. | ||
39 | 240 | annotation_stack = VersionedDict() | 236 | annotation_stack = VersionedDict() |
41 | 241 | annotation_stack.push(None) | 237 | first_version_name = kwparams.pop('as_of', None) |
42 | 238 | annotation_stack.push(first_version_name) | ||
43 | 242 | annotation_stack['type'] = FIELD_TYPE | 239 | annotation_stack['type'] = FIELD_TYPE |
44 | 243 | 240 | ||
45 | 241 | if first_version_name is not None: | ||
46 | 242 | # The user explicitly said to start publishing this field in a | ||
47 | 243 | # particular version. | ||
48 | 244 | annotation_stack['_as_of_was_used'] = True | ||
49 | 245 | annotation_stack['exported'] = True | ||
50 | 246 | |||
51 | 244 | annotation_key_for_argument_key = {'exported_as' : 'as', | 247 | annotation_key_for_argument_key = {'exported_as' : 'as', |
52 | 245 | 'exported' : 'exported', | 248 | 'exported' : 'exported', |
53 | 246 | 'readonly' : 'readonly'} | 249 | 'readonly' : 'readonly'} |
54 | 247 | 250 | ||
55 | 248 | # If keyword parameters are present, they define the field's | 251 | # If keyword parameters are present, they define the field's |
58 | 249 | # behavior for the first version. Incorporate them into the | 252 | # behavior for the first exposed version. Incorporate them into |
59 | 250 | # VersionedDict. | 253 | # the VersionedDict. |
60 | 251 | for (key, annotation_key) in annotation_key_for_argument_key.items(): | 254 | for (key, annotation_key) in annotation_key_for_argument_key.items(): |
61 | 252 | if key in kwparams: | 255 | if key in kwparams: |
62 | 256 | if (key == "exported" and kwparams[key] == False | ||
63 | 257 | and first_version_name is not None): | ||
64 | 258 | raise ValueError( | ||
65 | 259 | ("as_of=%s says to export %s, but exported=False " | ||
66 | 260 | "says not to.") % ( | ||
67 | 261 | first_version_name, field.__class__.__name__)) | ||
68 | 253 | annotation_stack[annotation_key] = kwparams.pop(key) | 262 | annotation_stack[annotation_key] = kwparams.pop(key) |
69 | 263 | |||
70 | 254 | # If any keywords are left over, raise an exception. | 264 | # If any keywords are left over, raise an exception. |
71 | 255 | if len(kwparams) > 0: | 265 | if len(kwparams) > 0: |
72 | 256 | raise TypeError("exported got an unexpected keyword " | 266 | raise TypeError("exported got an unexpected keyword " |
73 | @@ -275,7 +285,7 @@ | |||
74 | 275 | # We track the field's mutator information separately because it's | 285 | # We track the field's mutator information separately because it's |
75 | 276 | # defined in the named operations, not in the fields. The last | 286 | # defined in the named operations, not in the fields. The last |
76 | 277 | # thing we want to do is try to insert a foreign value into an | 287 | # thing we want to do is try to insert a foreign value into an |
78 | 278 | # already create annotation stack. | 288 | # already created annotation stack. |
79 | 279 | field.setTaggedValue(LAZR_WEBSERVICE_MUTATORS, {}) | 289 | field.setTaggedValue(LAZR_WEBSERVICE_MUTATORS, {}) |
80 | 280 | 290 | ||
81 | 281 | return field | 291 | return field |
82 | @@ -1320,27 +1330,37 @@ | |||
83 | 1320 | versioned_dict = field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED) | 1330 | versioned_dict = field.queryTaggedValue(LAZR_WEBSERVICE_EXPORTED) |
84 | 1321 | mutator_annotations = field.queryTaggedValue(LAZR_WEBSERVICE_MUTATORS) | 1331 | mutator_annotations = field.queryTaggedValue(LAZR_WEBSERVICE_MUTATORS) |
85 | 1322 | 1332 | ||
86 | 1323 | # If the first version is None and the second version is the | ||
87 | 1324 | # earliest version, consolidate the two versions. | ||
88 | 1325 | earliest_version = versions[0] | 1333 | earliest_version = versions[0] |
89 | 1326 | stack = versioned_dict.stack | 1334 | stack = versioned_dict.stack |
90 | 1327 | 1335 | ||
107 | 1328 | if (len(stack) >= 2 | 1336 | if (len(stack) >= 2 and stack[0][0] is None |
108 | 1329 | and stack[0][0] is None and stack[1][0] == earliest_version): | 1337 | and stack[1][0] == earliest_version): |
109 | 1330 | 1338 | # The behavior of the earliest version is defined with keyword | |
110 | 1331 | # Make sure the implicit definition of the earliest version | 1339 | # arguments, but the first explicitly-defined version also |
111 | 1332 | # doesn't conflict with the explicit definition. The only | 1340 | # refers to the earliest version. We need to consolidate the |
112 | 1333 | # exception is the 'as' value, which is implicitly defined | 1341 | # versions. |
113 | 1334 | # by the system, not explicitly by the user. | 1342 | implicit_earliest_version = stack[0][1] |
114 | 1335 | for key, value in stack[1][1].items(): | 1343 | explicit_earliest_version = stack[1][1] |
115 | 1336 | implicit_value = stack[0][1].get(key) | 1344 | for key, value in explicit_earliest_version.items(): |
116 | 1337 | if (implicit_value != value and | 1345 | if key not in implicit_earliest_version: |
117 | 1338 | not (key == 'as' and implicit_value == field.__name__)): | 1346 | # This key was defined for the earliest version using a |
118 | 1339 | raise ValueError( | 1347 | # configuration dictionary, but not defined at all |
119 | 1340 | error_prefix + 'Annotation "%s" has conflicting values ' | 1348 | # using keyword arguments. The configuration |
120 | 1341 | 'for the earliest version: "%s" (from keyword arguments) ' | 1349 | # dictionary takes precedence. |
121 | 1342 | 'and "%s" (defined explicitly).' % ( | 1350 | continue |
122 | 1343 | key, implicit_value, value)) | 1351 | implicit_value = implicit_earliest_version[key] |
123 | 1352 | if implicit_value == value: | ||
124 | 1353 | # The two values are in sync. | ||
125 | 1354 | continue | ||
126 | 1355 | if key == 'as' and implicit_value == field.__name__: | ||
127 | 1356 | # The implicit value was set by the system, not by the | ||
128 | 1357 | # user. The later value will simply take precedence. | ||
129 | 1358 | continue | ||
130 | 1359 | raise ValueError( | ||
131 | 1360 | error_prefix + 'Annotation "%s" has conflicting values ' | ||
132 | 1361 | 'for the earliest version: "%s" (from keyword arguments) ' | ||
133 | 1362 | 'and "%s" (defined explicitly).' % ( | ||
134 | 1363 | key, implicit_value, value)) | ||
135 | 1344 | stack[0][1].update(stack[1][1]) | 1364 | stack[0][1].update(stack[1][1]) |
136 | 1345 | stack.remove(stack[1]) | 1365 | stack.remove(stack[1]) |
137 | 1346 | 1366 | ||
138 | @@ -1349,6 +1369,19 @@ | |||
139 | 1349 | if stack[0][0] is None: | 1369 | if stack[0][0] is None: |
140 | 1350 | stack[0] = (earliest_version, stack[0][1]) | 1370 | stack[0] = (earliest_version, stack[0][1]) |
141 | 1351 | 1371 | ||
142 | 1372 | # If require_explicit_versions is set, make sure the first version | ||
143 | 1373 | # to set 'exported' also sets '_as_of_was_used'. | ||
144 | 1374 | if getUtility(IWebServiceConfiguration).require_explicit_versions: | ||
145 | 1375 | for version, annotations in stack: | ||
146 | 1376 | if annotations.get('exported', False): | ||
147 | 1377 | if not annotations.get('_as_of_was_used', False): | ||
148 | 1378 | raise ValueError( | ||
149 | 1379 | error_prefix + ( | ||
150 | 1380 | "Field was exported in version %s, but not" | ||
151 | 1381 | " by using as_of. The service configuration" | ||
152 | 1382 | " requires that you use as_of." % version)) | ||
153 | 1383 | break | ||
154 | 1384 | |||
155 | 1352 | # Make sure there is at most one mutator for the earliest version. | 1385 | # Make sure there is at most one mutator for the earliest version. |
156 | 1353 | # If there is one, move it from the mutator-specific dictionary to | 1386 | # If there is one, move it from the mutator-specific dictionary to |
157 | 1354 | # the normal tag stack. | 1387 | # the normal tag stack. |
158 | @@ -1386,8 +1419,8 @@ | |||
159 | 1386 | if stack[0][0] == earliest_version: | 1419 | if stack[0][0] == earliest_version: |
160 | 1387 | new_stack = [stack[0]] | 1420 | new_stack = [stack[0]] |
161 | 1388 | else: | 1421 | else: |
164 | 1389 | # The field is not initially published. | 1422 | # The field is not initially exported. |
165 | 1390 | new_stack = (earliest_version, dict(published=False)) | 1423 | new_stack = [(earliest_version, dict(exported=False))] |
166 | 1391 | most_recent_tags = new_stack[0][1] | 1424 | most_recent_tags = new_stack[0][1] |
167 | 1392 | most_recent_mutator_tags = earliest_mutator | 1425 | most_recent_mutator_tags = earliest_mutator |
168 | 1393 | for version in versions[1:]: | 1426 | for version in versions[1:]: |
169 | 1394 | 1427 | ||
170 | === modified file 'src/lazr/restful/docs/webservice-declarations.txt' | |||
171 | --- src/lazr/restful/docs/webservice-declarations.txt 2011-02-16 15:41:04 +0000 | |||
172 | +++ src/lazr/restful/docs/webservice-declarations.txt 2011-03-09 16:02:29 +0000 | |||
173 | @@ -847,6 +847,55 @@ | |||
174 | 847 | Generating the webservice | 847 | Generating the webservice |
175 | 848 | ========================= | 848 | ========================= |
176 | 849 | 849 | ||
177 | 850 | Setup | ||
178 | 851 | ----- | ||
179 | 852 | |||
180 | 853 | Before we can continue, we must define a web service configuration | ||
181 | 854 | object. Each web service needs to have one of these registered | ||
182 | 855 | utilities providing basic information about the web service. This one | ||
183 | 856 | is just a dummy. | ||
184 | 857 | |||
185 | 858 | >>> from lazr.restful.testing.helpers import TestWebServiceConfiguration | ||
186 | 859 | >>> from zope.component import provideUtility | ||
187 | 860 | >>> from lazr.restful.interfaces import IWebServiceConfiguration | ||
188 | 861 | >>> class MyWebServiceConfiguration(TestWebServiceConfiguration): | ||
189 | 862 | ... active_versions = ["beta", "1.0", "2.0", "3.0"] | ||
190 | 863 | ... last_version_with_mutator_named_operations = "1.0" | ||
191 | 864 | ... first_version_with_total_size_link = "2.0" | ||
192 | 865 | ... code_revision = "1.0b" | ||
193 | 866 | ... default_batch_size = 50 | ||
194 | 867 | >>> provideUtility(MyWebServiceConfiguration(), IWebServiceConfiguration) | ||
195 | 868 | |||
196 | 869 | We must also set up the ability to create versioned requests. This web | ||
197 | 870 | service has four versions: 'beta', '1.0', '2.0', and '3.0'. We'll | ||
198 | 871 | need a marker interface for every version, registered as a utility | ||
199 | 872 | under the name of the version. | ||
200 | 873 | |||
201 | 874 | Each version interface subclasses the previous version's | ||
202 | 875 | interface. This lets a request use a resource definition for the | ||
203 | 876 | previous version if it hasn't changed since then. | ||
204 | 877 | |||
205 | 878 | >>> from zope.component import getSiteManager | ||
206 | 879 | >>> from lazr.restful.interfaces import IWebServiceVersion | ||
207 | 880 | >>> class ITestServiceRequestBeta(IWebServiceVersion): | ||
208 | 881 | ... pass | ||
209 | 882 | >>> class ITestServiceRequest10(ITestServiceRequestBeta): | ||
210 | 883 | ... pass | ||
211 | 884 | >>> class ITestServiceRequest20(ITestServiceRequest10): | ||
212 | 885 | ... pass | ||
213 | 886 | >>> class ITestServiceRequest30(ITestServiceRequest20): | ||
214 | 887 | ... pass | ||
215 | 888 | >>> sm = getSiteManager() | ||
216 | 889 | >>> for marker, name in [(ITestServiceRequestBeta, 'beta'), | ||
217 | 890 | ... (ITestServiceRequest10, '1.0'), | ||
218 | 891 | ... (ITestServiceRequest20, '2.0'), | ||
219 | 892 | ... (ITestServiceRequest30, '3.0')]: | ||
220 | 893 | ... sm.registerUtility(marker, IWebServiceVersion, name=name) | ||
221 | 894 | |||
222 | 895 | >>> from lazr.restful.testing.webservice import FakeRequest | ||
223 | 896 | >>> request = FakeRequest(version='beta') | ||
224 | 897 | |||
225 | 898 | |||
226 | 850 | Entry | 899 | Entry |
227 | 851 | ----- | 900 | ----- |
228 | 852 | 901 | ||
229 | @@ -968,51 +1017,6 @@ | |||
230 | 968 | ... self.base_price = base_price | 1017 | ... self.base_price = base_price |
231 | 969 | ... self.inventory_number = inventory_number | 1018 | ... self.inventory_number = inventory_number |
232 | 970 | 1019 | ||
233 | 971 | Before we can continue, we must define a web service configuration | ||
234 | 972 | object. Each web service needs to have one of these registered | ||
235 | 973 | utilities providing basic information about the web service. This one | ||
236 | 974 | is just a dummy. | ||
237 | 975 | |||
238 | 976 | >>> from lazr.restful.testing.helpers import TestWebServiceConfiguration | ||
239 | 977 | >>> from zope.component import provideUtility | ||
240 | 978 | >>> from lazr.restful.interfaces import IWebServiceConfiguration | ||
241 | 979 | >>> class MyWebServiceConfiguration(TestWebServiceConfiguration): | ||
242 | 980 | ... active_versions = ["beta", "1.0", "2.0", "3.0"] | ||
243 | 981 | ... last_version_with_mutator_named_operations = "1.0" | ||
244 | 982 | ... first_version_with_total_size_link = "2.0" | ||
245 | 983 | ... code_revision = "1.0b" | ||
246 | 984 | ... default_batch_size = 50 | ||
247 | 985 | >>> provideUtility(MyWebServiceConfiguration(), IWebServiceConfiguration) | ||
248 | 986 | |||
249 | 987 | We must also set up the ability to create versioned requests. This web | ||
250 | 988 | service has four versions: 'beta', '1.0', '2.0', and '3.0'. We'll | ||
251 | 989 | need a marker interface for every version, registered as a utility | ||
252 | 990 | under the name of the version. | ||
253 | 991 | |||
254 | 992 | Each version interface subclasses the previous version's | ||
255 | 993 | interface. This lets a request use a resource definition for the | ||
256 | 994 | previous version if it hasn't changed since then. | ||
257 | 995 | |||
258 | 996 | >>> from zope.component import getSiteManager | ||
259 | 997 | >>> from lazr.restful.interfaces import IWebServiceVersion | ||
260 | 998 | >>> class ITestServiceRequestBeta(IWebServiceVersion): | ||
261 | 999 | ... pass | ||
262 | 1000 | >>> class ITestServiceRequest10(ITestServiceRequestBeta): | ||
263 | 1001 | ... pass | ||
264 | 1002 | >>> class ITestServiceRequest20(ITestServiceRequest10): | ||
265 | 1003 | ... pass | ||
266 | 1004 | >>> class ITestServiceRequest30(ITestServiceRequest20): | ||
267 | 1005 | ... pass | ||
268 | 1006 | >>> sm = getSiteManager() | ||
269 | 1007 | >>> for marker, name in [(ITestServiceRequestBeta, 'beta'), | ||
270 | 1008 | ... (ITestServiceRequest10, '1.0'), | ||
271 | 1009 | ... (ITestServiceRequest20, '2.0'), | ||
272 | 1010 | ... (ITestServiceRequest30, '3.0')]: | ||
273 | 1011 | ... sm.registerUtility(marker, IWebServiceVersion, name=name) | ||
274 | 1012 | |||
275 | 1013 | >>> from lazr.restful.testing.webservice import FakeRequest | ||
276 | 1014 | >>> request = FakeRequest(version='beta') | ||
277 | 1015 | |||
278 | 1016 | Now we can turn a Book object into something that implements | 1020 | Now we can turn a Book object into something that implements |
279 | 1017 | IBookEntry. | 1021 | IBookEntry. |
280 | 1018 | 1022 | ||
281 | @@ -1047,6 +1051,7 @@ | |||
282 | 1047 | ... | 1051 | ... |
283 | 1048 | TypeError: 'IBookSet' isn't exported as an entry. | 1052 | TypeError: 'IBookSet' isn't exported as an entry. |
284 | 1049 | 1053 | ||
285 | 1054 | |||
286 | 1050 | Collection | 1055 | Collection |
287 | 1051 | ---------- | 1056 | ---------- |
288 | 1052 | 1057 | ||
289 | 1053 | 1058 | ||
290 | === modified file 'src/lazr/restful/interfaces/_rest.py' | |||
291 | --- src/lazr/restful/interfaces/_rest.py 2011-03-02 18:49:23 +0000 | |||
292 | +++ src/lazr/restful/interfaces/_rest.py 2011-03-09 16:02:29 +0000 | |||
293 | @@ -514,6 +514,13 @@ | |||
294 | 514 | default = {} | 514 | default = {} |
295 | 515 | ) | 515 | ) |
296 | 516 | 516 | ||
297 | 517 | require_explicit_versions = Bool( | ||
298 | 518 | default=False, | ||
299 | 519 | description=u"""If true, each exported field and named | ||
300 | 520 | operation must explicitly declare the first version in which | ||
301 | 521 | it appears. If false, fields and operations are published in | ||
302 | 522 | all versions.""") | ||
303 | 523 | |||
304 | 517 | last_version_with_mutator_named_operations = TextLine( | 524 | last_version_with_mutator_named_operations = TextLine( |
305 | 518 | default=None, | 525 | default=None, |
306 | 519 | description=u"""In earlier versions of lazr.restful, mutator methods | 526 | description=u"""In earlier versions of lazr.restful, mutator methods |
307 | 520 | 527 | ||
308 | === modified file 'src/lazr/restful/testing/helpers.py' | |||
309 | --- src/lazr/restful/testing/helpers.py 2011-02-17 14:56:25 +0000 | |||
310 | +++ src/lazr/restful/testing/helpers.py 2011-03-09 16:02:29 +0000 | |||
311 | @@ -70,6 +70,7 @@ | |||
312 | 70 | code_revision = "1.0b" | 70 | code_revision = "1.0b" |
313 | 71 | default_batch_size = 50 | 71 | default_batch_size = 50 |
314 | 72 | hostname = 'example.com' | 72 | hostname = 'example.com' |
315 | 73 | require_explicit_versions = False | ||
316 | 73 | 74 | ||
317 | 74 | def get_request_user(self): | 75 | def get_request_user(self): |
318 | 75 | return 'A user' | 76 | return 'A user' |
319 | 76 | 77 | ||
320 | === modified file 'src/lazr/restful/testing/webservice.py' | |||
321 | --- src/lazr/restful/testing/webservice.py 2011-02-17 14:56:25 +0000 | |||
322 | +++ src/lazr/restful/testing/webservice.py 2011-03-09 16:02:29 +0000 | |||
323 | @@ -28,8 +28,9 @@ | |||
324 | 28 | import simplejson | 28 | import simplejson |
325 | 29 | import sys | 29 | import sys |
326 | 30 | from types import ModuleType | 30 | from types import ModuleType |
327 | 31 | import unittest | ||
328 | 31 | import urllib | 32 | import urllib |
330 | 32 | import unittest | 33 | |
331 | 33 | from urlparse import urljoin | 34 | from urlparse import urljoin |
332 | 34 | import wsgi_intercept | 35 | import wsgi_intercept |
333 | 35 | 36 | ||
334 | @@ -448,15 +449,15 @@ | |||
335 | 448 | """A marker interface for requests to the '2.0' web service.""" | 449 | """A marker interface for requests to the '2.0' web service.""" |
336 | 449 | 450 | ||
337 | 450 | 451 | ||
340 | 451 | class WebServiceTestCase(CleanUp, unittest.TestCase): | 452 | class TestCaseWithWebServiceFixtures(CleanUp, unittest.TestCase): |
341 | 452 | """A test case for web service operations.""" | 453 | """A test case that needs to have some aspects of a web service set up. |
342 | 453 | 454 | ||
344 | 454 | testmodule_objects = [] | 455 | If the test case is testing a real web service, use |
345 | 456 | WebServiceTestCase instead. | ||
346 | 457 | """ | ||
347 | 455 | 458 | ||
348 | 456 | def setUp(self): | 459 | def setUp(self): |
352 | 457 | """Set the component registry with the given model.""" | 460 | super(TestCaseWithWebServiceFixtures, self).setUp() |
350 | 458 | super(WebServiceTestCase, self).setUp() | ||
351 | 459 | |||
353 | 460 | # Register a simple configuration object. | 461 | # Register a simple configuration object. |
354 | 461 | webservice_configuration = WebServiceTestConfiguration() | 462 | webservice_configuration = WebServiceTestConfiguration() |
355 | 462 | sm = getGlobalSiteManager() | 463 | sm = getGlobalSiteManager() |
356 | @@ -471,7 +472,18 @@ | |||
357 | 471 | sm.registerUtility( | 472 | sm.registerUtility( |
358 | 472 | IWebServiceTestRequest20, IWebServiceVersion, name='2.0') | 473 | IWebServiceTestRequest20, IWebServiceVersion, name='2.0') |
359 | 473 | 474 | ||
360 | 475 | |||
361 | 476 | class WebServiceTestCase(TestCaseWithWebServiceFixtures): | ||
362 | 477 | """A test case for web service operations.""" | ||
363 | 478 | |||
364 | 479 | testmodule_objects = [] | ||
365 | 480 | |||
366 | 481 | def setUp(self): | ||
367 | 482 | """Set the component registry with the given model.""" | ||
368 | 483 | super(WebServiceTestCase, self).setUp() | ||
369 | 484 | |||
370 | 474 | # Register a service root resource | 485 | # Register a service root resource |
371 | 486 | sm = getGlobalSiteManager() | ||
372 | 475 | service_root = ServiceRootResource() | 487 | service_root = ServiceRootResource() |
373 | 476 | sm.registerUtility(service_root, IServiceRootResource) | 488 | sm.registerUtility(service_root, IServiceRootResource) |
374 | 477 | 489 | ||
375 | 478 | 490 | ||
376 | === modified file 'src/lazr/restful/tests/test_declarations.py' | |||
377 | --- src/lazr/restful/tests/test_declarations.py 2011-01-21 21:08:43 +0000 | |||
378 | +++ src/lazr/restful/tests/test_declarations.py 2011-03-09 16:02:29 +0000 | |||
379 | @@ -1,5 +1,3 @@ | |||
380 | 1 | import unittest | ||
381 | 2 | |||
382 | 3 | from zope.component import ( | 1 | from zope.component import ( |
383 | 4 | adapts, getMultiAdapter, getSiteManager, getUtility, provideUtility) | 2 | adapts, getMultiAdapter, getSiteManager, getUtility, provideUtility) |
384 | 5 | from zope.component.interfaces import ComponentLookupError | 3 | from zope.component.interfaces import ComponentLookupError |
385 | @@ -10,9 +8,15 @@ | |||
386 | 10 | from zope.security.management import endInteraction, newInteraction | 8 | from zope.security.management import endInteraction, newInteraction |
387 | 11 | 9 | ||
388 | 12 | from lazr.restful.declarations import ( | 10 | from lazr.restful.declarations import ( |
392 | 13 | export_as_webservice_entry, exported, export_read_operation, | 11 | export_as_webservice_entry, |
393 | 14 | export_write_operation, mutator_for, operation_for_version, | 12 | exported, |
394 | 15 | operation_parameters) | 13 | export_read_operation, |
395 | 14 | export_write_operation, | ||
396 | 15 | generate_entry_interfaces, | ||
397 | 16 | mutator_for, | ||
398 | 17 | operation_for_version, | ||
399 | 18 | operation_parameters, | ||
400 | 19 | ) | ||
401 | 16 | from lazr.restful.fields import Reference | 20 | from lazr.restful.fields import Reference |
402 | 17 | from lazr.restful.interfaces import ( | 21 | from lazr.restful.interfaces import ( |
403 | 18 | IEntry, IResourceGETOperation, IWebServiceConfiguration, | 22 | IEntry, IResourceGETOperation, IWebServiceConfiguration, |
404 | @@ -22,32 +26,30 @@ | |||
405 | 22 | AttemptToContributeToNonExportedInterface, | 26 | AttemptToContributeToNonExportedInterface, |
406 | 23 | ConflictInContributingInterfaces, find_interfaces_and_contributors) | 27 | ConflictInContributingInterfaces, find_interfaces_and_contributors) |
407 | 24 | from lazr.restful._resource import EntryAdapterUtility, EntryResource | 28 | from lazr.restful._resource import EntryAdapterUtility, EntryResource |
409 | 25 | from lazr.restful.testing.webservice import FakeRequest | 29 | from lazr.restful.testing.webservice import ( |
410 | 30 | FakeRequest, | ||
411 | 31 | TestCaseWithWebServiceFixtures, | ||
412 | 32 | ) | ||
413 | 26 | from lazr.restful.testing.helpers import ( | 33 | from lazr.restful.testing.helpers import ( |
414 | 27 | create_test_module, TestWebServiceConfiguration, register_test_module) | 34 | create_test_module, TestWebServiceConfiguration, register_test_module) |
415 | 28 | 35 | ||
416 | 29 | 36 | ||
418 | 30 | class ContributingInterfacesTestCase(unittest.TestCase): | 37 | class ContributingInterfacesTestCase(TestCaseWithWebServiceFixtures): |
419 | 31 | """Tests for interfaces that contribute fields/operations to others.""" | 38 | """Tests for interfaces that contribute fields/operations to others.""" |
420 | 32 | 39 | ||
421 | 33 | def setUp(self): | 40 | def setUp(self): |
424 | 34 | provideUtility( | 41 | super(ContributingInterfacesTestCase, self).setUp() |
423 | 35 | TestWebServiceConfiguration(), IWebServiceConfiguration) | ||
425 | 36 | sm = getSiteManager() | 42 | sm = getSiteManager() |
426 | 37 | sm.registerUtility( | ||
427 | 38 | ITestServiceRequestBeta, IWebServiceVersion, name='beta') | ||
428 | 39 | sm.registerUtility( | ||
429 | 40 | ITestServiceRequest10, IWebServiceVersion, name='1.0') | ||
430 | 41 | sm.registerAdapter(ProductToHasBugsAdapter) | 43 | sm.registerAdapter(ProductToHasBugsAdapter) |
431 | 42 | sm.registerAdapter(ProjectToHasBugsAdapter) | 44 | sm.registerAdapter(ProjectToHasBugsAdapter) |
432 | 43 | sm.registerAdapter(ProductToHasBranchesAdapter) | 45 | sm.registerAdapter(ProductToHasBranchesAdapter) |
433 | 44 | sm.registerAdapter(DummyFieldMarshaller) | 46 | sm.registerAdapter(DummyFieldMarshaller) |
434 | 45 | self.beta_request = FakeRequest(version='beta') | ||
435 | 46 | alsoProvides( | ||
436 | 47 | self.beta_request, getUtility(IWebServiceVersion, name='beta')) | ||
437 | 48 | self.one_zero_request = FakeRequest(version='1.0') | 47 | self.one_zero_request = FakeRequest(version='1.0') |
438 | 49 | alsoProvides( | 48 | alsoProvides( |
439 | 50 | self.one_zero_request, getUtility(IWebServiceVersion, name='1.0')) | 49 | self.one_zero_request, getUtility(IWebServiceVersion, name='1.0')) |
440 | 50 | self.two_zero_request = FakeRequest(version='2.0') | ||
441 | 51 | alsoProvides( | ||
442 | 52 | self.two_zero_request, getUtility(IWebServiceVersion, name='2.0')) | ||
443 | 51 | self.product = Product() | 53 | self.product = Product() |
444 | 52 | self.project = Project() | 54 | self.project = Project() |
445 | 53 | 55 | ||
446 | @@ -59,7 +61,7 @@ | |||
447 | 59 | # access .bug_count. | 61 | # access .bug_count. |
448 | 60 | self.product._bug_count = 10 | 62 | self.product._bug_count = 10 |
449 | 61 | register_test_module('testmod', IProduct, IHasBugs) | 63 | register_test_module('testmod', IProduct, IHasBugs) |
451 | 62 | adapter = getMultiAdapter((self.product, self.beta_request), IEntry) | 64 | adapter = getMultiAdapter((self.product, self.one_zero_request), IEntry) |
452 | 63 | self.assertEqual(adapter.bug_count, 10) | 65 | self.assertEqual(adapter.bug_count, 10) |
453 | 64 | 66 | ||
454 | 65 | def test_operations(self): | 67 | def test_operations(self): |
455 | @@ -68,46 +70,46 @@ | |||
456 | 68 | self.product._bug_count = 10 | 70 | self.product._bug_count = 10 |
457 | 69 | register_test_module('testmod', IProduct, IHasBugs) | 71 | register_test_module('testmod', IProduct, IHasBugs) |
458 | 70 | adapter = getMultiAdapter( | 72 | adapter = getMultiAdapter( |
460 | 71 | (self.product, self.beta_request), | 73 | (self.product, self.one_zero_request), |
461 | 72 | IResourceGETOperation, name='getBugsCount') | 74 | IResourceGETOperation, name='getBugsCount') |
462 | 73 | self.assertEqual(adapter(), '10') | 75 | self.assertEqual(adapter(), '10') |
463 | 74 | 76 | ||
464 | 75 | def test_contributing_interface_with_differences_between_versions(self): | 77 | def test_contributing_interface_with_differences_between_versions(self): |
468 | 76 | # In the 'beta' version, IHasBranches.development_branches is exported | 78 | # In the '1.0' version, IHasBranches.development_branches is exported |
469 | 77 | # with its original name whereas for the '1.0' version it's exported | 79 | # with its original name whereas for the '2.0' version it's exported |
470 | 78 | # as 'development_branch_10'. | 80 | # as 'development_branch_20'. |
471 | 79 | self.product._dev_branch = Branch('A product branch') | 81 | self.product._dev_branch = Branch('A product branch') |
472 | 80 | register_test_module('testmod', IProduct, IHasBranches) | 82 | register_test_module('testmod', IProduct, IHasBranches) |
474 | 81 | adapter = getMultiAdapter((self.product, self.beta_request), IEntry) | 83 | adapter = getMultiAdapter((self.product, self.one_zero_request), IEntry) |
475 | 82 | self.assertEqual(adapter.development_branch, self.product._dev_branch) | 84 | self.assertEqual(adapter.development_branch, self.product._dev_branch) |
476 | 83 | 85 | ||
477 | 84 | adapter = getMultiAdapter( | 86 | adapter = getMultiAdapter( |
479 | 85 | (self.product, self.one_zero_request), IEntry) | 87 | (self.product, self.two_zero_request), IEntry) |
480 | 86 | self.assertEqual( | 88 | self.assertEqual( |
482 | 87 | adapter.development_branch_10, self.product._dev_branch) | 89 | adapter.development_branch_20, self.product._dev_branch) |
483 | 88 | 90 | ||
484 | 89 | def test_mutator_for_just_one_version(self): | 91 | def test_mutator_for_just_one_version(self): |
487 | 90 | # On the 'beta' version, IHasBranches contributes a read only | 92 | # On the '1.0' version, IHasBranches contributes a read only |
488 | 91 | # development_branch field, but on version '1.0' that field can be | 93 | # development_branch field, but on version '2.0' that field can be |
489 | 92 | # modified as we define a mutator for it. | 94 | # modified as we define a mutator for it. |
490 | 93 | self.product._dev_branch = Branch('A product branch') | 95 | self.product._dev_branch = Branch('A product branch') |
491 | 94 | register_test_module('testmod', IProduct, IHasBranches) | 96 | register_test_module('testmod', IProduct, IHasBranches) |
493 | 95 | adapter = getMultiAdapter((self.product, self.beta_request), IEntry) | 97 | adapter = getMultiAdapter((self.product, self.one_zero_request), IEntry) |
494 | 96 | try: | 98 | try: |
495 | 97 | adapter.development_branch = None | 99 | adapter.development_branch = None |
496 | 98 | except AttributeError: | 100 | except AttributeError: |
497 | 99 | pass | 101 | pass |
498 | 100 | else: | 102 | else: |
499 | 101 | self.fail('IHasBranches.development_branch should be read-only ' | 103 | self.fail('IHasBranches.development_branch should be read-only ' |
501 | 102 | 'on the beta version') | 104 | 'on the 1.0 version') |
502 | 103 | 105 | ||
503 | 104 | adapter = getMultiAdapter( | 106 | adapter = getMultiAdapter( |
510 | 105 | (self.product, self.one_zero_request), IEntry) | 107 | (self.product, self.two_zero_request), IEntry) |
511 | 106 | self.assertEqual( | 108 | self.assertEqual( |
512 | 107 | adapter.development_branch_10, self.product._dev_branch) | 109 | adapter.development_branch_20, self.product._dev_branch) |
513 | 108 | adapter.development_branch_10 = None | 110 | adapter.development_branch_20 = None |
514 | 109 | self.assertEqual( | 111 | self.assertEqual( |
515 | 110 | adapter.development_branch_10, None) | 112 | adapter.development_branch_20, None) |
516 | 111 | 113 | ||
517 | 112 | def test_contributing_to_multiple_interfaces(self): | 114 | def test_contributing_to_multiple_interfaces(self): |
518 | 113 | # Check that the webservice adapter for both IProduct and IProject | 115 | # Check that the webservice adapter for both IProduct and IProject |
519 | @@ -115,10 +117,10 @@ | |||
520 | 115 | self.product._bug_count = 10 | 117 | self.product._bug_count = 10 |
521 | 116 | self.project._bug_count = 100 | 118 | self.project._bug_count = 100 |
522 | 117 | register_test_module('testmod', IProduct, IProject, IHasBugs) | 119 | register_test_module('testmod', IProduct, IProject, IHasBugs) |
524 | 118 | adapter = getMultiAdapter((self.product, self.beta_request), IEntry) | 120 | adapter = getMultiAdapter((self.product, self.one_zero_request), IEntry) |
525 | 119 | self.assertEqual(adapter.bug_count, 10) | 121 | self.assertEqual(adapter.bug_count, 10) |
526 | 120 | 122 | ||
528 | 121 | adapter = getMultiAdapter((self.project, self.beta_request), IEntry) | 123 | adapter = getMultiAdapter((self.project, self.one_zero_request), IEntry) |
529 | 122 | self.assertEqual(adapter.bug_count, 100) | 124 | self.assertEqual(adapter.bug_count, 100) |
530 | 123 | 125 | ||
531 | 124 | def test_multiple_contributing_interfaces(self): | 126 | def test_multiple_contributing_interfaces(self): |
532 | @@ -127,7 +129,7 @@ | |||
533 | 127 | self.product._bug_count = 10 | 129 | self.product._bug_count = 10 |
534 | 128 | self.product._dev_branch = Branch('A product branch') | 130 | self.product._dev_branch = Branch('A product branch') |
535 | 129 | register_test_module('testmod', IProduct, IHasBugs, IHasBranches) | 131 | register_test_module('testmod', IProduct, IHasBugs, IHasBranches) |
537 | 130 | adapter = getMultiAdapter((self.product, self.beta_request), IEntry) | 132 | adapter = getMultiAdapter((self.product, self.one_zero_request), IEntry) |
538 | 131 | self.assertEqual(adapter.bug_count, 10) | 133 | self.assertEqual(adapter.bug_count, 10) |
539 | 132 | self.assertEqual(adapter.development_branch, self.product._dev_branch) | 134 | self.assertEqual(adapter.development_branch, self.product._dev_branch) |
540 | 133 | 135 | ||
541 | @@ -139,7 +141,7 @@ | |||
542 | 139 | class Empty: | 141 | class Empty: |
543 | 140 | implements(IEmpty) | 142 | implements(IEmpty) |
544 | 141 | register_test_module('testmod', IEmpty) | 143 | register_test_module('testmod', IEmpty) |
546 | 142 | entry_resource = EntryResource(Empty(), self.beta_request) | 144 | entry_resource = EntryResource(Empty(), self.one_zero_request) |
547 | 143 | self.assertEquals({}, entry_resource.entry._orig_interfaces) | 145 | self.assertEquals({}, entry_resource.entry._orig_interfaces) |
548 | 144 | self.assertEquals([], entry_resource.redacted_fields) | 146 | self.assertEquals([], entry_resource.redacted_fields) |
549 | 145 | 147 | ||
550 | @@ -148,7 +150,7 @@ | |||
551 | 148 | # interface where the field is defined and adapt the context to that | 150 | # interface where the field is defined and adapt the context to that |
552 | 149 | # interface before accessing that field. | 151 | # interface before accessing that field. |
553 | 150 | register_test_module('testmod', IProduct, IHasBugs, IHasBranches) | 152 | register_test_module('testmod', IProduct, IHasBugs, IHasBranches) |
555 | 151 | entry_resource = EntryResource(self.product, self.beta_request) | 153 | entry_resource = EntryResource(self.product, self.one_zero_request) |
556 | 152 | self.assertEquals([], entry_resource.redacted_fields) | 154 | self.assertEquals([], entry_resource.redacted_fields) |
557 | 153 | 155 | ||
558 | 154 | def test_redacted_fields_with_permission_checker(self): | 156 | def test_redacted_fields_with_permission_checker(self): |
559 | @@ -161,7 +163,7 @@ | |||
560 | 161 | secure_product = ProxyFactory( | 163 | secure_product = ProxyFactory( |
561 | 162 | self.product, | 164 | self.product, |
562 | 163 | checker=MultiChecker([(IProduct, 'zope.Public')])) | 165 | checker=MultiChecker([(IProduct, 'zope.Public')])) |
564 | 164 | entry_resource = EntryResource(secure_product, self.beta_request) | 166 | entry_resource = EntryResource(secure_product, self.one_zero_request) |
565 | 165 | self.assertEquals([], entry_resource.redacted_fields) | 167 | self.assertEquals([], entry_resource.redacted_fields) |
566 | 166 | finally: | 168 | finally: |
567 | 167 | endInteraction() | 169 | endInteraction() |
568 | @@ -183,7 +185,7 @@ | |||
569 | 183 | register_test_module('testmod', IProduct, IHasBranches) | 185 | register_test_module('testmod', IProduct, IHasBranches) |
570 | 184 | self.assertRaises( | 186 | self.assertRaises( |
571 | 185 | ComponentLookupError, | 187 | ComponentLookupError, |
573 | 186 | getMultiAdapter, (dummy, self.beta_request), IEntry) | 188 | getMultiAdapter, (dummy, self.one_zero_request), IEntry) |
574 | 187 | 189 | ||
575 | 188 | def test_cannot_contribute_to_non_exported_interface(self): | 190 | def test_cannot_contribute_to_non_exported_interface(self): |
576 | 189 | # A contributing interface can only contribute to exported interfaces. | 191 | # A contributing interface can only contribute to exported interfaces. |
577 | @@ -218,7 +220,7 @@ | |||
578 | 218 | # different adapters, its type name is that of the main interface and | 220 | # different adapters, its type name is that of the main interface and |
579 | 219 | # not one of its contributors. | 221 | # not one of its contributors. |
580 | 220 | register_test_module('testmod', IProduct, IHasBugs) | 222 | register_test_module('testmod', IProduct, IHasBugs) |
582 | 221 | adapter = getMultiAdapter((self.product, self.beta_request), IEntry) | 223 | adapter = getMultiAdapter((self.product, self.one_zero_request), IEntry) |
583 | 222 | self.assertEqual( | 224 | self.assertEqual( |
584 | 223 | 'product', EntryAdapterUtility(adapter.__class__).singular_type) | 225 | 'product', EntryAdapterUtility(adapter.__class__).singular_type) |
585 | 224 | 226 | ||
586 | @@ -308,13 +310,13 @@ | |||
587 | 308 | not_exported = TextLine(title=u'Not exported') | 310 | not_exported = TextLine(title=u'Not exported') |
588 | 309 | development_branch = exported( | 311 | development_branch = exported( |
589 | 310 | Reference(schema=IBranch, readonly=True), | 312 | Reference(schema=IBranch, readonly=True), |
592 | 311 | ('1.0', dict(exported_as='development_branch_10')), | 313 | ('2.0', dict(exported_as='development_branch_20')), |
593 | 312 | ('beta', dict(exported_as='development_branch'))) | 314 | ('1.0', dict(exported_as='development_branch'))) |
594 | 313 | 315 | ||
595 | 314 | @mutator_for(development_branch) | 316 | @mutator_for(development_branch) |
596 | 315 | @export_write_operation() | 317 | @export_write_operation() |
597 | 316 | @operation_parameters(value=TextLine()) | 318 | @operation_parameters(value=TextLine()) |
599 | 317 | @operation_for_version('1.0') | 319 | @operation_for_version('2.0') |
600 | 318 | def set_dev_branch(value): | 320 | def set_dev_branch(value): |
601 | 319 | pass | 321 | pass |
602 | 320 | 322 | ||
603 | @@ -342,7 +344,135 @@ | |||
604 | 342 | adapts(Interface, IHTTPRequest) | 344 | adapts(Interface, IHTTPRequest) |
605 | 343 | 345 | ||
606 | 344 | 346 | ||
611 | 345 | class ITestServiceRequestBeta(IWebServiceVersion): | 347 | # Classes for TestReqireExplicitVersions |
612 | 346 | pass | 348 | |
613 | 347 | class ITestServiceRequest10(IWebServiceVersion): | 349 | class IFieldExportedWithoutAsOf(Interface): |
614 | 348 | pass | 350 | export_as_webservice_entry() |
615 | 351 | |||
616 | 352 | field = exported(TextLine(), exported=True) | ||
617 | 353 | |||
618 | 354 | |||
619 | 355 | class IFieldExportedToEarliestVersionUsingAsOf(Interface): | ||
620 | 356 | export_as_webservice_entry() | ||
621 | 357 | |||
622 | 358 | field = exported(TextLine(), as_of='1.0') | ||
623 | 359 | |||
624 | 360 | |||
625 | 361 | class IFieldExportedToLatestVersionUsingAsOf(Interface): | ||
626 | 362 | export_as_webservice_entry() | ||
627 | 363 | |||
628 | 364 | field = exported(TextLine(), as_of='2.0') | ||
629 | 365 | |||
630 | 366 | |||
631 | 367 | class IFieldDefiningAttributesBeforeAsOf(Interface): | ||
632 | 368 | export_as_webservice_entry() | ||
633 | 369 | |||
634 | 370 | field = exported(TextLine(), ('1.0', dict(exported=True)), | ||
635 | 371 | as_of='2.0') | ||
636 | 372 | |||
637 | 373 | class IFieldAsOfNonexistentVersion(Interface): | ||
638 | 374 | export_as_webservice_entry() | ||
639 | 375 | |||
640 | 376 | field = exported(TextLine(), as_of='nosuchversion') | ||
641 | 377 | |||
642 | 378 | |||
643 | 379 | class IFieldDoubleDefinition(Interface): | ||
644 | 380 | export_as_webservice_entry() | ||
645 | 381 | |||
646 | 382 | field = exported(TextLine(), ('2.0', dict(exported_as='name2')), | ||
647 | 383 | exported_as='name2', as_of='2.0') | ||
648 | 384 | |||
649 | 385 | |||
650 | 386 | class TestRequireExplicitVersions(TestCaseWithWebServiceFixtures): | ||
651 | 387 | """Test behavior when require_explicit_versions is True.""" | ||
652 | 388 | |||
653 | 389 | def setUp(self): | ||
654 | 390 | super(TestRequireExplicitVersions, self).setUp() | ||
655 | 391 | self.utility = getUtility(IWebServiceConfiguration) | ||
656 | 392 | self.utility.require_explicit_versions = True | ||
657 | 393 | |||
658 | 394 | def _assertRaises(self, exception, func, *args, **kwargs): | ||
659 | 395 | # Approximate the behavior of testtools assertRaises, which | ||
660 | 396 | # returns the raised exception. I couldn't get | ||
661 | 397 | # testtools.TestCase to play nicely with zope's Cleanup class. | ||
662 | 398 | self.assertRaises(exception, func, *args, **kwargs) | ||
663 | 399 | try: | ||
664 | 400 | func(*args, **kwargs) | ||
665 | 401 | except Exception, e: | ||
666 | 402 | return e | ||
667 | 403 | |||
668 | 404 | def test_field_exported_as_of_earlier_version_is_exported_in_subsequent_versions(self): | ||
669 | 405 | |||
670 | 406 | interfaces = generate_entry_interfaces( | ||
671 | 407 | IFieldExportedToEarliestVersionUsingAsOf, [], | ||
672 | 408 | *self.utility.active_versions) | ||
673 | 409 | interface_10 = interfaces[0][1] | ||
674 | 410 | interface_20 = interfaces[1][1] | ||
675 | 411 | self.assertEquals(interface_10.names(), ['field']) | ||
676 | 412 | self.assertEquals(interface_20.names(), ['field']) | ||
677 | 413 | |||
678 | 414 | def test_field_exported_as_of_later_version_is_not_exported_in_earlier_versions(self): | ||
679 | 415 | |||
680 | 416 | interfaces = generate_entry_interfaces( | ||
681 | 417 | IFieldExportedToLatestVersionUsingAsOf, [], | ||
682 | 418 | *self.utility.active_versions) | ||
683 | 419 | interface_10 = interfaces[0][1] | ||
684 | 420 | interface_20 = interfaces[1][1] | ||
685 | 421 | self.assertEquals(interface_10.names(), []) | ||
686 | 422 | self.assertEquals(interface_20.names(), ['field']) | ||
687 | 423 | |||
688 | 424 | def test_field_not_exported_using_as_of_fails(self): | ||
689 | 425 | exception = self._assertRaises( | ||
690 | 426 | ValueError, generate_entry_interfaces, | ||
691 | 427 | IFieldExportedWithoutAsOf, [], *self.utility.active_versions) | ||
692 | 428 | self.assertEquals( | ||
693 | 429 | str(exception), | ||
694 | 430 | ('Field "field" in interface "IFieldExportedWithoutAsOf": ' | ||
695 | 431 | 'Field was exported in version 1.0, but not by using as_of. ' | ||
696 | 432 | 'The service configuration requires that you use as_of.') | ||
697 | 433 | ) | ||
698 | 434 | |||
699 | 435 | |||
700 | 436 | def test_field_cannot_be_both_exported_and_not_exported(self): | ||
701 | 437 | # If you use the as_of keyword argument, you can't also set | ||
702 | 438 | # the exported keyword argument to False. | ||
703 | 439 | exception = self._assertRaises( | ||
704 | 440 | ValueError, exported, TextLine(), as_of='1.0', exported=False) | ||
705 | 441 | self.assertEquals( | ||
706 | 442 | str(exception), | ||
707 | 443 | ('as_of=1.0 says to export TextLine, but exported=False ' | ||
708 | 444 | 'says not to.')) | ||
709 | 445 | |||
710 | 446 | def test_field_exported_as_of_nonexistent_version_fails(self): | ||
711 | 447 | exception = self._assertRaises( | ||
712 | 448 | ValueError, generate_entry_interfaces, | ||
713 | 449 | IFieldAsOfNonexistentVersion, [], | ||
714 | 450 | *self.utility.active_versions) | ||
715 | 451 | self.assertEquals( | ||
716 | 452 | str(exception), | ||
717 | 453 | ('Field "field" in interface "IFieldAsOfNonexistentVersion": ' | ||
718 | 454 | 'Unrecognized version "nosuchversion".')) | ||
719 | 455 | |||
720 | 456 | def test_field_exported_with_duplicate_attributes_fails(self): | ||
721 | 457 | # You can't provide a dictionary of attributes for the | ||
722 | 458 | # version specified in as_of. | ||
723 | 459 | exception = self._assertRaises( | ||
724 | 460 | ValueError, generate_entry_interfaces, | ||
725 | 461 | IFieldDoubleDefinition, [], *self.utility.active_versions) | ||
726 | 462 | self.assertEquals( | ||
727 | 463 | str(exception), | ||
728 | 464 | ('Field "field" in interface "IFieldDoubleDefinition": ' | ||
729 | 465 | 'Duplicate annotations for version "2.0".')) | ||
730 | 466 | |||
731 | 467 | def test_field_with_annotations_that_precede_as_of_fails(self): | ||
732 | 468 | # You can't provide a dictionary of attributes for a version | ||
733 | 469 | # preceding the version specified in as_of. | ||
734 | 470 | exception = self._assertRaises( | ||
735 | 471 | ValueError, generate_entry_interfaces, | ||
736 | 472 | IFieldDefiningAttributesBeforeAsOf, [], | ||
737 | 473 | *self.utility.active_versions) | ||
738 | 474 | self.assertEquals( | ||
739 | 475 | str(exception), | ||
740 | 476 | ('Field "field" in interface ' | ||
741 | 477 | '"IFieldDefiningAttributesBeforeAsOf": Version "1.0" defined ' | ||
742 | 478 | 'after the later version "2.0".')) |
I still think it would be nicer to allow a default to be provided for exported_as, per our IRC discussion, but it's not a deal-breaker.
There are some extra blank lines at 669 and 679, and it would be nice to document every test.