Develop and Deploy a server less python application that updates Twitter banner in real time
Tags: python, serverless, twitter, showdev
Published: 2021-06-12 14:58:33


Hey hope you are staying positive and testing negative in the  pandemic 🙌🏻

![Hello gif](https://media.giphy.com/media/fWfowxJtHySJ0SGCgN/giphy.gif)

Let's check step by step on how to make your python serverless application that updates your Twitter banner or you could do anything, this has a huge potential.

We will be using a service called [Deta](https://deta.sh/), as their tagline says, "Build your apps in hours, deploy them in seconds." we will be doing the same :D
Btw it is completely free now so feel free to use it 🔥

This all started with this tweet of mine

{% twitter 1401894233632616448 %}

Then this 

{% twitter 1403429001247399938 %}

We will be covering the latest tweet application here, if you want the notion one too, let me know in the comments below 

So let's break it down step by step and make the application.

Let's make a list of requirements 
- [Twitter Developer account](https://developer.twitter.com/)
- [Deta Account](https://deta.sh/)
- 10-15 minutes of your time 😌


## Twitter Developer Account
Apply for a Twitter developer account if you haven't already, they will take some time for verification but after then you will be good to go.

### Step 1 - Create a new Twitter application

Head over to [Projects and Apps Tab](https://developer.twitter.com/en/portal/projects-and-apps) and create a new application

![Step 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jmu4aaveeh30ibj6r7nk.png)


### Step 2 - Name your application

![Step 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8gx1kgbgynefsblnnr5x.png)

### Step 3 - Consumer keys and secret

This is an important one, you will be getting your keys and tokens so please note them.
![Step 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0db3ii16lw9jj3dcg9wg.png)


### Step 4 - Permissions 

Once you go to the project dashboard head over to the app permissions, click edit and make the app `Read and Write`.

![Step 4 - 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jpjl06gczit51u18n4g.png)
![Step 4 - 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/918wpk7tutntsar9sdnr.png)

### Step 5 - Access token and access secret

The step y'all have been waiting for getting the tokens.
![Step 5 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skouhxd0pi87115qcadv.png)
![Step 5 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sf8nrmvygy4674x1bngj.png)


By end of this process, we will be having

The two keys from step 3
- CONSUMER_KEY
- CONSUMER_SECRET

The two keys from step 5
- ACCESS_TOKEN
- ACCESS_TOKEN_SECRET


## Deta 
![Deta logo](https://uploads-ssl.webflow.com/5eb96efa78dc680fc15be3be/5ebd24f6cbf6e9ebd674656e_Logo.svg)

[Docs](https://docs.deta.sh/docs/home/) of the Deta are amazing, you can check that if you have any issues with the application.

### Step 0 - Install Deta
[Install Deta](https://docs.deta.sh/docs/cli/install) based on your OS.

TL;DR
For Mac and Linux
```bash
curl -fsSL https://get.deta.dev/cli.sh | sh
```
For Windows
```bash
iwr https://get.deta.dev/cli.ps1 -useb | iex
```

then please do a `deta login` in your preferred terminal.

There is a known issue with Safari and Brave browser, so please use chrome when performing this step. 

### Step 1 - Create new project

We will be creating a new [Deta Micro](https://docs.deta.sh/docs/micros/about)

To create a new micro with python run the following command.

`deta new --python banner_update`

Here `banner_update` is the application name.

To constantly check for updates and deploy them to the cloud `deta` offers a command, it's called `deta watch`.

Open a new terminal and run `deta watch` and leave it aside until we finish the project :D 

### Step 2 - Install the dependencies 
Create a `requirements.txt` file in the root directory and have the required packages listed in the file.

You can use the following packages for this application.

```txt
requests
fastapi
python-dotenv
tweepy
Pillow
```

### Step 3 - Environment variables
Create a `.env.` file 
Your `.env` file should look something similar to this 

```txt
CONSUMER_KEY="your key"
CONSUMER_SECRET="your secret"
ACCESS_TOKEN="your token"
ACCESS_TOKEN_SECRET="your secret"
```

create a `.detaignore` file and add `!.env` to it.

![Env file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pj7spsg3w7ggr0xg2bc4.png)

Deta ignores `.env` file by default.


### Step 4 Python Code

![Let's code](https://media.giphy.com/media/LmNwrBhejkK9EFP504/giphy.gif)

You can head over to [Headers me](https://www.headers.me/) and create a base for your twitter banner.

I created my base and it looks like this

![Twitter banner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/96o5lxxi8tz43kkyjxz6.png)

Download font of your choice in `.ttf` format. We need the font to write on the image.

The entire code looks something like this, don't worry let's go over it step by step.

```python
from deta import App
from fastapi import FastAPI
from dotenv import load_dotenv
from os.path import join, dirname
import os
import tweepy
import shutil
import tempfile
from PIL import Image, ImageDraw, ImageFont


# Connecting to twitter service
AUTH = tweepy.OAuthHandler(
    os.environ.get("CONSUMER_KEY"), os.environ.get("CONSUMER_SECRET")
)
AUTH.set_access_token(
    os.environ.get("ACCESS_TOKEN"), os.environ.get("ACCESS_TOKEN_SECRET")
)

api = tweepy.API(AUTH,parser=tweepy.parsers.JSONParser())
upload_api =  tweepy.API(AUTH)

consolas_font = ImageFont.truetype("fonts/Consolas.ttf", 48)


dotenv_path = join(dirname(__file__), ".env")
load_dotenv(dotenv_path)

app = App(FastAPI())



# Helpers
def text_wrap(text, font, max_width):
    
    lines = []
    # If the width of the text is smaller than image width
    # we don't need to split it, just add it to the lines array
    # and return
    if font.getsize(text)[0] <= max_width:
        lines.append(text) 
    else:
        # split the line by spaces to get words
        words = text.split(' ')  
        i = 0
        # append every word to a line while its width is shorter than image width
        while i < len(words):
            line = ''         
            while i < len(words) and font.getsize(line + words[i])[0] <= max_width:                
                line = line + words[i] + " "
                i += 1
            if not line:
                line = words[i]
                i += 1
            # when the line gets longer than the max width do not append the word, 
            # add the line to the lines array
            lines.append(line)    
    return lines


@app.get("/")
def http():
    return "Hello DEV Community"


@app.lib.run()
@app.lib.cron()
def driver(e=None):
    data = api.followers()
    followers = data['users'][:3]
    shutil.copy('base.png', '/tmp/')
    img = Image.open('/tmp/base.png')
    image_editable = ImageDraw.Draw(img)
    initial_x = 42
    initial_y = 64
    image_size = img.size 
    for follower in followers:
        screen_name =follower["screen_name"]
        description = follower["description"]
        message = f"{screen_name} : {description}"
        lines = text_wrap(message, consolas_font, image_size[0] * 0.48)
        for val in lines:
            image_editable.text(
            (initial_x, initial_y), val, font=consolas_font, fill=(255, 255, 255)
        )
            initial_y = initial_y + 48
        initial_y = initial_y + 48
    
    edited_temp = tempfile.NamedTemporaryFile(suffix=".png")
    img.save(edited_temp.name)
    upload_api.update_profile_banner(edited_temp.name)
    return followers
```

This essentially runs a Fast API service in the hood, so this can also be used to deploy your APIs and forgot about scaling because it is server less, they will take care of everything :D

#### Code Breakdown 1

```python
# Connecting to twitter service
AUTH = tweepy.OAuthHandler(
    os.environ.get("CONSUMER_KEY"), os.environ.get("CONSUMER_SECRET")
)
AUTH.set_access_token(
    os.environ.get("ACCESS_TOKEN"), os.environ.get("ACCESS_TOKEN_SECRET")
)

api = tweepy.API(AUTH,parser=tweepy.parsers.JSONParser())
upload_api =  tweepy.API(AUTH)

consolas_font = ImageFont.truetype("fonts/Consolas.ttf", 48)


dotenv_path = join(dirname(__file__), ".env")
load_dotenv(dotenv_path)

app = App(FastAPI())
```
Here we are initialising twitter api, fast api, font and loading environment variables. 
 
#### Code Breakdown 2
```python
# Helpers
def text_wrap(text, font, max_width):
    
    lines = []
    # If the width of the text is smaller than image width
    # we don't need to split it, just add it to the lines array
    # and return
    if font.getsize(text)[0] <= max_width:
        lines.append(text) 
    else:
        # split the line by spaces to get words
        words = text.split(' ')  
        i = 0
        # append every word to a line while its width is shorter than image width
        while i < len(words):
            line = ''         
            while i < len(words) and font.getsize(line + words[i])[0] <= max_width:                
                line = line + words[i] + " "
                i += 1
            if not line:
                line = words[i]
                i += 1
            # when the line gets longer than the max width do not append the word, 
            # add the line to the lines array
            lines.append(line)    
    return lines
```

This is a helper function that helps us break lines in the given space. I found it online, trying to find the source to give credit but didn't find it. Will update once I find it.


#### Code breakdown 3
```python
@app.lib.run()
@app.lib.cron()
def driver(e=None):
    data = api.followers()
    followers = data['users'][:3]
    shutil.copy('base.png', '/tmp/')
    img = Image.open('/tmp/base.png')
    image_editable = ImageDraw.Draw(img)
    initial_x = 42
    initial_y = 64
    image_size = img.size 
    for follower in followers:
        screen_name =follower["screen_name"]
        description = follower["description"]
        message = f"{screen_name} : {description}"
        lines = text_wrap(message, consolas_font, image_size[0] * 0.48)
        for val in lines:
            image_editable.text(
            (initial_x, initial_y), val, font=consolas_font, fill=(255, 255, 255)
        )
            initial_y = initial_y + 48
        initial_y = initial_y + 48
    
    edited_temp = tempfile.NamedTemporaryFile(suffix=".png")
    img.save(edited_temp.name)
    upload_api.update_profile_banner(edited_temp.name)
    return followers
```

`@app.lib.run()` and `@app.lib.cron()` are specific to Deta and you can look them up here [Run](https://docs.deta.sh/docs/micros/run/) [Cron](https://docs.deta.sh/docs/micros/cron/)

TL;DR version of cron and run are 
Run - is to run the particular part of the application
Cron - is to run the part for every given interval of time, for example, run the function once every 2 minutes.


`data = api.followers()` 
returns a JSON object of your followers.

`followers = data['users'][:3]` 
returns the last three followers and saves them in the followers list.

```python
shutil.copy('base.png', '/tmp/')
img = Image.open('/tmp/base.png')
image_editable = ImageDraw.Draw(img)
```

This will be a bit tricky to understand so please read carefully.

So you cannot read a file with `write` permissions in the root folder, but `ImageDraw` requires us to open the file in `write` mode because it needs to draw on the image => writing on the image.
But we always have the `/tmp/` folder where we can have write permissions because it is cleared frequently.
So we copy our base image into `/tmp` folder with `shutil` library.
Then open the image with `ImageDraw` 


```python
    initial_x = 42
    initial_y = 64
    image_size = img.size 
    for follower in followers:
        screen_name =follower["screen_name"];
        description = follower["description"];
        message = f"{screen_name} : {description}"
        lines = text_wrap(message, consolas_font, image_size[0] * 0.48)
        for val in lines:
            image_editable.text(
            (initial_x, initial_y), val, font=consolas_font, fill=(255, 255, 255)
        )
            initial_y = initial_y + 48
        initial_y = initial_y + 48
```
Here we are iterating through the 3 latest followers we have and save their username and bio, writing them on the base image we have using the `image_editable.text` command and after every line, we are updating the y-axis of the cursor.

```python
edited_temp = tempfile.NamedTemporaryFile(suffix=".png")
img.save(edited_temp.name)
upload_api.update_profile_banner(edited_temp.name)
return followers
```


### Step 5 Python Code

Making the application run once every two minutes.
Open a new terminal and just type

`deta cron set "2 minutes"`

Yup, it is that simple to run a cron job in Deta.


Woohoo, congratulations on making it to the end of the post.

Feel free to tag me on Twitter once it's done I will try to be your first tester, you can find me [@gillarohith](https://twitter.com/gillarohith)

Also please comment down if you get stuck anywhere or you can reach me on Twitter, my DMs are open.




![Thanks](https://media3.giphy.com/media/KB8C86UMgLDThpt4WT/giphy.gif?cid=ecf05e47z9s61cqcouzqcscd5f037k6yeo7ljgegtgy0z06v&rid=giphy.gif)

Thanks,
Rohith Gilla