Metadata-Version: 1.0
Name: peony-twitter
Version: 0.1
Summary: An asynchronous Twitter API client for Python
Home-page: https://github.com/odrling/peony-twitter
Author: Florian Badie
Author-email: florianbadie@gmail.com
License: MIT License
Description: Peony
        ===
        
        An asynchronous API client for Python
        
        Summary
        ===
        
        * [Installation](#install)
        * [Getting Started](#getstarted)
        * [Usage](#usage)
          * [How to access the response data](#access)
            * [From a REST API](#rest_access)
            * [From a Streaming API](#stream_access)
          * [Iterators](#iterators)
            * [Cursor iterator](#cursor_iterator)
            * [Max_id iterator](#max_id_iterator)
            * [Since_id iterator](#since_id_iterator)
          * [Tasks](#tasks)
            * [The `task` decorator](#task)
          * [Event handlers](#event_handler)
        * [Advanced Usage](#adv_usage)
        
        <h2 id='install'>Installation</h2>
        
        To install this module simply run:  
        
            pip install peony-twitter
        
        <h1 id='getstarted'>Getting started</h1>
        
        You can easily create a client using the class `PeonyClient`.
        Make sure to get your api keys and access tokens from
        [Twitter's application management page][apps]
        
        *Note: the package name is peony and not peony-twitter*
        
        
        ```python
        import asyncio
        
        # Note: the package name is peony and not peony-twitter
        from peony import PeonyClient
        
        loop = asyncio.get_event_loop()
        
        # create the client using your api keys
        client = PeonyClient(consumer_key=YOUR_CONSUMER_KEY,
                             consumer_secret=YOUR_CONSUMER_SECRET,
                             access_token=YOUR_ACCESS_TOKEN,
                             access_token_secret=YOUR_ACCESS_TOKEN_SECRET)
        
        # this is a coroutine
        req = client.api.statuses.update.post(status="I'm using Peony!!")
        
        # run the coroutine
        loop.run_until_complete(req)
        ```
        
        
        [apps]: <https://apps.twitter.com>
        
        
        <h1 id='usage'>Usage</h1>
        
        You can easily access any Twitter API endpoint
        
        ```python
        creds = dict(consumer_key=YOUR_CONSUMER_KEY,
                     consumer_secret=YOUR_CONSUMER_SECRET,
                     access_token=YOUR_ACCESS_TOKEN,
                     access_token_secret=YOUR_ACCESS_TOKEN_SECRET)
        
        client = PeonyClient(**creds)
        
        # to access api.twitter.com/1.1/statuses/home_timeline.json
        # using the GET method with the parameters count and since_id
        async def home():
            return await client.api.statuses.home_timeline.get(count=200, since_id=0)
        
        # to access userstream.twitter.com/1.1/statuses/filter.json
        # using the POST method with the parameter track
        async def track():
            async with client.stream.statuses.filter.post(track="uwu") as ressource:
                pass  # do something, see next chapter
        
        # would GET subdomain.twitter.com/1.1/path.json if it were
        # an API endpoint
        async def path():
            return await client.subdomain.path.get()
        ```
        
        see [Accessing an API using a different api version](#adv_api) to access APIs
        that do not use the version '1.1'
        
        <h2 id='response_access'>How to access the response data</h2>
        
        <h3 id='rest_access'>From a REST API</h3>
        
        A call to a REST API endpoint should return a PeonyResponse object.
        
        
        ```python
        async def home():
            req = client.api.statuses.home_timeline.get(count=200, since_id=0)
        
            # this is a PeonyResponse object
            response = await req
        
            # you can iterate over the response object
            for tweet in response:
                # you can access items as you would do in a dictionnary
                user_id = tweet['user']['id']
        
                # or as you would access an attribute
                username = tweet.user.screen_name
        
                print("@{username} ({id}): {text}".format(username=username,
                                                          id=user_id,
                                                          text=tweet.text))
        ```
        
        
        <h3 id='stream_access'>From a Streaming API</h3>
        
        A call to a Streaming API endpoint should return a StreamContext object, that
        yields a StreamResponse object.
        
        ```python
        async def track():
            ctx = client.stream.statuses.filter.post(track="uwu")
        
            # this is an asynchronous context (StreamContext)
            async with ctx as stream:
                # stream is an asynchronous iterator (StreamResponse)
                async for tweet in stream:
                    # you can then access items as you would do with a
                    # PeonyResponse object
                    user_id = tweet['user']['id']
                    username = tweet.user.screen_name
        
                    print("@{username} ({id}): {text}".format(username=username,
                                                              id=user_id,
                                                              text=tweet.text))
        ```
        
        <h2 id='iterators'>Iterators</h2>
        
        Sometimes you need to make several requests to the same API endpoint in order
        to get all the data you want (e.g. getting more than 200 tweets of an user).  
        Some iterators are included in Peony and usable through the peony.iterators
        module that deals with the actual iteration, getting all the responses you need.
        
        <h3 id='cursor_iterator'>Cursor iterator</h3>
        
        This is an iterator for endpoints using the `cursor` parameter
        (e.g. followers/ids.json). The first argument given to the iterator is the
        coroutine function that will make the request.
        
        
        ```python
        import peony.iterators
        from peony import PeonyClient
        
        # creds being a dictionnary containing your api keys
        client = PeonyClient(**creds)
        
        async def get_followers(user_id, **additional_params):
            followers_ids = peony.iterators.with_cursor(
                client.api.followers.ids.get,
                id=user_id,
                count=5000,
                **additional_params
            )
        
            followers = []
            async for data in followers_ids:
                followers.extend(data.ids)
        
            return followers
        ```
        
        <h3 id="max_id_iterator">Max_id iterator</h3>
        An iterator for endpoints using the `max_id` parameter
        (e.g. statuses/user_timeline.json)
        
        
        ```python
        from peony import PeonyClient
        import peony.iterators
        
        client = PeonyClient(**creds)
        
        async def get_tweets(user_id, n_tweets=1600, **additional_params):
              responses = peony.iterators.with_max_id(
                  client.api.statuses.user_timeline.get,
                  user_id=user,
                  count=200,
                  **additional_params
              )
        
              user_tweets = []
        
              async for tweets in responses:
                  user_tweets.extend(tweets)
        
                  if len(user_tweets) >= n_tweets:
                      user_tweets = user_tweets[:n_tweets]
                      break
        
              return user_tweets
        ```
        
        <h3 id='since_id_iterator'>Since_id iterator</h3>
        An iterator for endpoints using the `since_id` parameter
        (e.g. statuses/home_timeline.json)
        
        
        ```python
        import asyncio
        import html
        
        try:
            from . import peony, api, testdir
        except SystemError:
            from __init__ import peony, testdir
            import api
        
        
        client = peony.PeonyClient(**api.keys)
        
        async def get_home(since_id=None, **params):
            responses = peony.iterators.with_since_id(
                client.api.statuses.home_timeline.get,
                count=200,
                **params
            )
        
            home = []
            async for tweets in responses:
                for tweet in reversed(tweets):
                    text = html.unescape(tweet.text)
                    print("@{user.screen_name}: {text}".format(user=tweet.user,
                                                               text=text))
                    print("-"*10)
        
                await asyncio.sleep(180)
        
            return sorted(home, key=lambda tweet: tweet.id)
        ```
        
        <h2 id='tasks'>Tasks</h2>
        
        The main advantage of an asynchronous client is that it will be able to run
        multiple tasks... asynchronously.  
        Which is quite interesting here if you want to access several Streaming APIs,
        or perform some requests periodically while using a Streaming API.
        
        
        So I tried to make it easier to create such a program.
        
        <h2 id='task'>The `task` decorator</h2>
        
        First you will need to create a subclass of PeonyClient and add a `task`
        decorator to the methods that you want to run.
        
        
        ```python
        import asyncio
        import time
        
        from peony import PeonyClient, task
        
        class AwesomePeonyClient(PeonyClient):
            @staticmethod
            async def wait_awesome_hour():
                """ wait until the next awesome hour """
                await asyncio.sleep(-time.time() % 3600)
        
            async def send_awesome_tweet(self, status="Peony is awesome!!"):
                """ send an awesome tweet """
                await self.api.statuses.update.post(status=status)
        
            @task
            async def awesome_loop(self):
                """ send an awesome tweet every hour """
                while True:
                    await self.wait_awesome_hour()
                    await self.send_awesome_tweet()
        
            @task
            async def awesome_user(self):
                """ The user using this program must be just as awesome, right? """
                user = await self.api.account.verify_credentials.get()
        
                print("This is an awesome user", user.screen_name)
        
            @task
            async def awesome_stream(self):
                """
                    Tweets that contain awesome without a typo must be
                    quite awesome too
                """
                async with self.stream.statuses.filter(track="awesome") as stream:
                    async for tweet in stream:
                        print("This is an awesome tweet", tweet.text)
        
        
        def main():
            """ run all the tasks simultaneously """
            loop = asyncio.get_event_loop()
        
            # set your api keys here
            awesome_client = AwesomePeonyClient(
                consumer_key=your_consumer_key,
                consumer_secret=your_consumer_secret,
                access_token=your_access_token,
                access_token_secret=your_access_token_secret
            )
        
            asyncio.ensure_future(asyncio.wait(awesome_client.tasks))
            loop.run_forever()
        
            # if there was no stream:
            # loop.run_until_complete(asyncio.wait(awesome_client.tasks))
        
        
        if __name__ == '__main__':
            main()
        ```
        
        <h2 id="event_handlers">Event handlers</h2>
        
        Let's say that your awesome bot has become very popular, and so you'd like to
        add some new features to it that would make use of the Streaming API. You could
        use the `task` decorator but there is a better way to do it.  
        
        *keeping the code from above*
        ```python
        
        from peony import EventStream, event_handler, events
        
        # adding permissions dirtily, you should probably try to load them in
        # AwesomePeonyClient.__init__ instead
        AwesomePeonyClient.permissions = {
            "admin": [42]
        }
        
        @AwesomePeonyClient.event_stream
        class AwesomeUserStream(EventStream):
        
            @property
            def stream_request(self):
                # stream_request must return the request used to access the stream
                return self.userstream.user.get()
        
            @event_handler(*events.on_connect)
            def awesome_connection(self, data):
                print("Connected to stream!")
        
            @event_handler(*events.on_follow)
            def awesome_follow(self, data, *args):
                print("You have a new awesome follower @%s" % data.source.screen_name)
        
            # when adding a prefix argument to an event handler it adds a
            # command attribute to the function that you can use as a decorator
            # to create commands
            # it also adds a command argument to the event_handler
            @event_handler(*events.on_dm, prefix='/')
            async def awesome_dm_received(self, data, command):
                # Important: command.run is a coroutine
                msg = await command.run(self, data=data.direct_message)
        
                if msg:
                    await self.api.direct_messages.new.post(
                        user_id=data.direct_message.sender.id,
                        text=msg
                    )
        
            # Here a command is called when the dm contains:
            # "{prefix}{command_name}"
            # So this command is called when an user sends a dm which
            # contains "/awesome_reply"
            @on_awesome_dm_received.command
            def awesome_reply(self, data):
                return "I can send awesome dms too!"
        
            # user must have op permission to use this command
            @on_awesome_dm_received.command.restricted('op')
            async def awesome_tweet(self, data):
                awesome_status = " ".join(word for word in data.text.split()
                                          if word != "/awesome_tweet")
                await self.api.statuses.update.post(status=awesome_status)
        
                return "sent " + awesome_status
        
            # user must have admin or op permission to use this command
            @on_awesome_dm_received.command.restricted('admin', 'op')
            async def awesome_smiley(self, data):
                return "( ﾟ▽ﾟ)/awesome"
        
        ```
        
        
        <h1 id='adv_usage'>Advanced Usage</h1>
        
        <h2 id='adv_api'>Accessing an API using a different api version</h2>
        
        There actually two ways:
        * create a client with an `api_version` argument
        * provide the api version with the subdomain of the api when creating the path to the ressource
        
        ### Create a client with a custom api version
        
        ```python
        # creds being a dict with your api_keys
        # notice the use of the `suffix` argument to change the default
        # extension ('.json')
        client = PeonyClient(**creds, api_version='1', suffix='')
        
        # params being the parameters of the request
        req = client['ads-api'].accounts[id].reach_estimate.get(**params)
        ```
        
        ### Add a version when creating the request
        
        ```python
        # notice the use of the `_suffix` argument to change the default
        # extension for a request
        
        # using a tuple as key
        req = client['ads-api', '1'].accounts[id].reach_estimate.get(_suffix='',
                                                                     **kwargs)
        
        # using a dict as key
        ads = client[dict(api='ads-api', version='1')]
        req = ads.accounts[id].reach_estimate.get(**kwargs, _suffix='')
        ```
        
        You can also add more arguments to the tuple or dictionnary
        
        ```python
        # with a dictionnary
        adsapi = dict(
            api='ads-api',
            version='1',
            suffix='',
            base_url='https://{api}.twitter.com/{version}'
        )
        
        req = client[adsapi].accounts[id].reach_estimate.get(**kwargs,)
        
        
        # with a tuple
        ads = client['ads-api', '1', '', 'https://{api}.twitter.com/{version}']
        req = ads.accounts[id].reach_estimate.get(**kwargs)
        ```
        
Keywords: twitter,asyncio,asynchronous
Platform: UNKNOWN
