toplevel.rst 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. *****************
  2. Top-level objects
  3. *****************
  4. Every web service has a top-level "root" object.
  5. >>> from lazr.restfulclient.tests.example import CookbookWebServiceClient
  6. >>> service = CookbookWebServiceClient()
  7. The root object provides access to service-wide objects.
  8. >>> sorted(service.lp_entries)
  9. ['featured_cookbook']
  10. >>> sorted(service.lp_collections)
  11. ['cookbooks', 'dishes', 'recipes']
  12. Top-level entries
  13. =================
  14. You can access a top-level entry through attribute access.
  15. >>> print service.featured_cookbook.name
  16. Mastering the Art of French Cooking
  17. Top-level collections
  18. =====================
  19. You can access a top-level collection through attribute access.
  20. >>> len(service.dishes)
  21. 3
  22. Specific top-level collections may support key-based lookups. For
  23. instance, the recipe collection does lookups by recipe ID. This is
  24. custom code written for this specific web service, and it won't work
  25. in general.
  26. >>> print service.recipes[1].dish.name
  27. Roast chicken
  28. Looking up an object in a top-level collection triggers an HTTP
  29. request, and if the object does not exist on the server, a KeyError is
  30. raised.
  31. >>> import httplib2
  32. >>> httplib2.debuglevel = 1
  33. >>> debug_service = CookbookWebServiceClient()
  34. send: 'GET ...'
  35. ...
  36. >>> recipe = debug_service.recipes[4]
  37. send: 'GET /1.0/recipes/4 ...'
  38. ...
  39. >>> recipe = debug_service.recipes[1000]
  40. Traceback (most recent call last):
  41. ...
  42. KeyError: 1000
  43. If you want to look up an object without triggering an HTTP request,
  44. you can use parentheses instead of square brackets.
  45. >>> recipe = debug_service.recipes(4)
  46. >>> nonexistent_recipe = debug_service.recipes(1000)
  47. >>> sorted(recipe.lp_attributes)
  48. ['http_etag', 'id', 'instructions', ...]
  49. The HTTP request, and any potential error, happens when you try to
  50. access one of the object's properties.
  51. >>> print recipe.instructions
  52. send: 'GET /1.0/recipes/4 ...'
  53. ...
  54. Preheat oven to...
  55. >>> print nonexistent_recipe.instructions
  56. Traceback (most recent call last):
  57. ...
  58. NotFound: HTTP Error 404: Not Found
  59. ...
  60. This is useful if you plan to invoke a named operation on the object
  61. instead of accessing its properties--this can save you an HTTP request
  62. and speed up your applicaiton.
  63. Now, let's imagine that a top-level collection is misconfigured. We
  64. know that the top-level collection of recipes contains objects whose
  65. resource type is 'recipe'. But let's tell lazr.restfulclient that that
  66. collection contains objects of type 'cookbook'.
  67. >>> from lazr.restfulclient.tests.example import RecipeSet
  68. >>> print RecipeSet.collection_of
  69. recipe
  70. >>> RecipeSet.collection_of = 'cookbook'
  71. Looking up an object will give you something that presents the
  72. interface of a cookbook.
  73. >>> not_really_a_cookbook = debug_service.recipes(2)
  74. >>> sorted(not_really_a_cookbook.lp_attributes)
  75. ['confirmed', 'copyright_date', 'cuisine'...]
  76. But once you try to access one of the object's properties, and the
  77. HTTP request is made...
  78. >>> print not_really_a_cookbook.resource_type_link
  79. send: 'GET /1.0/recipes/2 ...'
  80. ...
  81. http://cookbooks.dev/1.0/#recipe
  82. ...the server serves a recipe, and so the client-side object takes on
  83. the properties of a recipe. You can only fool lazr.restfulclient up to
  84. the point where it has real data to look at.
  85. >>> sorted(not_really_a_cookbook.lp_attributes)
  86. ['http_etag', 'id', 'instructions', ...]
  87. >>> print not_really_a_cookbook.instructions
  88. Draw, singe, stuff, and truss...
  89. This isn't just a defense mechanism: it's a useful feature when a
  90. top-level collection contains mixed subclasses of some superclass. For
  91. instance, the launchpadlib library defines the 'people' collection as
  92. containing 'team' objects, even though it also contains 'person'
  93. objects, which expose a subset of a team's functionality. All objects
  94. looked up in that collection start out as team objects, but once an
  95. object's data is fetched, if it turns out to actually be a person, it
  96. switches from the "team" interface to the "people" interface. (This
  97. bit of hackery is necessary because WADL doesn't have an inheritance
  98. mechanism.)
  99. If you try to access a property based on a resource type the object
  100. doesn't really implement, you'll get an error.
  101. >>> not_really_a_cookbook = debug_service.recipes(3)
  102. >>> sorted(not_really_a_cookbook.lp_attributes)
  103. ['confirmed', 'copyright_date', 'cuisine'...]
  104. >>> not_really_a_cookbook.cuisine
  105. Traceback (most recent call last):
  106. ...
  107. AttributeError: http://cookbooks.dev/1.0/recipes/3 object has no attribute 'cuisine'
  108. Cleanup.
  109. >>> httplib2.debuglevel = 0
  110. >>> RecipeSet.collection_of = 'recipe'
  111. Versioning
  112. ==========
  113. By passing in a 'version' argument to the client constructor, you can
  114. access different versions of the web service.
  115. >>> print service.recipes[1].self_link
  116. http://cookbooks.dev/1.0/recipes/1
  117. >>> devel_service = CookbookWebServiceClient(version="devel")
  118. >>> print devel_service.recipes[1].self_link
  119. http://cookbooks.dev/devel/recipes/1
  120. You can also forgo the 'version' argument and pass in a service root
  121. that incorporates a version string.
  122. >>> devel_service = CookbookWebServiceClient(
  123. ... service_root="http://cookbooks.dev/devel/", version=None)
  124. >>> print devel_service.recipes[1].self_link
  125. http://cookbooks.dev/devel/recipes/1
  126. Error reporting
  127. ===============
  128. If there's an error communicating with the server, lazr.restfulclient
  129. raises HTTPError or an appropriate subclass. The error might be a
  130. client-side error (maybe you tried to access something that doesn't
  131. exist) or a server-side error (maybe the server crashed due to a
  132. bug). The string representation of the error should have enough
  133. information to help you figure out what happened.
  134. This example demonstrates NotFound, the HTTPError subclass used when
  135. the server sends a 404 error For detailed information about the
  136. different HTTPError subclasses, see tests/test_error.py.
  137. >>> from lazr.restfulclient.errors import HTTPError
  138. >>> try:
  139. ... service.load("http://cookbooks.dev/")
  140. ... except Exception, e:
  141. ... pass
  142. >>> raise e
  143. Traceback (most recent call last):
  144. ...
  145. NotFound: HTTP Error 404: Not Found
  146. Response headers:
  147. ---
  148. ...
  149. content-type: text/plain
  150. ...
  151. ---
  152. Response body:
  153. ---
  154. ...
  155. ---
  156. >>> print isinstance(e, HTTPError)
  157. True