Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
A Python client for Cascade, the Yahoo! Mail API.
Python
branch: master

README.md

Background

PyCascade is a Python Cascade client implementation, written in pure Python. Although Cascade supports both BBAuth and OAuth authorization models, PyCascade supports only OAuth, via the oauth library. Only the JSON variant of the API is currently supported.

To apply for an OAuth key that works with Cascade, check out the YDN OAuth documentation.

Using as Library

The primary use of PyCascade is as a Cascade client library. To use it, one constructs a JSON11Client instance and invokes the call() method. The API that PyCascade provides is based on Python primitives: all JSON datatypes to be communicated over the wire should be handed to PyCascade as Python primitives; likewise, all JSON datatypes coming back from the server are de-serialized into Python primitives before being handed back to the PyCascade client application.

As a brief example, the following code snippet will construct a JSON11Client and then use it to print a list of folders in a mailbox.

oaConsumer = oauth.OAuthConsumer(...)
oaToken = oauth.OAuthToken(...)

jc = JSON11Client(oaConsumer, oaToken)
result = jc.call(
    'ListFolders',
    params = [{}]
)

for f in result['result']['folder']:
    print f['folderInfo']['name']

Note that the OAuthToken here must be an access token, not a request token. If the access token is stale, the PyCascade library will attempt to refresh it for you.

The PyCascade library provides some easier-to-use higher level functions for interacting with the Cascade OAuth implementation to interact with tokens. The following list of metthods are all present in the cascade module:

  • oauth_get_request_token()
  • oauth_get_access_token()
  • oauth_refresh_access_token()

In addition, it provides some methods to (de-)serialize OAuth tokens to and from query strings. These query strings are compatible with those generated by oauth.OAuthToken.from_string(), but contain additional data that allows for token refresh, etc.

  • oauth_token_to_query_string()
  • oauth_token_from_query_string()

Using to make Cascade calls from the shell

The PyCacade library can also be invoked directly from the shell. This can be useful to bolt on Cascade client functionality to other scripts, or to do interactive testing / exploration of the Cascade service.

The trickest part of using this is getting the OAuth configuration correct. One is required to specify the -k and -s options which indicate the OAuth consumer key and secret, respectively. Beyond that, one must specify an OAuth access token to use, which can be done in one of two ways: the -a option, which takes a token encoded as a query string via cascade.oauth_token_to_query_string(); or both of the --oauth-access-token-key and --oauth-access-token-secret options.

The major benefits of using the -a option is that token query strings can contain information which allows for refresh and that it is less verbose. Note that one can get a query-encoded access token by running the unit tests (below) and retrieving the OAUTH_ACCESS_TOKEN value from cascade_unittest_settings.py.

As a brief example, we call ListFolders.

