Build a Meme Fetcher API using Flask

Build a Meme Fetcher API using Flask

Python is an interpreted language that has found its usage with Web Developers, Data Scientists, Application Developers and more. With a fairly gentle learning curve and easy-to-understand syntax, it would be the best option for you to start off with your Backend Development Journey in a fairly quick manner.

Similar to any other Language, Python has got plenty of Frameworks to develop Backend Interfaces. Some of the most popular ones are Django, Flask, Web2Py and more. They are used to set up things pretty quickly and help you get started with the Business Logic of your Application and interaction with your Database Layer.

In this article, we will be covering on Flask primarily and how you can develop an Application Programming Interface (API) with Flask fairly quick which can fetch you Memes and Quotes.

What is Flask?

image

Flask is a Micro Web-Framework that is used for developing Backend Interface for your Application. It is a lightweight framework which has a small footprint and can be used to set up things pretty quickly, with a simple yet extensible core.

With the rise of complex applications which rely on Architectural Patterns to scale up, services are now being broken down into components that are being handled by a representative API. With the APIs, each service can be deployed independently and offers improved fault isolation.

Flask can be used to develop these REST APIs where simple HTTP methods can be used to communicate between the Client-Side and Server-Side Interfaces. Every API Request consists of four parts: Endpoint, Method, Header and a Body.

The Endpoint defines the URL route which is being requested by a User while the Method defines the type of request. It can be either a GET, POST, DELETE or a PUT/PATCH request. The header is a piece of information that is usually passed for authentication between the Client and the Server while the Body decides the information that we need to pass to the server.

That was a pretty quick way to summarize everything up going at the Server-Side. Let’s see how Flask can be used to rapidly compose an API which can return us a Date-Time of our current request in JSON Format:

from flask import Flask
from flask import jsonify
app = Flask(__name__)

@app.route("/")
def hello():
    return jsonify({'message' : 'Hello!'})
@app.route("/date")
def getDate():
    import datetime
    return jsonify({'message' : datetime.datetime.utcnow().isoformat()})

if __name__ == "__main__":
    app.run(host='0.0.0.0')

We managed to compose two endpoints for our Flask API within 15 Lines of Code. The @app.route decorator maps the HTTP requests to the functions and returns back HTTP responses in the form of JSON. The jsonify wraps the Datetime in a message and passes it as an HTTP response.

Let's Build the App

Now we will be building an API which can fetch Memes for us and we will define multiple endpoints for our Application to provide extensibility and customization options to the User. We will be using Praw which is a Python Reddit API Wrapper to help us fetch Memes directly from Reddit. Let’s get started with creating a Virtual Environment for our purpose.

Create a Folder for all your Code and open the Command Line Tool for your Operating System and push in the following commands:

$ pip3 install virtualenv

Once the virtualenv is installed you can activate a Virtual Environment by pushing a simple command:

$ virtualenv env

This will create a new Python executable in your current directory. Once the setup is finished, you can move forward with activating the Virtual Environment:

$ activate

You can alternatively deactivate your Virtual Environment with a simple command if required. Let’s move ahead to creating a simple Flask API. Push in the following command and type in:

$ pip3 install flask

Open your VS Code/PyCharm and make a File app.py and push in the following Code:

from flask import Flask

app = Flask(__name__)

@app.route('/hello')
def helloIndex():
    return 'Hello World from Python Flask!'

app.run(host='0.0.0.0', port=5000)

Kickstart your Code by opening the Command-Line and pushing in: python3 app.py.

This will kickstart your Code and you will have a Web Server running on Port 5000 with the message 'Hello World from Python Flask!'. Pretty easy isn’t it?

Let’s define the Logic Layer for our API in a separate file. We would need Praw for our purpose so we will install it via Python Package Manager:

$ pip3 install praw

Go to Praw’s Documentation Page and check out how you can carry out an OAuth Authentication before you can use the Wrapper in your API. You can register your Application for the purpose of building a Web App and you will get the following:

  • A Client ID which is a 14-Character String.
  • A Client Secret which is a 27-Character String.
  • Password for the Reddit Account you are using.
  • Username for your Reddit Account.

Let’s make a file named Main.py to define our Logic Functions here. Let’s authenticate this by running a User Run for our Application:

import praw

reddit = praw.Reddit( client_id= 'YOUR_CLIENT_ID',
                      client_secret= 'YOUR_CLIENT_SECRET',
                      password= 'YOUR_PASSWORD',
                      user_agent= 'YOUR_USER_AGENT',
                      username= 'YOUR_USER_NAME' )

Let’s define a Function now which can be used to check if our Image is a JPEG/PNG or not. This will be used to check if the Image we are getting is a Valid Image or not:

def check_image(urllink):
    ext = urllink[-4:]
    if ext == '.jpg' or ext == '.png':
        return True

    return False

