{"id":13502420,"url":"https://github.com/inueni/birdy","last_synced_at":"2025-12-24T01:54:25.257Z","repository":{"id":10317756,"uuid":"12444968","full_name":"inueni/birdy","owner":"inueni","description":"A super awesome Twitter API client for Python.","archived":false,"fork":false,"pushed_at":"2021-07-31T21:34:28.000Z","size":41,"stargazers_count":263,"open_issues_count":8,"forks_count":49,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-02-13T05:17:47.651Z","etag":null,"topics":["python","twitter-api","twitter-client","twitter-streaming-api"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/inueni.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-08-28T21:24:04.000Z","updated_at":"2024-12-02T21:54:43.000Z","dependencies_parsed_at":"2022-08-30T13:50:29.491Z","dependency_job_id":null,"html_url":"https://github.com/inueni/birdy","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inueni%2Fbirdy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inueni%2Fbirdy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inueni%2Fbirdy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inueni%2Fbirdy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inueni","download_url":"https://codeload.github.com/inueni/birdy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246174208,"owners_count":20735406,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["python","twitter-api","twitter-client","twitter-streaming-api"],"created_at":"2024-07-31T22:02:13.287Z","updated_at":"2025-12-24T01:54:25.246Z","avatar_url":"https://github.com/inueni.png","language":"Python","funding_links":[],"categories":["Python","Tools"],"sub_categories":[],"readme":"# birdy\n\n`birdy` is a super awesome Twitter API client for Python in just a little under 400 LOC.\n\n## TL;DR\n\n### Features\n\n  - [Future proof dynamic API with full REST and Streaming API coverage](#ok-im-sold-but-how-do-i-use-it-how-does-this-dynamic-api-construction-work)\n  - [OAuth1 (user) and OAuth2 (app) authentication workflows](#great-what-about-authorization-how-do-i-get-my-access-tokens)\n  - [Automatic JSON decoding](#automatic-json-decoding),\n    [JSONObject](#jsonobject)\n  - [ApiResponse](#apiresponse), [StreamResponse](#streamresponse) objects\n  - [Informative exceptions](#informative-exceptions)\n  - [Easily customizable through subclassing](#customize-and-extend-through-subclassing)\n  - [Built on top of the excellent requests and requests-ouathlib libraries](#credits)\n\n\n### Installation\n\nThe easiest and recommended way to install `birdy` is from [PyPI](https://pypi.python.org/pypi/birdy)\n\n```\npip install birdy\n```\n\n### Usage\n\nImport client and initialize it:\n\n```python\nfrom birdy.twitter import UserClient\nclient = UserClient(CONSUMER_KEY,\n                    CONSUMER_SECRET,\n                    ACCESS_TOKEN,\n                    ACCESS_TOKEN_SECRET)\n```\n\nGET example (**GET users/show**):\n\n```python\nresponse = client.api.users.show.get(screen_name='twitter')\nresponse.data\n```\n\nPOST example (**POST statuses/update**):\n\n```pyhton\nresponse = client.api.statuses.update.post(status='Hello @pybirdy!')\n```\n\nDynamic URL example (**POST statuses/destroy/:id**):\n\n```python\nresponse = client.api.statuses.destroy['240854986559455234'].post()\n```\n\nStreaming API example (**Public Stream POST statuses/filter**):\n\n```python\nresponse = client.stream.statuses.filter.post(track='twitter')\n\nfor data in response.stream():\n    print data\n```\n\n## Supported Python version\n\n`birdy` works with both `python2` (2.7+) and `python3` (3.4+). \n\n## Why another Python Twitter API client? Aren't there enough?\n\nThe concept behind `birdy` is so simple and awesome that it just had to be done, and the result is a super light weight and easy to use API\nclient, that covers the whole Twitter REST API in just a little under 400 lines of code.\n\nTo achieve this, `birdy` relies on established, battle tested python libraries like `requests` and `requests-ouathlib` to do the heavy\nlifting, but more importantly it relies on Python's dynamic nature to automatically construct API calls (no individual wrapper functions for API resources needed). This allows `birdy` to cover all existing Twitter API resources and any future additions, without the need to update `birdy` itself.\n\nIncludes full support for both **OAuth1** (user) and **OAuth2** (application) authentication workflows.\n\nFinally, `birdy` is 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).\n\n## OK, I'm sold, but how do I use it? How does this dynamic API construction work?\n\nThe 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 is **GET users/show** ([Twitter docs](https://dev.twitter.com/docs/api/1.1/get/users/show)).\n\nFirst you will need to import a client, here we import UserClient (OAuth1) and than initialize it.\n\n```python\nfrom birdy.twitter import UserClient\nclient = UserClient(CONSUMER_KEY,\n                    CONSUMER_SECRET,\n                    ACCESS_TOKEN,\n                    ACCESS_TOKEN_SECRET)\n```\n\nTo query the **GET /users/show** API resource and pass in the parameter screen\\_name='twitter' you do this.\n\n```python\nresource = client.api.users.show\nresponse = resource.get(screen_name='twitter')\n```\n\nWhat happens here is very simple, `birdy` translates the `users.show` part after `client.api` into the appropriate API resource path\n(**'users/show'**). Then when you call get() on the resource, `birdy` constructs a full resource URL, appends any parameters passed to get() to it and makes a GET request to that URL and returns the result.\n\nUsually the above example would be shortened to just one line like this.\n\n```python\nresponse = client.api.users.show.get(screen_name='twitter')\n```\n\nMaking 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 is **POST\nstatuses/update** ([Twitter docs](https://dev.twitter.com/docs/api/1.1/post/statuses/update)).\n\n```python\nresponse = client.api.statuses.update.post(status='Hello @pybirdy!')\n```\n\nLike before the part after `client.api` gets converted to the correct path, only this time post() is called instead of get(), so `birdy` makes a POST request and pass parameters (and files) as part of the request body.\n\nFor cases when dynamic values are part of the API resource URL, like when deleting a tweet at **POST statuses/destroy/:id** ([Twitter\ndocs](https://dev.twitter.com/docs/api/1.1/post/statuses/destroy/:id)), `birdy` supports an alternative, dictionary lookup like, syntax. For example, deleting a tweet with id '240854986559455234' looks like this.\n\n``` python\nresponse = client.api.statuses.destroy['240854986559455234'].post()\n```\n\nBy now it should be clear what happens above, `birdy` builds 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.\n\nActually 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:\n\n```python\nresponse = client.api['users/show'].get(screen_name='twitter')\n\nresponse = client.api['users']['show'].get(screen_name='twitter')\n\nresponse = client.api['statuses/destroy']['240854986559455234'].post()\n```\n\n### Is Streaming API supported as well?\n\nSure, since version 0.2, `birdy` comes with full support for Streaming API out of the box. Access to the Streaming API is provided by a special `StreamClient`.\n\n\u003e `StreamClient` can't be used to obtain access tokens, but you can use `UserClient` to get them.\n\nTo work with the Streaming API, first import the client and initialize it.\n\n```python\nfrom birdy.twitter import StreamClient\nclient = StreamClient(CONSUMER_KEY,\n                    CONSUMER_SECRET,\n                    ACCESS_TOKEN,\n                    ACCESS_TOKEN_SECRET)\n```\n\nTo access resources on the **Public** stream, like **POST statuses/filter** ([Twitter docs](https://dev.twitter.com/docs/api/1.1/post/statuses/filter))\n\n```python\nresource = client.stream.statuses.filter.post(track='twitter')\n```\n\nFor **User** stream resource **GET user** ([Twitter docs](https://dev.twitter.com/docs/api/1.1/get/user))\n\n```python\nresource = client.userstream.user.get()\n```\n\nAnd for **Site** stream resource **GET site** ([Twitter docs](https://dev.twitter.com/docs/api/1.1/get/site))\n\n```python\nresource = client.sitestream.site.get()\n```\n\nTo access the data in the stream you iterate over `resource.stream()` like this\n\n```python\nfor data in resource.stream():\n   print data\n```\n\n## Great, what about authorization? How do I get my access tokens?\n\n`birdy` supports both **OAuth1** and **OAuth2** authentication workflows by providing two different clients, a `UserClient` and `AppClient`\nrespectively. While requests to API resources, like in above examples are the same in both clients, the workflow for obtaining access tokens is slightly different.\n\n\u003e Before you get started, you will need to [register](https://dev.twitter.com/apps) your application with Twitter, to obtain your application's `CONSUMER_KEY` and `CONSUMER_SECRET`.\n\n### OAuth1 workflow for user authenticated requests (UserClient)\n\n#### Step 1: Creating a client instance\n\nFirst you need to import the `UserClient` and create an instance with your apps `CONSUMER_KEY` and `CONSUMER_SECRET`.\n\n```python\nfrom birdy.twitter import UserClient\n\nCONSUMER_KEY = 'YOUR_APPS_CONSUMER_KEY'\nCONSUMER_SECRET = 'YOUR_APPS_CONSUMER_SECRET'\nCALLBACK_URL = 'https://127.0.0.1:8000/callback'\n\nclient = UserClient(CONSUMER_KEY, CONSUMER_SECRET)\n```\n\n#### Step 2: Get request token and authorization URL\n\n\u003e Pass `callback_url` only if you have a Web app, Desktop and Mobile apps **do not** require it.\n\nNext you need to fetch request token from Twitter. If you are building a _Sign-in with Twitter_ type application it's done like this.\n\n```python\ntoken = client.get_signin_token(CALLBACK_URL)\n```\n\nOtherwise like this.\n\n```python\ntoken = client.get_authorize_token(CALLBACK_URL)\n```\n\nSave `token.oauth_token` and `token.oauth_token_secret` for later user, as this are not the final token and secret.\n\n```python\nACCESS_TOKEN = token.oauth_token\nACCESS_TOKEN_SECRET = token.oauth_token_secret\n```\n\nDirect the user to Twitter authorization url obtained from `token.auth_url`.\n\n#### Step 3: OAuth verification\n\n\u003e If you have a Desktop or Mobile app, `OAUTH_VERIFIER` is the PIN code, you can skip the part about extraction.\n\nAfter authorizing your application on Twitter, the user will be redirected back to the `callback_url` provided during client initialization in *Step 1*.\n\nYou will need to extract the `OAUTH_VERIFIER` from the URL. Most web frameworks provide an easy way of doing this or you can parse the URL yourself using `urlparse` module (if that is your thing).\n\nDjango and Flask examples:\n\n```python\n#Django\nOAUTH_VERIFIER = request.GET['oauth_verifier']\n\n#Flash\nOAUTH_VERIFIER = request.args.get('oauth_verifier')\n```\n\nOnce you have the `OAUTH_VERIFIER` you can use it to obtain the final access token and secret. To do that you will need to create a new instance of `UserClient`, this time also passing in `ACCESS_TOKEN` and `ACCESS_TOKEN_SECRET` obtained in *Step 2* and then fetch the tokens.\n\n```python\nclient = UserClient(CONSUMER_KEY, CONSUMER_SECRET,\n                    ACCESS_TOKEN, ACCESS_TOKEN_SECRET)\n\ntoken = client.get_access_token(OAUTH_VERIFIER)\n```\n\nNow that you have the final access token and secret you can save `token.oauth_token` and `token.oauth_token_secret` to 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.\n\n```python\nresponse = client.api.statuses.home_timeline.get()\nresponse.data\n```\n\nThat's it you have successfully authorized the user, retrieved the tokens and can now make API calls on their behalf.\n\n### OAuth2 workflow for app authenticated requests (AppClient)\n\n#### Step 1: Creating a client instance\n\nFor OAuth2 you will be using the `AppClient`, so first you need to import it and create an instance with your apps `CONSUMER_KEY` and `CONSUMER_SECRET`.\n\n```python\nfrom birdy.twitter import AppClient\n\nCONSUMER_KEY = 'YOUR_APPS_CONSUMER_KEY'\nCONSUMER_SECRET = 'YOUR_APPS_CONSUMER_SECRET'\n\nclient = AppClient(CONSUMER_KEY, CONSUMER_SECRET)\n```\n\n#### Step 2: Getting the access token\n\nOAuth2 workflow is much simpler compared to OAuth1, to obtain the access token you simply do this.\n\n```python\naccess_token = client.get_access_token()\n```\n\nThat's it, you can start using the client immediately to make API request on behalf of the app. It's recommended you save the `access_token` for later use. You initialize the client with a saved token like this.\n\n```python\nclient = AppClient(CONSUMER_KEY, CONSUMER_SECRET, SAVED_ACCESS_TOKEN)\n```\n\nKeep in mind that OAuth2 authenticated requests are **read-only** and not all API resources are available. Check [Twitter docs](https://dev.twitter.com/docs/api/1.1) for more information.\n\n## Any other useful features I should know about?\n\nOf course, `birdy` comes with some handy features, to ease your development, right out of the box. Lets take a look at some of the\ngoodies.\n\n### Automatic JSON decoding\n\nJSON 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.\n\n### JSONObject\n\nWhen decoding JSON data, `objects` are, instead of a regular Python dictionary, converted to a `JSONObject`, which is dictionary\nsubclass with attribute style access in addition to regular dictionary lookup style, for convenience. The following code produces the same\nresult\n\n```python\nfollowers_count = response.data['followers_count']\n\nfollowers_count = response.data.followers_count\n```\n\n### ApiResponse\n\nCalls to REST API resources return a `ApiResponse`, which in addition to returned data, also gives you access to response headers (useful for checking rate limits) and resource URL.\n\n```python\nresponse.data           # decoded JSON data\nresponse.resource_url   # resource URL\nresponse.headers        # dictionary containing response HTTP headers\n```\n\n### StreamResponse\n\n`StreamResponse` is returned when calling Streaming API resources and provides the **stream()** method which returns an iterator used to\nreceive JSON decoded streaming data. Like `ApiResponse` it also gives you access to response headers and resource URL.\n\n```python\nresponse.stream()       # a generator method used to iterate over the stream\n\nfor data in response.stream():\n    print data \n```\n\n### Informative exceptions\n\nThere are 4 types of exceptions in `birdy` all subclasses of base `BirdyException` (which is never directly raised).\n\n  - `TwitterClientError` raised for connection and access token retrieval errors\n  - `TwitterApiError` raised when Twitter returns an error\n  - `TwitterAuthError` raised when authentication fails,\n    `TwitterApiError` subclass\n  - `TwitterRateLimitError` raised when rate limit for resource is reached, `TwitterApiError` subclass\n\n`TwitterApiError` and `TwitterClientError` instances (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:\n\n```python\nexception.request_method    # HTTP method used to make the request (GET or POST)\nexception.resource_url      # URL of the API resource called\nexception.status_code       # HTTP status code returned by Twitter\nexception.error_code        # error code returned by Twitter\nexception.headers           # dictionary containing response HTTP headers\n```\n\n### Customize and extend through subclassing\n\n`birdy` was 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.\n\n\u003e Subclassing a client and then using the subclass instance in your codeis actually **the recommended way** of using `birdy`.\n\nFor example, if you don't wish to use `JSONObject` you have to override **get\\_json\\_object\\_hook()** method.\n\n```python\nfrom birdy.twitter import UserClient\n\nclass MyClient(UserClient):\n    @staticmethod\n    def get_json_object_hook(data):\n        return data\n\nclient = MyClient(...)\nresponse = client.api.users.show.get(screen_name='twitter')\n```\n\nOr maybe, if you want global error handling for common errors, just override **handle\\_response()** method.\n\n```python\nclass MyClient(UserClient):\n    def handle_response(self, method, response):\n        try:\n            response = super(MyClient, self).handle_response(method, response)\n        except TwitterApiError, e:\n            ...\n            # Your error handling code\n            ...\n        return response\n```\n\nAnother use of subclassing is configuration of `requests.Session` instance ([docs](http://docs.python-requests.org/en/latest/api/#sessionapi)) used to make HTTP requests, to configure it, you override the\n**configure\\_oauth\\_session()** method.\n\n```python\nclass MyClient(UserClient):\n    def configure_oauth_session(self, session):\n        session = super(MyClient, self).configure_oauth_session(session)\n        session.proxies = {'http': 'foo.bar:3128'}\n    return session\n```\n\n## Do you accept contributions and feature requests?\n\n**Yes**, both contributions (including feedback) and feature requests\nare welcome, the proper way in both cases is to first open an issue on\n[GitHub](https://github.com/inueni/birdy/issues) and we will take if\nfrom there.\n\n\u003e Keep in mind that I work on this project on my free time, so I might not be able to respond right way.\n\n## Credits\n\n`birdy` would not exists if not for the excellent [requests](http://www.python-requests.org) and [requests-oauthlib](https://requests-oauthlib.readthedocs.org/en/latest/) libraries and the wonderful [Python](http://www.python.org) programing language.\n\n## Question, comments, ...\n\nIf you need to contact me, you can find me on Twitter ([@sect2k](https://twitter.com/sect2k/)).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finueni%2Fbirdy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finueni%2Fbirdy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finueni%2Fbirdy/lists"}