% echo "[{}]" | python ./cascade.py call \
    -k 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
    -s 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
    -a 'xoauth_yahoo_guid=UDLAKKVZB3N4BVS7CMIDBIDRYI&oauth_token_secret=3800c4b46be3eceb26938eb3af0198c031661c80&xoauth_authorization_expires_on=2147483650&oauth_session_handle=ANCIPkvlhRw8XL_VsZpCU88oUzYLm2f6cFCoS2.zF1x79QU-&oauth_token=A%3DiDHi8nvCpwQlBW6HqXB2eZywDxmGID3d28CX4ArOd7tF0YgHJCqDY0H39t6VeuobybSL8kVW1GCX0XedzVoPe2R8cl5Bw_uTsEHeKP.qnEMQz.qO9RWzdvFS.qzGIoaX3w.wLZUDAckrHOT2jSdDab72iXURTkuFaDjAdO.SIa6qJLRPQJuD_4fMgL4h1553y9uuz_revFBB8iqd8VQDWJKh1mr.p41ovaMDp90VR4Bpnap1xfQ7yNT.x9psZrtcS32MQcekaz.L3Ax6bKhBRc__JqeU5LIA0HONuM0yTk01vtb.Wcu0U7_jTRH3VAeyymKENhIlcsx3gUmJ0h5OzeAHvH16BEEl4SHS4nff2POJqZ7RSbrowkrWtoXYLCb5epjFoHXlk45c2xKbEpUyNYKGoLjrTLloTlfv90X6uRt3VctcsLEw8e5_kHUOfhcM7aUqMGRcZdihoC5ts6I0wxmVkGzGqSSdRIhYnZXcvB5dj9On9HtXBoQ.XF2kQT4zErW81i6dEgUu4i6hOq.Pey_qBLRVWno8ffyj.QRhUvhk2y2BrczKDHnnsLxXSOjVjFKo62IObXGjO7FelxOUeSc3tKn0N7Ic_AAxDQgo.X2v8mkoZKH66rHspH06vVURTLMG7oADaDsfZSe6CmnkrcOU7DN.AivzOV16Yu1wkXjSfABfSovE.QcRG6QB3fckypZ7PBfbRhOwboo_txK0a6A6WhMKA2EGnRBhCj68UEW7aLIEac2wJXWkErEhhcU2Z75.62KYjZlbAUyBUQ--&xoauth_expires_on=1266426257' \
    ListFolders
{
    "result": {
        "folder": [
            {
                "folderInfo": {
                    "name": "@B@Bulk", 
                    "fid": "%40B%40Bulk"
                }, 
                "total": 0, 
                "unread": 0, 
                "isSystem": true, 
                "size": 0
            }, 
            {
                "folderInfo": {
                    "name": "Draft", 
                    "fid": "Draft"
                }, 
                "total": 0, 
                "unread": 0, 
                "isSystem": true, 
                "size": 0
            }, 
            {
                "folderInfo": {
                    "name": "Inbox", 
                    "fid": "Inbox"
                }, 
                "total": 20, 
                "unread": 13, 
                "isSystem": true, 
                "size": 185992
            }, 
            {
                "folderInfo": {
                    "name": "Sent", 
                    "fid": "Sent"
                }, 
                "total": 0, 
                "unread": 0, 
                "isSystem": true, 
                "size": 0
            }, 
            {
                "folderInfo": {
                    "name": "Trash", 
                    "fid": "Trash"
                }, 
                "total": 0, 
                "unread": 0, 
                "isSystem": true, 
                "size": 0
            }
        ], 
        "numberOfFolders": 5
    }, 
    "error": null
}

The exit status for cascade.py call is zero if the call succeeded, nonzero otherwise.

Testing

PyCascade sports a handful of unit tests, which are executed by invoking cascade.py unittest from the shell, as follows:

% python ./cascade.py unittest
..
----------------------------------------------------------------------
Ran 2 tests in 1.005s

OK

OAuth Configuration

Most of these unit tests require OAuth authentication. As we do not want to commit these credentials to source control, a facility exists to generate them by interviewing the invoker, and persisting the results in the cascade_unittest_settings.py module in the current working directory. The consumer callback URL should have the same hostname as the one configured with the OAuth key being used, but should result in a 404. The idea is that we do not want the OAuth authorization flow to end up somewhere "real" -- we just want to steal the query parameters that are generated as part of this flow. Thus, the token callback URL is just the consumer callback URL with some query parameters populated by the OAuth flow.

In the following example, http://www.yttrium.ws is configured as the application URL for our OAuth key. We synthesize a fictitious path with some gook to ensure a 404.

% rm -f cascade_unittest_settings.py*
% python ./cascade.py
>>> generating cascade_unittest_settings.py
consumer key: 6epbh1bl4p2zdnhsn5bnikcu2n73apewat0hp166dzkit8n84pe6fjvruagjg3r3wh824cnp45av5pueag274xzayzb2awipnvci
consumer secret: x4jhpriqzwbaojwhkq2hhrkm2nagmuyd7yqawjmc
consumer callback URL: http://www.yttrium.ws/qqzzbb
>>> navigate to the following URL in your browser, then paste in the tokenc allback URL that results
https://api.login.yahoo.com/oauth/v2/request_auth?oauth_token=crw2ke7
token callback URL: http://www.yttrium.ws/qqzzbb?oauth_token=crw2ke7&oauth_verifier=o3caph
..
----------------------------------------------------------------------
Ran 2 tests in 1.005s

OK

Using an HTTP Proxy

PyCascade be run through an HTTP proxy by utilizing the built-in support that urllib2 provides for proxies. In the following example, both HTTP and HTTPS are sent through a proxy running on localhost port 8888.

% export http_proxy='http://localhost:8888'
% export https_proxy='http://localhost:8888'
% python ./cascade.py unittest
...
Something went wrong with that request. Please try again.