Package: birdy
birdyis a super awesome Twitter API client for Python in just a
little under 400 LOC.TL;DRFeaturesFuture proof dynamic API with full REST and Streaming API
coverageOAuth1 (user) and OAuth2 (app) authentication
workflowsAutomatic JSON decoding,JSONObjectApiResponse,StreamResponseobjectsInformative exceptionsEasily customizable through
subclassingBuilt on top of the excellent requests and requests-ouathlib
librariesInstallationThe easiest and recommended way to installbirdyis fromPyPIpip install birdyUsageImport client and initialize it:frombirdy.twitterimportUserClientclient=UserClient(CONSUMER_KEY,CONSUMER_SECRET,ACCESS_TOKEN,ACCESS_TOKEN_SECRET)GET example (GET users/show):response=client.api.users.show.get(screen_name='twitter')response.dataPOST example (POST statuses/update):response = client.api.statuses.update.post(status='Hello @pybirdy!')Dynamic URL example (POST statuses/destroy/:id):response=client.api.statuses.destroy['240854986559455234'].post()Streaming API example (Public Stream POST statuses/filter):response=client.stream.statuses.filter.post(track='twitter')fordatainresponse.stream():printdataSupported Python versionbirdyworks with bothpython2(2.7+) andpython3(3.4+).Why another Python Twitter API client? Aren’t there enough?The concept behindbirdyis so simple and awesome that it just had
to be done, and the result is a super light weight and easy to use API
client, that covers the whole Twitter REST API in just a little under
400 lines of code.To achieve this,birdyrelies on established, battle tested python
libraries likerequestsandrequests-ouathlibto do the heavy
lifting, but more importantly it relies on Python’s dynamic nature to
automatically construct API calls (no individual wrapper functions for
API resources needed). This allowsbirdyto cover all existing
Twitter API resources and any future additions, without the need to
updatebirdyitself.Includes full support for bothOAuth1(user) andOAuth2(application) authentication workflows.Finally,birdyis simple and explicit by design, besides error
handling and JSON decoding it doesn’t process the returned data in any
way, that is left for you to handle (who’d know better what to do with
it).OK, I’m sold, but how do I use it? How does this dynamic API construction work?The easiest way to show you is by example. Lets say you want to query
Twitter for @twitter user information. The Twitter API resource for this
isGET users/show(Twitter
docs).First you will need to import a client, here we import UserClient
(OAuth1) and than initialize it.frombirdy.twitterimportUserClientclient=UserClient(CONSUMER_KEY,CONSUMER_SECRET,ACCESS_TOKEN,ACCESS_TOKEN_SECRET)To query theGET /users/showAPI resource and pass in the parameter
screen_name=‘twitter’ you do this.resource=client.api.users.showresponse=resource.get(screen_name='twitter')What happens here is very simple,birdytranslates theusers.showpart afterclient.apiinto the appropriate API
resource path (‘users/show’). Then when you call get() on the
resource,birdyconstructs a full resource URL, appends any
parameters passed to get() to it and makes a GET request to that URL and
returns the result.Usually the above example would be shortened to just one line like this.response=client.api.users.show.get(screen_name='twitter')Making a post request is similar, if for example, you would like to post
a status update, this is how to do it. The API resource isPOST
statuses/update(Twitter
docs).response=client.api.statuses.update.post(status='Hello @pybirdy!')Like before the part afterclient.apigets converted to the correct
path, only this time post() is called instead of get(), sobirdymakes a POST request and pass parameters (and files) as part of the
request body.For cases when dynamic values are part of the API resource URL, like
when deleting a tweet atPOST statuses/destroy/:id(Twitter
docs),birdysupports an alternative, dictionary lookup like, syntax. For
example, deleting a tweet with id ‘240854986559455234’ looks like this.response=client.api.statuses.destroy['240854986559455234'].post()By now it should be clear what happens above,birdybuilds the API
resource path and than makes a POST request, the only difference is that
part of the API path is provided like a dictionary key lookup.Actually any call can be written in this alternative syntax, use
whichever you prefer. Both syntax forms can be freely combined as in the
example above. Some more examples:response=client.api['users/show'].get(screen_name='twitter')response=client.api['users']['show'].get(screen_name='twitter')response=client.api['statuses/destroy']['240854986559455234'].post()Is Streaming API supported as well?Sure, since version 0.2,birdycomes with full support for Streaming
API out of the box. Access to the Streaming API is provided by a specialStreamClient.StreamClientcan’t be used to obtain access tokens, but you can
useUserClientto get them.To work with the Streaming API, first import the client and initialize
it.frombirdy.twitterimportStreamClientclient=StreamClient(CONSUMER_KEY,CONSUMER_SECRET,ACCESS_TOKEN,ACCESS_TOKEN_SECRET)To access resources on thePublicstream, likePOST
statuses/filter(Twitter
docs)resource=client.stream.statuses.filter.post(track='twitter')ForUserstream resourceGET user(Twitter
docs)resource=client.userstream.user.get()And forSitestream resourceGET site(Twitter
docs)resource=client.sitestream.site.get()To access the data in the stream you iterate overresource.stream()like thisfordatainresource.stream():printdataGreat, what about authorization? How do I get my access tokens?birdysupports bothOAuth1andOAuth2authentication
workflows by providing two different clients, aUserClientandAppClientrespectively. While requests to API resources, like in
above examples are the same in both clients, the workflow for obtaining
access tokens is slightly different.Before you get started, you will need toregisteryour application with
Twitter, to obtain your application’sCONSUMER_KEYandCONSUMER_SECRET.OAuth1 workflow for user authenticated requests (UserClient)Step 1: Creating a client instanceFirst you need to import theUserClientand create an instance with
your appsCONSUMER_KEYandCONSUMER_SECRET.frombirdy.twitterimportUserClientCONSUMER_KEY='YOUR_APPS_CONSUMER_KEY'CONSUMER_SECRET='YOUR_APPS_CONSUMER_SECRET'CALLBACK_URL='https://127.0.0.1:8000/callback'client=UserClient(CONSUMER_KEY,CONSUMER_SECRET)Step 2: Get request token and authorization URLPasscallback_urlonly if you have a Web app, Desktop and Mobile
appsdo notrequire it.Next you need to fetch request token from Twitter. If you are building aSign-in with Twittertype application it’s done like this.token=client.get_signin_token(CALLBACK_URL)Otherwise like this.token=client.get_authorize_token(CALLBACK_URL)Savetoken.oauth_tokenandtoken.oauth_token_secretfor later
user, as this are not the final token and secret.ACCESS_TOKEN=token.oauth_tokenACCESS_TOKEN_SECRET=token.oauth_token_secretDirect the user to Twitter authorization url obtained fromtoken.auth_url.Step 3: OAuth verificationIf you have a Desktop or Mobile app,OAUTH_VERIFIERis the PIN
code, you can skip the part about extraction.After authorizing your application on Twitter, the user will be
redirected back to thecallback_urlprovided during client
initialization inStep 1.You will need to extract theOAUTH_VERIFIERfrom the URL. Most web
frameworks provide an easy way of doing this or you can parse the URL
yourself usingurlparsemodule (if that is your thing).Django and Flask examples:#DjangoOAUTH_VERIFIER=request.GET['oauth_verifier']#FlashOAUTH_VERIFIER=request.args.get('oauth_verifier')Once you have theOAUTH_VERIFIERyou can use it to obtain the final
access token and secret. To do that you will need to create a new
instance ofUserClient, this time also passing inACCESS_TOKENandACCESS_TOKEN_SECRETobtained inStep 2and then fetch the
tokens.client=UserClient(CONSUMER_KEY,CONSUMER_SECRET,ACCESS_TOKEN,ACCESS_TOKEN_SECRET)token=client.get_access_token(OAUTH_VERIFIER)Now that you have the final access token and secret you can savetoken.oauth_tokenandtoken.oauth_token_secretto the database
for later use, also you can use the client to start making API request
immediately. For example, you can retrieve the users home timeline like
this.response=client.api.statuses.home_timeline.get()response.dataThat’s it you have successfully authorized the user, retrieved the
tokens and can now make API calls on their behalf.OAuth2 workflow for app authenticated requests (AppClient)Step 1: Creating a client instanceFor OAuth2 you will be using theAppClient, so first you need to
import it and create an instance with your appsCONSUMER_KEYandCONSUMER_SECRET.frombirdy.twitterimportAppClientCONSUMER_KEY='YOUR_APPS_CONSUMER_KEY'CONSUMER_SECRET='YOUR_APPS_CONSUMER_SECRET'client=AppClient(CONSUMER_KEY,CONSUMER_SECRET)Step 2: Getting the access tokenOAuth2 workflow is much simpler compared to OAuth1, to obtain the access
token you simply do this.access_token=client.get_access_token()That’s it, you can start using the client immediately to make API
request on behalf of the app. It’s recommended you save theaccess_tokenfor later use. You initialize the client with a saved
token like this.client=AppClient(CONSUMER_KEY,CONSUMER_SECRET,SAVED_ACCESS_TOKEN)Keep in mind that OAuth2 authenticated requests areread-onlyand
not all API resources are available. CheckTwitter
docsfor more information.Any other useful features I should know about?Of course,birdycomes with some handy features, to ease your
development, right out of the box. Lets take a look at some of the
goodies.Automatic JSON decodingJSON data returned by the REST and Streaming API is automatically
decoded to native Python objects, no extra coding necessary, start using
the data right away.JSONObjectWhen decoding JSON data,objectsare, instead of a regular Python
dictionary, converted to aJSONObject, which is dictionary subclass
with attribute style access in addition to regular dictionary lookup
style, for convenience. The following code produces the same resultfollowers_count=response.data['followers_count']followers_count=response.data.followers_countApiResponseCalls to REST API resources return aApiResponse, which in addition
to returned data, also gives you access to response headers (useful for
checking rate limits) and resource URL.response.data# decoded JSON dataresponse.resource_url# resource URLresponse.headers# dictionary containing response HTTP headersStreamResponseStreamResponseis returned when calling Streaming API resources and
provides thestream()method which returns an iterator used to
receive JSON decoded streaming data. LikeApiResponseit also gives
you access to response headers and resource URL.response.stream()# a generator method used to iterate over the streamfordatainresponse.stream():printdataInformative exceptionsThere are 4 types of exceptions inbirdyall subclasses of baseBirdyException(which is never directly raised).TwitterClientErrorraised for connection and access token
retrieval errorsTwitterApiErrorraised when Twitter returns an errorTwitterAuthErrorraised when authentication fails,TwitterApiErrorsubclassTwitterRateLimitErrorraised when rate limit for resource is
reached,TwitterApiErrorsubclassTwitterApiErrorandTwitterClientErrorinstances (exepct for
access token retrieval errors) provide a informative error description
which includes the resource URL and request method used (very handy when
tracking errors in logs), also available is the following:exception.request_method# HTTP method used to make the request (GET or POST)exception.resource_url# URL of the API resource calledexception.status_code# HTTP status code returned by Twitterexception.error_code# error code returned by Twitterexception.headers# dictionary containing response HTTP headersCustomize and extend through subclassingbirdywas built with subclassing in mind, if you wish to change the
way it works, all you have to do is subclass one of the clients and
override some methods and you are good to go.Subclassing a client and then using the subclass instance in your
codeis actuallythe recommended wayof usingbirdy.For example, if you don’t wish to useJSONObjectyou have to
overrideget_json_object_hook()method.frombirdy.twitterimportUserClientclassMyClient(UserClient):@staticmethoddefget_json_object_hook(data):returndataclient=MyClient(...)response=client.api.users.show.get(screen_name='twitter')Or maybe, if you want global error handling for common errors, just
overridehandle_response()method.classMyClient(UserClient):defhandle_response(self,method,response):try:response=super(MyClient,self).handle_response(method,response)exceptTwitterApiError,e:...# Your error handling code...returnresponseAnother use of subclassing is configuration ofrequests.Sessioninstance
(docs)
used to make HTTP requests, to configure it, you override theconfigure_oauth_session()method.classMyClient(UserClient):defconfigure_oauth_session(self,session):session=super(MyClient,self).configure_oauth_session(session)session.proxies={'http':'foo.bar:3128'}returnsessionDo you accept contributions and feature requests?Yes, both contributions (including feedback) and feature requests
are welcome, the proper way in both cases is to first open an issue onGitHuband we will take if
from there.Keep in mind that I work on this project on my free time, so I might
not be able to respond right way.Creditsbirdywould not exists if not for the excellentrequestsandrequests-oauthliblibraries and the wonderfulPythonprograming language.Question, comments, …If you need to contact me, you can find me on Twitter
([@sect2k](https://twitter.com/sect2k/)).