Let’s fetch a Meme now. We will define a Function wherein we pass two parameters: sub which means the Subreddit from where we want to Fetch the Memes and count which means the number of Memes we wish to fetch. Let’s define the function now:

def get_meme(sub,count):
    sub_reddit = reddit.subreddit(sub)
    hot_meme = sub_reddit.hot(limit=count)
    result =[]
    for submissions in hot_meme:
        temp = {"Title": submissions.title,
                  "Url": submissions.url,
                  "Upvotes": submissions.ups,
                  "Downvotes": submissions.downs,
                  "Redditurl": submissions.shortlink,
                  "Subreddit": sub
                  }
        result.append(temp)

    return result

We will get the Title, URL, Upvotes, Downvotes, Reddit-URL and the Subreddit and append it to the Result and return it from the Function. We will define a similar function to get the Quotes as well from the Subreddit and the Count specified as well:

def get_text(sub,count):
    sub_reddit = reddit.subreddit(sub)
    hot_meme = sub_reddit.hot(limit=count)
    result=[]
    textP= "No selftext Present"
    for submission in hot_meme:
        if(submission.selftext):
            temp = {"Title": submission.title,
                    "text": submission.selftext,
                    "Upvotes": submission.ups,
                    "Downvotes": submission.downs,
                    "Redditurl": submission.shortlink,
                    "Subreddit": sub
                    }
        else:
            temp = {"Title": submission.title,
                    "text": textP,
                    "Upvotes": submission.ups,
                    "Downvotes": submission.downs,
                    "Redditurl": submission.shortlink,
                    "Subreddit": sub
                    }

        result.append(temp)
    return result

The submission defines the hottest submissions in a particular Subreddit and selftext defines the Markdown Content for a particular Submission. The rest of the code is pretty self-explanatory and you can play with it further before defining the API endpoints further.

Let's build the API

Go back to the app.py file and let’s get started with defining the REST API endpoints for our Application. Remove all the code that we had written earlier in our file since we will be starting afresh. Let’s first define the Subreddits from where we want to fetch our Memes and initialize our Flask Application, by importing necessary libraries and creating a running app:

from flask import Flask,jsonify
import random,logging
from Main import get_meme, check_image, get_text

app = Flask(__name__)
count = 0
randommeme = ['meme','dankmeme','wholesomeme','memes']

Let’s add the Decorators above individual functions now which converts them to a “Route” that defines new URLs for our Backend Service. Let’s create the Endpoint to represent our “Welcome” message on the screen, once the User kickstarts the API:

@app.route('/')
def welcome():
    return "Welcome To API"

Let’s know define an Endpoint which will return as a Meme from a Random Subreddit. We will make use of the get_meme() and check_image() function that we had defined earlier in our Main.py file to fetch Random Memes. We will take in the Title, URL of the Post, Upvotes/Downvotes, URL of the Picture and finally the Subreddit from where the meme has been fetched.

@app.route('/givememe')
def random_meme():              
    sub = random.choice(randommeme)
    r = get_meme(sub,100)
    requsted = random.choice(r)

    while not check_image(requsted["Url"]):
        requsted = random.choice(r)

    return jsonify({
        'Title':requsted["Title"],
        'Url': requsted["Url"],
        'Upvotes': requsted["Upvotes"],
        'Downvotes': requsted["Downvotes"],
        'Redditurl': requsted["Redditurl"],
        'Subreddit': requsted["Subreddit"]
    })

Let’s now define a Route that returns us a Meme from a Subreddit of our choice. We will be placing our code in a Try-Catch Block just to ensure that a 404 Message is returned if in case the Subreddit does not exist. We will return all the Data as a JSON with a Random Meme from the particular Subreddit, we are fetching from.

@app.route('/givememe/<sub>')
def custom_meme(sub):   
    try:
        r = get_meme(sub,100)

    except:
        return jsonify({
            'Status_code': 404,
            'Message': 'Invalid Subreddit'
        })

    requsted = random.choice(r)

    while not check_image(requsted["Url"]):
        count=count+1
        requsted = random.choice(r)
        if count == 100:
            break

    return jsonify({
        'Title':requsted["Title"],
        'Url': requsted["Url"],
        'Upvotes': requsted["Upvotes"],
        'Downvotes': requsted["Downvotes"],
        'Redditurl': requsted["Redditurl"],
        'Subreddit': requsted["Subreddit"]
    })

The Code for this is pretty self-explanatory so let’s move forward towards building more endpoints and adding more functionality to our API with all the Functions that we had defined earlier.

We will now create an Endpoint which fetches us Memes given the number of Counts. Let’s write the code for this now:

@app.route('/givememe/<int:c>')
def multiple(c):
    sub = random.choice(randommeme)

    if c >= 50:
        return jsonify({
            'status_code': 400,
            'message': 'Ensure that the Count is less than 50'
        })

    requested = get_meme(sub, 100)

    random.shuffle(requested)

    memes = []
    for post in requested:
        if check_image(post["Url"]) and len(memes) != c:

            t = {
                'Title': post["Title"],
                'Url': post["Url"],
                'Upvotes': post["Upvotes"],
                'Downvotes': post["Downvotes"],
                'Redditurl': post["Redditurl"],
                'Subreddit': post["Subreddit"]
            }
            memes.append(t)


    return jsonify({
        'memes': memes,
        'count': len(memes)
        })

