****************
Named operations
****************

Entries and collections support named operations: one-off
functionality that's been given a name and a set of parameters.

    >>> from lazr.restfulclient.tests.example import CookbookWebServiceClient
    >>> service = CookbookWebServiceClient()

Arguments to named operations are automatically converted to JSON for
transmission over the wire. Here's a named operation that takes a
boolean argument.

    >>> [recipe for recipe in service.cookbooks.find_recipes(
    ...      search="Chicken", vegetarian=True)]
    []

Strings that happen to be numbers are handled properly. Here, if "1.234"
were converted into a number at any point in the chain, the 'search'
operation on the server wouldn't know how to handle it and the request
would fail.

    >>> [people for people in service.cookbooks.find_recipes(search="1.234")]
    []


Special data types
------------------

lazr.restfulclient uses some data types that don't directly correspond
to JSON data types. These data types can be used in named
operations. For instance, a named operation can take a date or
datetime object as one of its arguments.

    >>> import datetime
    >>> date = datetime.datetime(1994, 1, 1)
    >>> cookbook = service.cookbooks.create(
    ...     name="New cookbook", cuisine="General",
    ...     copyright_date=date, price=1.23, last_printing=date)
    >>> print cookbook.name
    New cookbook

A named operation can take an entry as one of its arguments.
lazr.restfulclient lets you pass in the actual entry as the argument
value.

    >>> dish = service.recipes[1].dish
    >>> cookbook = service.recipes[1].cookbook
    >>> print cookbook.find_recipe_for(dish=dish)
    http://cookbooks.dev/1.0/recipes/1

A named operation can take binary data as one of its arguments.

    >>> cookbook.replace_cover(cover="\x00\xe2\xe3")
    >>> cookbook.cover.open().read()
    '\x00\xe2\xe3'

A named operation that returns a null value corresponds to a Python
value of None.

    >>> dish = service.recipes[4].dish
    >>> print cookbook.find_recipe_for(dish=dish)
    None

JSON-encoding
-------------

lazr.restfulclient encodes most arguments (even string arguments) as
JSON before sending them over the wire. This way, a named operation
that takes a string argument can take a string that looks like a JSON
object without causing confusion.

    >>> cookbooks = service.cookbooks.find_for_cuisine(cuisine="General")
    >>> len([cookbook for cookbook in cookbooks]) > 0
    True

    >>> cookbook = service.cookbooks.create(
    ...    name="null", cuisine="General",
    ...    copyright_date=date, price=1.23, last_printing=date)
    >>> cookbook.name
    u'null'

    >>> cookbook = service.cookbooks.create(
    ...     name="4.56", cuisine="General",
    ...     copyright_date=date, price=1.23, last_printing=date)
    >>> cookbook.name
    u'4.56'

    >>> cookbook = service.cookbooks.create(
    ...     name='"foo"', cuisine="General",
    ...     copyright_date=date, price=1.23, last_printing=date)
    >>> cookbook.name
    u'"foo"'

A named operation that takes a non-string object (such as a float)
will not accept a string that's the JSON representation of the
object.

    >>> try:
    ...     service.cookbooks.create(
    ...         name="Yet another 1.23 cookbook", cuisine="General",
    ...         copyright_date=date, last_printing=date, price="1.23")
    ... except Exception, e:
    ...     print e.content
    price: got 'unicode', expected float, int: u'1.23'
