Merge lp:~mithrandi/txfluiddb/object-operations into lp:~txfluiddb-maint/txfluiddb/trunk-obsolete-1.6
- object-operations
- Merge into trunk-obsolete-1.6
Status: | Merged |
---|---|
Merged at revision: | 3 |
Proposed branch: | lp:~mithrandi/txfluiddb/object-operations |
Merge into: | lp:~txfluiddb-maint/txfluiddb/trunk-obsolete-1.6 |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~mithrandi/txfluiddb/object-operations |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tristan Seligmann | Needs Resubmitting | ||
Jonathan Jacobs (community) | Needs Fixing | ||
Review via email: mp+10500@code.launchpad.net |
Commit message
Description of the change
Tristan Seligmann (mithrandi) wrote : | # |
Jonathan Jacobs (jjacobs) wrote : | # |
1. `Endpoint.submit`'s parameters need documentation, specifically the "data" parameter probably needs some careful and clear documentation.
2. The `agent` parameter to `getPage` should probably include a version.
3. There are some klaxons going off in `Object.get`. You test for the "valueType" key, and then read "valueEncoding", test it against a value and then create a blob with "valueType". It looks like "valueEncoding" was a brainfart. Also, why not use the "encoding" value (that you tested) as the parameter to `decode`? See point 5.
4. `Object.query`'s return value is not documented.
5. The description of the return value for `Object.getTags` is slightly confusing.
6. There isn't any test coverage for the potential exception / errback in `Object.get`.
7. `ObjectTests.
8. In "ObjectTests", `test_setTag`, `test_setBlob`, `test_createObj
9. There are a handful of lines longer than 80 columns.
Jonathan Jacobs (jjacobs) wrote : | # |
> parameter to `decode`? See point 5.
I meant point 6.
- 13. By Tristan Seligmann <tristan@viridian>
-
Improve Endpoint.submit documentation.
- 14. By Tristan Seligmann <tristan@viridian>
-
Document Object.query return value.
- 15. By Tristan Seligmann <tristan@viridian>
-
Fix test docstring.
- 16. By Tristan Seligmann <tristan@viridian>
-
Tests for encoding stuff.
- 17. By Tristan Seligmann
-
Test unsupported encodings.
- 18. By Tristan Seligmann
-
Split long lines.
Tristan Seligmann (mithrandi) wrote : | # |
I believe I've addressed all of these points except for point 8; I'm not sure how to write a useful docstring for those tests. Any suggestions?
Tristan Seligmann (mithrandi) wrote : | # |
Testing something.
Jonathan Jacobs (jjacobs) wrote : | # |
> I believe I've addressed all of these points except for point 8; I'm not sure
> how to write a useful docstring for those tests. Any suggestions?
My only suggestion is the obvious one, attempt to concisely explain the desired behaviour of the code. What is supposed to happen? etc.
I noticed that several other tests are missing docstrings too.
- 19. By Tristan Seligmann
-
Docstrings.
Preview Diff
1 | === modified file 'LICENSE' | |||
2 | --- LICENSE 2009-08-17 23:05:23 +0000 | |||
3 | +++ LICENSE 2009-08-21 00:05:49 +0000 | |||
4 | @@ -1,4 +1,4 @@ | |||
6 | 1 | Copyright (c) 2009 Tristan Seligmann | 1 | Copyright © 2009 Tristan Seligmann |
7 | 2 | 2 | ||
8 | 3 | Permission is hereby granted, free of charge, to any person obtaining | 3 | Permission is hereby granted, free of charge, to any person obtaining |
9 | 4 | a copy of this software and associated documentation files (the | 4 | a copy of this software and associated documentation files (the |
10 | 5 | 5 | ||
11 | === modified file 'txfluiddb/client.py' | |||
12 | --- txfluiddb/client.py 2009-08-19 20:33:58 +0000 | |||
13 | +++ txfluiddb/client.py 2009-08-21 00:42:26 +0000 | |||
14 | @@ -15,7 +15,7 @@ | |||
15 | 15 | @type components: C{list} of C{unicode} | 15 | @type components: C{list} of C{unicode} |
16 | 16 | @ivar components: A list of path components for this identifier. | 16 | @ivar components: A list of path components for this identifier. |
17 | 17 | 17 | ||
19 | 18 | @type collectionName: C{str} | 18 | @type collectionName: C{unicode} |
20 | 19 | @cvar collectionName: The top-level path component under which these | 19 | @cvar collectionName: The top-level path component under which these |
21 | 20 | identifiers exist. | 20 | identifiers exist. |
22 | 21 | 21 | ||
23 | @@ -66,13 +66,19 @@ | |||
24 | 66 | return u'/'.join(self.components) | 66 | return u'/'.join(self.components) |
25 | 67 | 67 | ||
26 | 68 | 68 | ||
28 | 69 | def getURL(self, endpoint, prefix=None): | 69 | def getURL(self, endpoint, prefix=None, suffix=[]): |
29 | 70 | """ | 70 | """ |
30 | 71 | Get the URL for this path as accessed through the given endpoint. | 71 | Get the URL for this path as accessed through the given endpoint. |
31 | 72 | 72 | ||
32 | 73 | @type endpoint: L{Endpoint} | 73 | @type endpoint: L{Endpoint} |
33 | 74 | @param endpoint: The endpoint to operate through. | 74 | @param endpoint: The endpoint to operate through. |
34 | 75 | 75 | ||
35 | 76 | @type prefix: C{list} of C{unicode} | ||
36 | 77 | @param prefix: A list of components to prepend before this path. | ||
37 | 78 | |||
38 | 79 | @type suffix: C{list} of C{unicode} | ||
39 | 80 | @param suffix: A list of components to append after this path. | ||
40 | 81 | |||
41 | 76 | @rtype: C{str} | 82 | @rtype: C{str} |
42 | 77 | """ | 83 | """ |
43 | 78 | if self.collectionName is None: | 84 | if self.collectionName is None: |
44 | @@ -83,12 +89,11 @@ | |||
45 | 83 | components.append(self.collectionName) | 89 | components.append(self.collectionName) |
46 | 84 | else: | 90 | else: |
47 | 85 | components.extend(prefix) | 91 | components.extend(prefix) |
54 | 86 | 92 | components.extend(self.components) | |
55 | 87 | for component in self.components: | 93 | components.extend(suffix) |
56 | 88 | encoded = component.encode('utf-8') | 94 | |
57 | 89 | quoted = quote(encoded, safe='') | 95 | components = [quote(component.encode('utf-8'), safe='') |
58 | 90 | components.append(quoted) | 96 | for component in components] |
53 | 91 | |||
59 | 92 | return endpoint.getRootURL() + '/'.join(components) | 97 | return endpoint.getRootURL() + '/'.join(components) |
60 | 93 | 98 | ||
61 | 94 | 99 | ||
62 | @@ -97,7 +102,7 @@ | |||
63 | 97 | """ | 102 | """ |
64 | 98 | Representation of a FluidDB namespace. | 103 | Representation of a FluidDB namespace. |
65 | 99 | """ | 104 | """ |
67 | 100 | collectionName = 'namespaces' | 105 | collectionName = u'namespaces' |
68 | 101 | 106 | ||
69 | 102 | 107 | ||
70 | 103 | def child(self, name): | 108 | def child(self, name): |
71 | @@ -141,7 +146,7 @@ | |||
72 | 141 | @rtype: C{Deferred} -> C{unicode} | 146 | @rtype: C{Deferred} -> C{unicode} |
73 | 142 | @return: The description. | 147 | @return: The description. |
74 | 143 | """ | 148 | """ |
76 | 144 | url = self.getURL(endpoint) + '?' + urlencode({'returnDescription': 'true'}) | 149 | url = self.getURL(endpoint) + '?returnDescription=true' |
77 | 145 | d = endpoint.submit(url=url, method='GET') | 150 | d = endpoint.submit(url=url, method='GET') |
78 | 146 | return d.addCallback(lambda res: res[u'description']) | 151 | return d.addCallback(lambda res: res[u'description']) |
79 | 147 | 152 | ||
80 | @@ -179,7 +184,7 @@ | |||
81 | 179 | namespaces.append(self.child(name)) | 184 | namespaces.append(self.child(name)) |
82 | 180 | return namespaces | 185 | return namespaces |
83 | 181 | 186 | ||
85 | 182 | url = self.getURL(endpoint) + '?' + urlencode({'returnNamespaces': 'true'}) | 187 | url = self.getURL(endpoint) + '?returnNamespaces=true' |
86 | 183 | d = endpoint.submit(url=url, method='GET') | 188 | d = endpoint.submit(url=url, method='GET') |
87 | 184 | return d.addCallback(_parseResponse) | 189 | return d.addCallback(_parseResponse) |
88 | 185 | 190 | ||
89 | @@ -200,7 +205,7 @@ | |||
90 | 200 | namespaces.append(self.tag(name)) | 205 | namespaces.append(self.tag(name)) |
91 | 201 | return namespaces | 206 | return namespaces |
92 | 202 | 207 | ||
94 | 203 | url = self.getURL(endpoint) + '?' + urlencode({'returnTags': 'true'}) | 208 | url = self.getURL(endpoint) + '?returnTags=true' |
95 | 204 | d = endpoint.submit(url=url, method='GET') | 209 | d = endpoint.submit(url=url, method='GET') |
96 | 205 | return d.addCallback(_parseResponse) | 210 | return d.addCallback(_parseResponse) |
97 | 206 | 211 | ||
98 | @@ -273,7 +278,7 @@ | |||
99 | 273 | """ | 278 | """ |
100 | 274 | Representation of a FluidDB tag. | 279 | Representation of a FluidDB tag. |
101 | 275 | """ | 280 | """ |
103 | 276 | collectionName = 'tags' | 281 | collectionName = u'tags' |
104 | 277 | 282 | ||
105 | 278 | 283 | ||
106 | 279 | def getDescription(self, endpoint): | 284 | def getDescription(self, endpoint): |
107 | @@ -286,7 +291,7 @@ | |||
108 | 286 | @rtype: C{Deferred} -> C{unicode} | 291 | @rtype: C{Deferred} -> C{unicode} |
109 | 287 | @return: The description. | 292 | @return: The description. |
110 | 288 | """ | 293 | """ |
112 | 289 | url = self.getURL(endpoint) + '?' + urlencode({'returnDescription': 'true'}) | 294 | url = self.getURL(endpoint) + '?returnDescription=true' |
113 | 290 | d = endpoint.submit(url=url, method='GET') | 295 | d = endpoint.submit(url=url, method='GET') |
114 | 291 | return d.addCallback(lambda res: res[u'description']) | 296 | return d.addCallback(lambda res: res[u'description']) |
115 | 292 | 297 | ||
116 | @@ -356,12 +361,15 @@ | |||
117 | 356 | return getPage(*a, **kw) | 361 | return getPage(*a, **kw) |
118 | 357 | 362 | ||
119 | 358 | 363 | ||
121 | 359 | def submit(self, url, method, data=None): | 364 | def submit(self, url, method, data=None, headers=None): |
122 | 360 | """ | 365 | """ |
123 | 361 | Submit a request through this endpoint. | 366 | Submit a request through this endpoint. |
124 | 362 | """ | 367 | """ |
127 | 363 | headers = {'Accept': 'application/json'} | 368 | if headers is None: |
128 | 364 | if data is not None: | 369 | headers = {} |
129 | 370 | |||
130 | 371 | headers['Accept'] = 'application/json' | ||
131 | 372 | if data is not None and not isinstance(data, str): | ||
132 | 365 | data = json.dumps(data) | 373 | data = json.dumps(data) |
133 | 366 | headers['Content-Type'] = 'application/json' | 374 | headers['Content-Type'] = 'application/json' |
134 | 367 | 375 | ||
135 | @@ -369,5 +377,202 @@ | |||
136 | 369 | if res: | 377 | if res: |
137 | 370 | return json.loads(unicode(res, 'utf-8')) | 378 | return json.loads(unicode(res, 'utf-8')) |
138 | 371 | 379 | ||
140 | 372 | d = self.getPage(url=url, method=method, postdata=data, headers=headers, agent='txFluidDB') | 380 | d = self.getPage(url=url, |
141 | 381 | method=method, | ||
142 | 382 | postdata=data, | ||
143 | 383 | headers=headers, | ||
144 | 384 | agent='txFluidDB') | ||
145 | 373 | return d.addCallback(_parse) | 385 | return d.addCallback(_parse) |
146 | 386 | |||
147 | 387 | |||
148 | 388 | |||
149 | 389 | class Object(_HasPath): | ||
150 | 390 | """ | ||
151 | 391 | A FluidDB object. | ||
152 | 392 | |||
153 | 393 | @type uuid: C{unicode} | ||
154 | 394 | @ivar uuid: The UUID of the object. | ||
155 | 395 | """ | ||
156 | 396 | collectionName = u'objects' | ||
157 | 397 | |||
158 | 398 | |||
159 | 399 | def __init__(self, uuid): | ||
160 | 400 | self.uuid = uuid | ||
161 | 401 | |||
162 | 402 | |||
163 | 403 | @property | ||
164 | 404 | def components(self): | ||
165 | 405 | """ | ||
166 | 406 | Our only path component is the object UUID. | ||
167 | 407 | """ | ||
168 | 408 | return [self.uuid] | ||
169 | 409 | |||
170 | 410 | |||
171 | 411 | @classmethod | ||
172 | 412 | def create(cls, endpoint, about=None): | ||
173 | 413 | """ | ||
174 | 414 | Create a new object. | ||
175 | 415 | |||
176 | 416 | @type endpoint: L{Endpoint} | ||
177 | 417 | @param endpoint: The endpoint to operate through. | ||
178 | 418 | |||
179 | 419 | @type about: C{unicode} or C{None} | ||
180 | 420 | @param about: The value for the about tag, if desired. | ||
181 | 421 | |||
182 | 422 | @rtype: C{Deferred} -> L{Object} | ||
183 | 423 | @return: The newly created object. | ||
184 | 424 | """ | ||
185 | 425 | url = endpoint.getRootURL() + 'objects' | ||
186 | 426 | data = {} | ||
187 | 427 | if about is not None: | ||
188 | 428 | data[u'about'] = about | ||
189 | 429 | |||
190 | 430 | def _parseResponse(response): | ||
191 | 431 | return Object(response[u'id']) | ||
192 | 432 | |||
193 | 433 | d = endpoint.submit(url=url, method='POST', data=data) | ||
194 | 434 | return d.addCallback(_parseResponse) | ||
195 | 435 | |||
196 | 436 | |||
197 | 437 | @classmethod | ||
198 | 438 | def query(cls, endpoint, query): | ||
199 | 439 | """ | ||
200 | 440 | Search for objects that match a query. | ||
201 | 441 | |||
202 | 442 | @type endpoint: L{Endpoint} | ||
203 | 443 | @param endpoint: The endpoint to operate through. | ||
204 | 444 | |||
205 | 445 | @type query: C{unicode} | ||
206 | 446 | @param query: A query string to search on. | ||
207 | 447 | """ | ||
208 | 448 | qs = '?' + urlencode({'query': query.encode('utf-8')}) | ||
209 | 449 | url = endpoint.getRootURL() + 'objects' + qs | ||
210 | 450 | |||
211 | 451 | def _parseResponse(response): | ||
212 | 452 | return [Object(uuid) for uuid in response['ids']] | ||
213 | 453 | |||
214 | 454 | d = endpoint.submit(url=url, method='GET') | ||
215 | 455 | return d.addCallback(_parseResponse) | ||
216 | 456 | |||
217 | 457 | |||
218 | 458 | def getTags(self, endpoint): | ||
219 | 459 | """ | ||
220 | 460 | Get the visible tags on this object. | ||
221 | 461 | |||
222 | 462 | @type endpoint: L{Endpoint} | ||
223 | 463 | @param endpoint: The endpoint to operate through. | ||
224 | 464 | |||
225 | 465 | @rtype: C{Deferred} -> (C{unicode}, C{list} of L{Tag}) | ||
226 | 466 | @return: A tuple of the about tag value and the list of tags. | ||
227 | 467 | """ | ||
228 | 468 | def _parseResponse(response): | ||
229 | 469 | about = response[u'about'] | ||
230 | 470 | tags = [Tag(*path.split(u'/')) for path in response[u'tagPaths']] | ||
231 | 471 | return (about, tags) | ||
232 | 472 | |||
233 | 473 | url = self.getURL(endpoint) | ||
234 | 474 | d = endpoint.submit(url=url, | ||
235 | 475 | method='GET', | ||
236 | 476 | data={u'showAbout': True}) | ||
237 | 477 | return d.addCallback(_parseResponse) | ||
238 | 478 | |||
239 | 479 | |||
240 | 480 | def get(self, endpoint, tag): | ||
241 | 481 | """ | ||
242 | 482 | Get the value of a tag on this object. | ||
243 | 483 | |||
244 | 484 | @type endpoint: L{Endpoint} | ||
245 | 485 | @param endpoint: The endpoint to operate through. | ||
246 | 486 | |||
247 | 487 | @type tag: L{Tag} | ||
248 | 488 | @param tag: The tag to retrieve. | ||
249 | 489 | |||
250 | 490 | @rtype: Varies depending on what value is stored. | ||
251 | 491 | @return: The stored value. | ||
252 | 492 | """ | ||
253 | 493 | def _parseResponse(response): | ||
254 | 494 | value = response[u'value'] | ||
255 | 495 | if u'valueType' in response: | ||
256 | 496 | encoding = response[u'valueEncoding'] | ||
257 | 497 | if encoding != u'base-64': | ||
258 | 498 | raise ValueError('Unsupported encoding received: %r' % encoding) | ||
259 | 499 | value = value.decode('base-64') | ||
260 | 500 | return Blob(response[u'valueType'], value) | ||
261 | 501 | return value | ||
262 | 502 | |||
263 | 503 | url = self.getURL(endpoint, suffix=tag.components) + '?format=json' | ||
264 | 504 | d = endpoint.submit(url=url, | ||
265 | 505 | method='GET', | ||
266 | 506 | headers={'Accept-Encoding': 'base-64'}) | ||
267 | 507 | return d.addCallback(_parseResponse) | ||
268 | 508 | |||
269 | 509 | |||
270 | 510 | def set(self, endpoint, tag, value): | ||
271 | 511 | """ | ||
272 | 512 | Set the value of a tag on this object. | ||
273 | 513 | |||
274 | 514 | @type endpoint: L{Endpoint} | ||
275 | 515 | @param endpoint: The endpoint to operate through. | ||
276 | 516 | |||
277 | 517 | @type tag: L{Tag} | ||
278 | 518 | @param tag: The tag to set or replace. | ||
279 | 519 | |||
280 | 520 | @type value: Any JSON-encodable value | ||
281 | 521 | @param value: The value to store. | ||
282 | 522 | """ | ||
283 | 523 | url = self.getURL(endpoint, suffix=tag.components) + '?format=json' | ||
284 | 524 | return endpoint.submit(url=url, | ||
285 | 525 | method='PUT', | ||
286 | 526 | data={u'value': value}) | ||
287 | 527 | |||
288 | 528 | |||
289 | 529 | def setBlob(self, endpoint, tag, value): | ||
290 | 530 | """ | ||
291 | 531 | Set the value of a tag on this object to a blob. | ||
292 | 532 | |||
293 | 533 | @type endpoint: L{Endpoint} | ||
294 | 534 | @param endpoint: The endpoint to operate through. | ||
295 | 535 | |||
296 | 536 | @type tag: L{Tag} | ||
297 | 537 | @param tag: The tag to set or replace. | ||
298 | 538 | |||
299 | 539 | @type value: L{Blob} | ||
300 | 540 | @param value: The value to store. | ||
301 | 541 | """ | ||
302 | 542 | url = self.getURL(endpoint, suffix=tag.components) | ||
303 | 543 | return endpoint.submit(url=url, | ||
304 | 544 | method='PUT', | ||
305 | 545 | headers={'Content-Type': value.contentType}, | ||
306 | 546 | data=value.data) | ||
307 | 547 | |||
308 | 548 | |||
309 | 549 | def delete(self, endpoint, tag): | ||
310 | 550 | """ | ||
311 | 551 | Delete a tag from this object. | ||
312 | 552 | |||
313 | 553 | Note that FluidDB does not support deleting objects themselves. | ||
314 | 554 | |||
315 | 555 | @type endpoint: L{Endpoint} | ||
316 | 556 | @param endpoint: The endpoint to operate through. | ||
317 | 557 | |||
318 | 558 | @type tag: L{Tag} | ||
319 | 559 | @param tag: The tag to retrieve. | ||
320 | 560 | """ | ||
321 | 561 | url = self.getURL(endpoint, suffix=tag.components) | ||
322 | 562 | return endpoint.submit(url=url, method='DELETE') | ||
323 | 563 | |||
324 | 564 | |||
325 | 565 | |||
326 | 566 | class Blob(object): | ||
327 | 567 | """ | ||
328 | 568 | A binary blob with a MIME content type. | ||
329 | 569 | |||
330 | 570 | @type contentType: C{unicode} | ||
331 | 571 | @ivar contentType: The MIME content-type of the data. | ||
332 | 572 | |||
333 | 573 | @type data: C{str} | ||
334 | 574 | @ivar data: The actual data. | ||
335 | 575 | """ | ||
336 | 576 | def __init__(self, contentType, data): | ||
337 | 577 | self.contentType = contentType | ||
338 | 578 | self.data = data | ||
339 | 374 | 579 | ||
340 | === modified file 'txfluiddb/test/test_client.py' | |||
341 | --- txfluiddb/test/test_client.py 2009-08-19 20:33:58 +0000 | |||
342 | +++ txfluiddb/test/test_client.py 2009-08-21 00:42:26 +0000 | |||
343 | @@ -4,7 +4,7 @@ | |||
344 | 4 | import simplejson as json | 4 | import simplejson as json |
345 | 5 | 5 | ||
346 | 6 | from txfluiddb.errors import InvalidName | 6 | from txfluiddb.errors import InvalidName |
348 | 7 | from txfluiddb.client import Namespace, Tag, Endpoint, _HasPath | 7 | from txfluiddb.client import Namespace, Tag, Endpoint, _HasPath, Object, Blob |
349 | 8 | 8 | ||
350 | 9 | 9 | ||
351 | 10 | 10 | ||
352 | @@ -44,6 +44,28 @@ | |||
353 | 44 | 'http://fluiddb.test.url/tests/foo') | 44 | 'http://fluiddb.test.url/tests/foo') |
354 | 45 | 45 | ||
355 | 46 | 46 | ||
356 | 47 | def test_getURLPrefix(self): | ||
357 | 48 | """ | ||
358 | 49 | Passing prefix to getURL prepends the given components to the path. | ||
359 | 50 | """ | ||
360 | 51 | endpoint = Endpoint('http://fluiddb.test.url/') | ||
361 | 52 | path = _HasPath(u'foo') | ||
362 | 53 | path.collectionName = 'tests' | ||
363 | 54 | self.assertEqual(path.getURL(endpoint, prefix=[u'quux', u'42']), | ||
364 | 55 | 'http://fluiddb.test.url/quux/42/foo') | ||
365 | 56 | |||
366 | 57 | |||
367 | 58 | def test_getURLSuffix(self): | ||
368 | 59 | """ | ||
369 | 60 | Passing suffix to getURL appends the given components to the path. | ||
370 | 61 | """ | ||
371 | 62 | endpoint = Endpoint('http://fluiddb.test.url/') | ||
372 | 63 | path = _HasPath(u'foo') | ||
373 | 64 | path.collectionName = 'tests' | ||
374 | 65 | self.assertEqual(path.getURL(endpoint, suffix=[u'quux', u'42']), | ||
375 | 66 | 'http://fluiddb.test.url/tests/foo/quux/42') | ||
376 | 67 | |||
377 | 68 | |||
378 | 47 | def test_urlEncoding(self): | 69 | def test_urlEncoding(self): |
379 | 48 | """ | 70 | """ |
380 | 49 | URL encoding is done according to IRI rules. | 71 | URL encoding is done according to IRI rules. |
381 | @@ -374,3 +396,199 @@ | |||
382 | 374 | endpoint = Endpoint('http://fluiddb.test.url/', '20090817') | 396 | endpoint = Endpoint('http://fluiddb.test.url/', '20090817') |
383 | 375 | self.assertEqual(endpoint.getRootURL(), | 397 | self.assertEqual(endpoint.getRootURL(), |
384 | 376 | 'http://fluiddb.test.url/20090817/') | 398 | 'http://fluiddb.test.url/20090817/') |
385 | 399 | |||
386 | 400 | |||
387 | 401 | |||
388 | 402 | class ObjectTests(TestCase): | ||
389 | 403 | """ | ||
390 | 404 | Tests for L{Object}. | ||
391 | 405 | """ | ||
392 | 406 | def setUp(self): | ||
393 | 407 | self.endpoint = MockEndpoint('http://fluiddb.test.url/') | ||
394 | 408 | |||
395 | 409 | |||
396 | 410 | def test_getTags(self): | ||
397 | 411 | """ | ||
398 | 412 | The getTags method returns the about value and the list of tags. | ||
399 | 413 | """ | ||
400 | 414 | response = {u'about': u'Testing object', | ||
401 | 415 | u'tagPaths': [u'test/tag1', u'test/tag2']} | ||
402 | 416 | self.endpoint.response = json.dumps(response) | ||
403 | 417 | |||
404 | 418 | def _gotResponse(response): | ||
405 | 419 | about, tags = response | ||
406 | 420 | self.assertEqual(about, u'Testing object') | ||
407 | 421 | self.assertEqual(len(tags), 2) | ||
408 | 422 | tag1, tag2 = tags | ||
409 | 423 | self.assertIsInstance(tag1, Tag) | ||
410 | 424 | self.assertEqual(tag1.getPath(), u'test/tag1') | ||
411 | 425 | self.assertIsInstance(tag2, Tag) | ||
412 | 426 | self.assertEqual(tag2.getPath(), u'test/tag2') | ||
413 | 427 | |||
414 | 428 | obj = Object(u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx') | ||
415 | 429 | d = obj.getTags(self.endpoint).addCallback(_gotResponse) | ||
416 | 430 | self.assertEqual(self.endpoint.method, 'GET') | ||
417 | 431 | self.assertEqual( | ||
418 | 432 | self.endpoint.url, | ||
419 | 433 | 'http://fluiddb.test.url/objects/xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx') | ||
420 | 434 | self.assertEqual(json.loads(self.endpoint.data), | ||
421 | 435 | {u'showAbout': True}) | ||
422 | 436 | return d | ||
423 | 437 | |||
424 | 438 | |||
425 | 439 | def makeGetRequest(self, response): | ||
426 | 440 | """ | ||
427 | 441 | Utility wrapper for testing L{Object.get} operations. | ||
428 | 442 | """ | ||
429 | 443 | self.endpoint.response = json.dumps(response) | ||
430 | 444 | obj = Object(u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx') | ||
431 | 445 | tag = Namespace(u'test').child(u'tag') | ||
432 | 446 | d = obj.get(self.endpoint, tag) | ||
433 | 447 | self.assertEqual(self.endpoint.method, 'GET') | ||
434 | 448 | self.assertEqual(self.endpoint.data, None) | ||
435 | 449 | self.assertEqual( | ||
436 | 450 | self.endpoint.url, | ||
437 | 451 | 'http://fluiddb.test.url/objects/xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx/test/tag?format=json') | ||
438 | 452 | return d | ||
439 | 453 | |||
440 | 454 | |||
441 | 455 | def test_getString(self): | ||
442 | 456 | """ | ||
443 | 457 | Retrieving a JSON string value results in a C{unicode} object. | ||
444 | 458 | """ | ||
445 | 459 | response = {u'value': u'Data goes where?'} | ||
446 | 460 | def _gotResponse(response): | ||
447 | 461 | self.assertIsInstance(response, unicode) | ||
448 | 462 | self.assertEqual(response, u'Data goes where?') | ||
449 | 463 | return self.makeGetRequest(response).addCallback(_gotResponse) | ||
450 | 464 | |||
451 | 465 | |||
452 | 466 | def test_getInteger(self): | ||
453 | 467 | """ | ||
454 | 468 | Retrieving a JSON integer value results in an C{integer} object. | ||
455 | 469 | """ | ||
456 | 470 | response = {u'value': 42} | ||
457 | 471 | def _gotResponse(response): | ||
458 | 472 | self.assertEqual(response, 42) | ||
459 | 473 | return self.makeGetRequest(response).addCallback(_gotResponse) | ||
460 | 474 | |||
461 | 475 | |||
462 | 476 | def test_getBlob(self): | ||
463 | 477 | """ | ||
464 | 478 | Retrieving a binary value results in a C{Blob} object. | ||
465 | 479 | """ | ||
466 | 480 | response = {u'value': u'PHA+Zm9vPC9wPg==', | ||
467 | 481 | u'valueEncoding': u'base-64', | ||
468 | 482 | u'valueType': u'text/html'} | ||
469 | 483 | def _gotResponse(response): | ||
470 | 484 | self.assertEqual(self.endpoint.headers['Accept-Encoding'], 'base-64') | ||
471 | 485 | self.assertEqual(response.contentType, u'text/html') | ||
472 | 486 | self.assertEqual(response.data, '<p>foo</p>') | ||
473 | 487 | return self.makeGetRequest(response).addCallback(_gotResponse) | ||
474 | 488 | |||
475 | 489 | |||
476 | 490 | def test_deleteTag(self): | ||
477 | 491 | """ | ||
478 | 492 | Deleting a tag returns nothing if successful. | ||
479 | 493 | """ | ||
480 | 494 | self.endpoint.response = '' | ||
481 | 495 | |||
482 | 496 | obj = Object(u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx') | ||
483 | 497 | tag = Namespace(u'test').child(u'tag') | ||
484 | 498 | d = obj.delete(self.endpoint, tag) | ||
485 | 499 | self.assertEqual(self.endpoint.method, 'DELETE') | ||
486 | 500 | self.assertEqual( | ||
487 | 501 | self.endpoint.url, | ||
488 | 502 | 'http://fluiddb.test.url/objects/xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx/test/tag') | ||
489 | 503 | self.assertEqual(self.endpoint.data, None) | ||
490 | 504 | return d | ||
491 | 505 | |||
492 | 506 | |||
493 | 507 | def test_setTag(self): | ||
494 | 508 | self.endpoint.response = '' | ||
495 | 509 | |||
496 | 510 | obj = Object(u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx') | ||
497 | 511 | tag = Namespace(u'test').child(u'tag') | ||
498 | 512 | d = obj.set(self.endpoint, tag, 42) | ||
499 | 513 | self.assertEqual(self.endpoint.method, 'PUT') | ||
500 | 514 | self.assertEqual( | ||
501 | 515 | self.endpoint.url, | ||
502 | 516 | 'http://fluiddb.test.url/objects/xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx/test/tag?format=json') | ||
503 | 517 | self.assertEqual(json.loads(self.endpoint.data), | ||
504 | 518 | {u'value': 42}) | ||
505 | 519 | return d | ||
506 | 520 | |||
507 | 521 | |||
508 | 522 | def test_setBlob(self): | ||
509 | 523 | self.endpoint.response = '' | ||
510 | 524 | |||
511 | 525 | obj = Object(u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx') | ||
512 | 526 | tag = Namespace(u'test').child(u'tag') | ||
513 | 527 | blob = Blob(u'text/html', '<p>foo</p>') | ||
514 | 528 | d = obj.setBlob(self.endpoint, tag, blob) | ||
515 | 529 | self.assertEqual(self.endpoint.method, 'PUT') | ||
516 | 530 | self.assertEqual( | ||
517 | 531 | self.endpoint.url, | ||
518 | 532 | 'http://fluiddb.test.url/objects/xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx/test/tag') | ||
519 | 533 | self.assertEqual(self.endpoint.data, '<p>foo</p>') | ||
520 | 534 | self.assertEqual(self.endpoint.headers['Content-Type'], 'text/html') | ||
521 | 535 | return d | ||
522 | 536 | |||
523 | 537 | |||
524 | 538 | def test_createObject(self): | ||
525 | 539 | """ | ||
526 | 540 | Creating an object returns the newly created object. | ||
527 | 541 | """ | ||
528 | 542 | response = {u'URI': u'http://fluiddb.test.url/objects/xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx', | ||
529 | 543 | u'id': u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'} | ||
530 | 544 | self.endpoint.response = json.dumps(response) | ||
531 | 545 | |||
532 | 546 | def _gotResponse(obj): | ||
533 | 547 | self.assertIsInstance(obj, Object) | ||
534 | 548 | self.assertEqual(obj.uuid, u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx') | ||
535 | 549 | |||
536 | 550 | d = Object.create(self.endpoint).addCallback(_gotResponse) | ||
537 | 551 | self.assertEqual(self.endpoint.method, 'POST') | ||
538 | 552 | self.assertEqual(self.endpoint.url, 'http://fluiddb.test.url/objects') | ||
539 | 553 | self.assertEqual(json.loads(self.endpoint.data), {}) | ||
540 | 554 | return d | ||
541 | 555 | |||
542 | 556 | |||
543 | 557 | def test_createObjectWithAbout(self): | ||
544 | 558 | response = {u'URI': u'http://fluiddb.test.url/objects/xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx', | ||
545 | 559 | u'id': u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'} | ||
546 | 560 | self.endpoint.response = json.dumps(response) | ||
547 | 561 | |||
548 | 562 | def _gotResponse(obj): | ||
549 | 563 | self.assertIsInstance(obj, Object) | ||
550 | 564 | self.assertEqual(obj.uuid, u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx') | ||
551 | 565 | |||
552 | 566 | d = Object.create(self.endpoint, u'New object').addCallback(_gotResponse) | ||
553 | 567 | self.assertEqual(self.endpoint.method, 'POST') | ||
554 | 568 | self.assertEqual(self.endpoint.url, 'http://fluiddb.test.url/objects') | ||
555 | 569 | self.assertEqual(json.loads(self.endpoint.data), | ||
556 | 570 | {u'about': u'New object'}) | ||
557 | 571 | return d | ||
558 | 572 | |||
559 | 573 | |||
560 | 574 | def test_query(self): | ||
561 | 575 | """ | ||
562 | 576 | Querying returns all objects matching the query. | ||
563 | 577 | """ | ||
564 | 578 | response = {u'ids': [u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxx1', | ||
565 | 579 | u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxx2']} | ||
566 | 580 | self.endpoint.response = json.dumps(response) | ||
567 | 581 | |||
568 | 582 | def _gotResponse(objs): | ||
569 | 583 | self.assertEqual(len(objs), 2) | ||
570 | 584 | obj1, obj2 = objs | ||
571 | 585 | self.assertIsInstance(obj1, Object) | ||
572 | 586 | self.assertEqual(obj1.uuid, u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxx1') | ||
573 | 587 | self.assertIsInstance(obj2, Object) | ||
574 | 588 | self.assertEqual(obj2.uuid, u'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxx2') | ||
575 | 589 | |||
576 | 590 | d = Object.query(self.endpoint, u'has fluiddb/about').addCallback(_gotResponse) | ||
577 | 591 | self.assertEqual(self.endpoint.method, 'GET') | ||
578 | 592 | self.assertEqual(self.endpoint.url, 'http://fluiddb.test.url/objects?query=has+fluiddb%2Fabout') | ||
579 | 593 | self.assertEqual(self.endpoint.data, None) | ||
580 | 594 | return d |
Implement operations on objects.