We are adding a count of 50 here so that if the user asks for more than 50 Memes, we will send an Error Message correspondingly. We will implement another Route which can fetch us a number of Memes from a particular Subreddit. Let’s implement this:

@app.route('/givememe/<sub>/<int:c>')
def multiple_from_sub(sub, c):


    if c >= 50:
        return jsonify({
            'status_code': 400,
            'message': 'Ensure that the Count is less than 50'
        })

    requested = get_meme(sub, 100)

    random.shuffle(requested)

    memes = []
    for post in requested:
        if check_image(post["Url"]) and len(memes) != c:

            t = {
                'Title': post["Title"],
                'Url': post["Url"],
                'Upvotes': post["Upvotes"],
                'Downvotes': post["Downvotes"],
                'Redditurl': post["Redditurl"],
                'Subreddit': post["Subreddit"]
            }
            memes.append(t)


    return jsonify({
        'memes': memes,
        'count': len(memes)
        })

Let’s see what Endpoints we have implemented till now:

EndpointDescription
/givememeReturns a Random Meme from any Subreddit that we had defined.
/givememe/<sub>Returns a Meme from the Subreddit that the User has specified.
/givememe/<int: c>Returns a Number of Memes from any Subreddit that we had defined.
/givememe/<sub>Returns a Number of Memes from a Subreddit that the User has specified.

We will now define similar Routes to fetch the Text as Quotes as well from the Subreddits. The Code is pretty self-explanatory and you can skim through them as we implement further routes here.

Let’s implement a Route to fetch a Quote from a Subreddit specified by the User:

@app.route('/givetext/<sub>')
def text_meme(sub):         
    r = get_text(sub,100)
    requsted = random.choice(r)

    return jsonify({
        'Title': requsted["Title"],
        'Selftext': requsted["text"],
        'Upvotes': requsted["Upvotes"],
        'Downvotes': requsted["Downvotes"],
        'Redditurl': requsted["Redditurl"],
        'Subreddit': requsted["Subreddit"]
    })

Let’s implement a final functional Route now to return a number of Quotes from a particular Subreddit:

@app.route('/givetext/<sub>/<int:count>')
def text_count_meme(sub,count):             
    if count >= 50:
        return jsonify({
            'status_code': 400,
            'message': 'Please ensure the count is less than 50'
        })

    requested = get_text(sub, 100)
    random.shuffle(requested)


    textmeme = []
    for post in requested:
        if len(textmeme) != count:
            t = {
                'Title': post["Title"],
                'Selftext': post["text"],
                'Upvotes': post["Upvotes"],
                'Downvotes': post["Downvotes"],
                'Redditurl': post["Redditurl"],
                'Subreddit': post["Subreddit"]
            }
            textmeme.append(t)

    return jsonify({
        'sub': textmeme,
        'count': len(textmeme)
    })

We will define a 404 Route as well so that the User can fall back on an Error Message if he has directed to an unspecified URL.

@app.errorhandler(404)
@app.route('/<lol>')
def not_found(lol):
    return "<h1git >Are You Lost?<h1>"

Now that we have built our API, let’s add this final block of Code which kickstarts our Application in Debug Mode:

if __name__ == '__main__':
    app.run(debug = true)

Testing the API

Now that we have built all our API endpoints and defined the Logic in them, we will move forward with testing our API at all suitable endpoints. Here, I will make use of Postman to test our API endpoints. If you have not installed the Postman, you can make use of your Browser to test our all the API endpoints.

Open your Command Line Tool and push in the command:

$ python3 app.py

This will kickstart the Server at Port 5000. Open Postman and create a new Request. Let’s push in the API endpoint: http://localhost:5000/givememe to create a Request:

MemeAPITest1.PNG

We can further experiment this with the other endpoints as well to further explore how our API works and functions. Let’s check out another API endpoint to fetch 15 Random Memes by passing in: http://localhost:5000/givememe/15 to create a Request:

MemeAPITest2.PNG

You can optionally test these Endpoints on your Browser as well and they would send you proper JSON Response. You are now free to experiment with these endpoints and customize them according to your needs and purposes.

Conclusion

In this article, we have covered up the intricacies of API Development and how you can compose a Microservices API with Flask and Python fairly quick to fetch Memes and Quotes from particular Subreddits.

We have also learnt how we can define our Endpoints and test them using Postman. We have a fully functional API at ready, and we can now work upon integrating a Front-End for the Application which can bring us a Meme-Sharing Network at hand as well.

This article was originally published on the Medium Publication of Analytics Vidhya. Check it out here.