© Kunal Relan 2019
K. RelanBuilding REST APIs with Flaskhttps://doi.org/10.1007/978-1-4842-5022-8_1

1. Beginning with Flask

Kunal Relan1 
(1)
New Delhi, Delhi, India
 

Flask is a BSD licensed, Python microframework based on Werkzeug and Jinja2. Being a microframework doesn’t make it any less functional; Flask is a very simple yet highly extensible framework. This gives developers the power to choose the configuration they want, thereby making writing applications or plugins easy. Flask was originally created by Pocoo, a team of open source developers in 2010, and it is now developed and maintained by The Pallets Project who power all the components behind Flask. Flask is supported by an active and helpful developer community including an active IRC channel and a mailing list.

Introduction to Flask

Flask has two major components, Werkzeug and Jinja2. While Werkzeug is responsible for providing routing, debugging, and Web Server Gateway Interface (WSGI), Flask leverages Jinja2 as template engine. Natively, Flask doesn’t support database access, user authentication, or any other high-level utility, but it does provide support for extensions integration to add all such functionalities, making Flask a micro- yet production-ready framework for developing web applications and services. A simple Flask application can fit into a single Python file or it can be modularized to create a production-ready application. The idea behind Flask is to build a good foundation for all applications leaving everything else on extensions.

Flask community is quite big and active with hundreds of open source extensions. The Flask core team continuously reviews extensions and ensures approved extensions are compatible with the future releases. Flask being a microframework provides flexibility to the developers to choose the design decisions appropriate to their project. It maintains a registry of extensions which is regularly updated and continuously maintained.

Starting Flask

Flask, just like all other Python libraries, is installable from the Python Package Index (PPI) and is really easy to setup and start developing with, and it only takes a few minutes to getting started with Flask. To be able to follow this book, you should be familiar with Python, command line (or at least PIP), and MySQL.

As promised, Flask is really easy to start with, and just five lines of code lets you get started with a minimal Flask application.
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
    return 'Hello, From Flask!'
if __name__== '__main__':
      app.run()
Listing 1-1

Basic Flask Application

The preceding code imports the Flask library, initiates the application by creating an instance of the Flask class, declares the route, and then defines the function to execute when the route is called. This code is enough to start your first Flask application.

The following code launches a very simple built-in server, which is good enough for testing but probably not when you want to go in production, but we will cover that in the later chapters.

When this application starts, the index route upon request shall return “Hello From Flask!” as shown in Figure 1-1.
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig1_HTML.jpg
Figure 1-1

Flask minimal application

Flask Components Covered in This Book

Now that you have been introduced to Flask, we will discuss the components that we’ll cover in Flask REST API development in this book.

This book will serve as a practical guide to REST API development using Flask, and we’ll be using MySQL as the backend database. As already discussed, Flask doesn’t come with native database access support, and to bridge that gap, we’ll use a Flask extension called Flask-SQLAlchemy which adds support for SQLAlchemy in Flask. SQLAlchemy is essentially a Python SQL toolkit and Object Relational Mapper which provides the developers the full power and flexibility of SQL.

SQLAlchemy provides full support for enterprise-level design patterns and is designed for high-performing database access while maintaining efficiency and ease of use. We’ll build a user authentication module, CRUD (Create, Read, Update, and Delete) REST APIs for object creation, retrieval, manipulation, and deletion. We’ll also integrate a documentation utility called Swagger for creating API documentation, write unit and integration tests, learn application debugging, and, finally, check out different methods of deploying and monitoring our REST APIs on cloud platforms for production use.

For unit tests, we’ll use pytest which is a full-featured Python testing tool—pytest is easy to write tests with and yet is scalable to support complex use cases. We’ll also use Postman which is a complete REST API Platform—Postman provides integration tools for every stage of the API lifecycle, making API development easier and more reliable.

API deployment and monitoring are critical parts of REST API development; development paradigm changes drastically when it comes to scaling the APIs for production use cases, and for the sake of this book, we’ll deploy our REST APIs using uWSGI and Nginx on a cloud Ubuntu server. We’ll also deploy our REST APIs on Heroku which is a cloud platform that facilitates Flask app deployment and scaling out of the box.

Last but not least, we’ll discuss debugging common Flask errors and warnings and debugging Nginx requests and check out Flask application monitoring ensuring least amount on the downtime for production use.

Introduction to RESTful Services

Representational State Transfer (REST) is a software architectural style for web services that provides a standard for data communication between different kinds of systems. Web services are open standard web applications that interact with other applications with a motive of exchanging data making it an essential part of client server architecture in modern web and mobile applications. In simple terms, REST is a standard for exchanging data over the Web for the sake of interoperability between computer systems. Web services which conform to the REST architectural style are called RESTful web services which allow requesting systems to access and manipulate the data using a uniform and predefined set of stateless operations.

Since its inception in 2000 by Roy Feilding, RESTful architecture has grown a lot and has been implemented in millions of systems since then. REST has now become one of the most important technologies for web-based applications and is likely to grow even more with its integration in mobile and IoT-based applications as well. Every major development language has frameworks for building REST web services. REST principles are what makes it popular and heavily used. REST is stateless, making it straightforward for any kind of system to use and also making it possible for each request to be served by a different system.

REST enables us to distinguish between the client and the server, letting us implement the client and the server independently. The most important feature of REST is its statelessness, which simply means that neither the client nor the server has to know the state of each other to be able to communicate. In this way, both the client and the server can understand any message received without seeing the previous message. Since we are talking about RESTful web services, let’s take a dive into web services and compare other web service standards.

Web services in a simple definition is a service offered by one electronic device to another, enabling the communication via the World Wide Web. In practice, web services provide resource-oriented, web-based interface to a database server and so on utilized by another web client. Web services provide a platform for different kinds of systems to communicate to each other, using a solution for programs to be able to communicate with each other in a language they understand (Figure 1-2).
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig2_HTML.jpg
Figure 1-2

REST architecture diagram

SOAP (Simple Object Access Protocol) is another web service communication protocol which has been overtaken by REST in the recent years. REST services now dominate the industry representing more than 70% of public APIs according to Stormpath. They operate by exposing consistent interface to access named resources. SOAP, however, exposes components of application logic as services rather than data. SOAP is now a legacy protocol originally created by Microsoft and has a lot of other constraints when compared to REST. SOAP only exchanges data over XML, and REST provides the ability to exchange data over a variety of data formats. RESTful services are comparatively faster and less resource intensive. However, SOAP still has its own use cases in which it’s a preferred protocol over REST.

SOAP is preferred when robust security is essential as it provides support for Web Services Security (WS-Security), which is a specification defining how security measures are implemented in web services to protect them from external attacks. Another advantage of SOAP over REST is its built-in retry logic to compensate for failed requests unlike REST in which the client has to handle failed requests by retrying. SOAP is highly extensible with other technologies and protocols like WS-Security, WS-addressing, WS-coordination, and so on which provides it an edge over other web service protocols.

Now, when we have briefly discussed web services—REST and SOAP—let’s discuss features of REST protocol. In general, REST services are defined and implemented using the following features:
  1. 1.

    Uniform interface

     
  2. 2.

    Representations

     
  3. 3.

    Messages

     
  4. 4.

    Links between resources

     
  5. 5.

    Caching

     
  6. 6.

    Stateless

     

Uniform Interface

RESTful services should have a uniform interface to access resources, and as the name suggests, APIs’ interface for the system should be uniform across the system. A logical URI system with uniform ways to fetch and manipulate data is what makes REST easy to work with. HTTP/1.1 provides a set of methods to work on noun-based resources; the methods are generally called verbs for this purpose.

In REST architecture, there is a concept of safe and idempotent methods. Safe methods are the ones that do not modify resources like a GET or a HEAD method. An idempotent method is a method which produces the same result no matter how many times it is executed. Table 1-1 provides a list of commonly used HTTP verbs in RESTful services.
Table 1-1

Commonly used HTTP verbs useful in RESTful services

Verb

CRUD

Operation

Safe

Idempotent

GET

Read

Fetch a single or multiple resource

Yes

Yes

POST

Created

Insert a new resource

No

No

PUT

Update/Create

Insert a new resource or update existing

No

Yes

DELETE

Delete

Delete a single or multiple resource

No

Yes

OPTIONS

READ

List allowed operations on a resource

Yes

Yes

HEAD

READ

Return only response headers and no body

Yes

Yes

PATCH

Update/Modify

Only update the provided changes to the resource

No

No

Representations

RESTful services focus on resources and providing access to the resources. A resource can be easily thought of as an object in OOP. The first thing to do while designing RESTful services is identifying different resources and determining the relation between them. A representation is a machine-readable explanation defining the current state of a resource.

Once the resources are identified, representations are the next course of action. REST provides us the ability to use any format for representing the resources in the system. Unlike SOAP which restricts us to use XML to represent the data, we can either use JSON or XML. Usually, JSON is the preferred method for representing the resources to be called by mobile or web clients, but XML can be used to represent more complex resources.

Here is a small example of representing resources in both formats.
<?xml version="1.0" encoding="UTF-8"?>
<Book>
  <ID> 1 </ID>
  <Name> Building REST APIs with Flask </Name>
  <Author> Kunal Relan </Author>
  <Publisher > Apress </ Publisher >
</Book>
Listing 1-2

XML Representation of a Book Resource

{
      "ID": "1",
      "Name": "Building REST APIs wiith Flask",
      "Author": "Kunal Relan",
      "Publisher": "Apress"
}
In REST Systems, you can use either of the methods or both the methods depending on the requesting client to represent the data.
Listing 1-3

JSON Representation of a Book resource

Messages

In REST architecture, which essentially established client–server style way of data communication, messages are an important key. The client and the server talk to each other via messages in which the client sends a message to the server which is often called as a request and the server sends a response. Apart from the actual data exchanged between the client and the server in the form of request and response body, there is some metadata exchanged by the client and the server both in the form of request and response headers. HTTP 1.1 defines request and response headers formats in the following way in order to achieve a uniform way of data communication across different kinds of systems (Figure 1-3).
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig3_HTML.jpg
Figure 1-3

HTTP sample request

In Figure 1-4, GET is the request method, “/comments” is the path in the server, “postId=1” is a request parameter, “HTTP/1.1” is the protocol version that the client is requesting, “jsonplaceholder.typicode.com” is the server host, and content type is a part of the request headers. All of these combined is what makes a HTTP request that the server understands.

In return, the HTTP server sends the response for the requested resources.
[
  {
    "postId": 1,
    "id": 1,
    "name": "id labore ex et quam laborum",
    "email": "[email protected]",
    "body": "laudantium enim quasi est quidem magnam voluptate ipsam eos tempora quo necessitatibus dolor quam autem quasi reiciendis et nam sapiente accusantium"
  },
  {
    "postId": 1,
    "id": 2,
    "name": "quo vero reiciendis velit similique earum",
    "email": "[email protected]",
    "body": "est natus enim nihil est dolore omnis voluptatem numquam et omnis occaecati quod ullam at voluptatem error expedita pariatur nihil sint nostrum voluptatem reiciendis et"
  },
  {
    "postId": 1,
    "id": 3,
    "name": "odio adipisci rerum aut animi",
    "email": "[email protected]",
    "body": "quia molestiae reprehenderit quasi aspernatur aut expedita occaecati aliquam eveniet laudantium omnis quibusdam delectus saepe quia accusamus maiores nam est cum et ducimus et vero voluptates excepturi deleniti ratione"
  },
  {
    "postId": 1,
    "id": 4,
    "name": "alias odio sit",
    "email": "[email protected]",
    "body": "non et atque occaecati deserunt quas accusantium unde odit nobis qui voluptatem quia voluptas consequuntur itaque dolor et qui rerum deleniti ut occaecati"
  },
  {
    "postId": 1,
    "id": 5,
    "name": "vero eaque aliquid doloribus et culpa",
    "email": "[email protected]",
    "body": "harum non quasi et ratione tempore iure ex voluptates in ratione harum architecto fugit inventore cupiditate voluptates magni quo et"
  }]
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig4_HTML.jpg
Figure 1-4

HTTP sample response

In the preceding figure, “HTTP/2” is the response HTTP version and “200” is the response code. The part below that till “cf-ray” is the response headers, and the array of post comments below “cf-ray” is the response body of the request.

Links Between Resources

A resource is the fundamental concept in the world of REST architecture. A resource is an object with a type, associated data, and relationships to other resources alongside a set of methods that can be executed on it. The resource in a REST API can contain link to other resources which should drive the process flow. Such as in the case of a HTML web page in which the links in the homepage drive the user flow, resources in REST API should be able to drive the flow without the user knowing the process map.
{
      "ID": "1",
      "Name": "Building REST APIs wiith Flask",
      "Author": "Kunal Relan",
      "Publisher": "Apress",
       "URI" : "https://apress.com/us/book/123456789"
}
Listing 1-4

A Book with Link to Buy

Caching

Caching is a technique that stores a copy of a given resource and serves it back when requested, saving extra DB calls and processing time. It can be done at different levels like the client, the server, or a middleware proxy server. Caching is an important tool for increasing the API performance and scaling the application; however, if not managed properly, it results in the client being served old results. Caching in REST APIs is controlled using HTTP headers. Cache headers have been an essential part of HTTP header specifications and have been an important part of scaling web services with efficiency. In REST specification, when a safe method is used on a resource URL, usually the reverse proxy caches the results to use the cached data when the same resource is requested the next time.

Stateless

Each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is therefore kept entirely on the client

—Roy Fielding

Statelessness here means that every HTTP response is a complete entity in itself and enough to serve the purpose of providing information to be executed without any need of another HTTP request. The point of statelessness is to defeat the purpose of accord with the server allowing intended flexibility in the infrastructure. To facilitate the same, REST servers provide enough information in the HTTP response that the client may need. Statelessness is an essential part of being able to scale the infrastructure enabling us to deploy multiple servers to serve millions of concurrent users given the fact that there is no server session state dependency. It also enables the caching feature of REST infrastructure as it lets the caching server to decide whether to cache the request or not, just by looking at the particular request irrespective of any previous requests.

Planning REST API

Here is a list of things we need to check while planning to create REST APIs:
  1. 1.

    Understanding the use case. It is really important to know why you are building the API and what services will the API provide.

     
  2. 2.

    Listing down API features to understand what all actions your APIs are going to do. This also includes listing down actions and grouping them together to tackle redundant endpoints.

     
  3. 3.

    Identify different platforms that’ll use the API and provide support accordingly.

     
  4. 4.

    Plan long term on supporting growth and scaling the infrastructure.

     
  5. 5.

    Plan API versioning strategy ensuring continuous support is maintained over different versions of the APIs.

     
  6. 6.

    Plan API access strategy, that is, authentication, ACL, and throttling.

     
  7. 7.

    Plan API documentation and testing.

     
  8. 8.

    Understand how to use hypermedia with your APIs.

     

So, these are the eight important things to ensure while planning your API and are really crucial for developing a stable, production-focused API system.

API Design

Now let’s look into API design. Here we’ll cover the standards of designing REST APIs keeping in mind the list of things we just talked about.

Long-Term Implementation

Long-term implementation helps you analyze the flaws in design before actual implementation. This helps the developers to choose the right kind of platforms and tools to build upon making sure the same system can be scaled for more users later.

Spec-Driven Development

Spec-driven development enforces API design using definition and not just the code, which ensures that the changes are made to the codebase while the API design is intact. It is good practice to use a tool like API Designer to understand the API design before development which also lets you foresee the flaws. Tools like swagger or RAML let you keep the API design standardized and enable you to port the API to different platforms if needed.

Prototyping

Once the API specs are put in place, prototyping helps you visualize the API before actual development by letting the developers create MOCK API to help them understand every potential aspect of the API.

Authentication and Authorization

Authentication involves the verification process to know who the person is, but it just doesn’t involve giving access to all the resources yet, and that’s where authorization comes in, which involves authorizing an authenticated person to keep a check on resources allowed to access using an Access Control List (ACL).

We have different ways of authenticating and authorizing users like basic authentication, HMAC, and OAuth. OAuth 2.0 is however a preferred method for the same and is a standard protocol used by enterprises as well as small companies for authentication and authorization in their REST APIs.

So, these are the key features of the REST infrastructure, and we’ll discuss more about how REST works and enables better communication in later chapters.

Now, we’ll start with setting up our development environment and understand some key factors of developing applications with Python.

Setting Up Development Environment

In this part, we’ll discuss setting up Python development environment for a Flask application. We’ll use virtual environments for a separate isolated environment for our dependencies. We’ll use PIP for installing and managing our dependencies and a couple of other helpful utilities in the process of setting up our development environment. For the sake of this book, we’ll be doing everything on macOS Mojave and Python 2.7, but you can feel free to use any operating system as per your convenience. So if you don’t have the right version of Python installed in your operating system, you can go ahead with installing Python on your choice of operating system using this link: www.python.org/downloads/ (Figure 1-5).
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig5_HTML.jpg
Figure 1-5

Python download

Working with PIP

PIP is a PyPi recommended tool for project dependency management. PIP comes preinstalled with Python if you are using Python downloaded from www.python.org .

However, if you don’t have PIP installed in your system, follow the guide here to install PIP.

In order to install PIP, download get-pip.py by using the following command in your terminal (or command line in Windows).
$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
Once you have the get-pip.py file, install and run the next command:
$ python get-pip.py

The previous command will install PIP, setuptools (required for installing source distributions), and wheel.

If you already have PIP, you can upgrade to the latest version of pip using the following command:
$ pip install -U pip
To test your installation, you should run the following command (Figure 1-6) in your terminal (or command line in Windows):
$ python -V
$ pip -V
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig6_HTML.jpg
Figure 1-6

Checking Python and PIP installation

Choosing the IDE

Before we start writing the code, we’ll need something to write with. Throughout this book, we’ll use Visual Studio Code which is an open source and free IDE available on all major operating systems. Visual Studio Code is available to download from www.code.visualstudio.com , and it provides good support for developing Python applications with plenty of handy plugins to facilitate development. You can choose to use your own preferred text editor or IDE to follow this book (Figure 1-7).
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig7_HTML.jpg
Figure 1-7

Visual Studio Code

Once we have the IDE setup, we can move to installing and setting up the virtual environment.

Understanding Python Virtual Environments

Python, just like other modern programming languages, provides a huge amount of third-party libraries and SDKs. Different applications might need various specific versions of third-party modules, and it won’t be possible for one Python installation to meet such requirements of every application. So, in the world of Python, the solution for this problem is virtual environment, which creates a separate self-contained directory tree containing a Python installation of the required version alongside the required packages.

At its core, the main purpose of a virtual environment is to create an isolated environment to contain an installation of Python and required packages for the application. There is no limit to the number of virtual environments you can create, and it’s super easy to create them.

Using Virtual Environments

In Python 2.7 we need a module called virtualenv which is installed using PIP to get started with Python virtual environments.

Note

In Python 3 the venv module comes preshipped as a part of the standard library.

To install virtualenv, type the following command in your terminal (or command line in case of Windows).
$ pip install virtualenv

Once we have the virtualenv module installed in our system, next we’ll create a new directory and create a virtual environment in it.

Now, type the following command to create a new directory and open it in your terminal.
$ mkdir pyenv && cd pyenv
The preceding command will create a directory and open it in your terminal, and then we’ll use the virtualenv module to create a new virtual environment inside the directory.
$ virtualenv venv

The previous command will use the virtualenv module and create a virtual environment called venv. You can name your virtual environment anything, but for this book, we’ll just use venv for the sake of uniformity.

Once this command stops executing, you’ll see a directory called venv. This directory will now hold your virtual environment.

The directory structure of the venv folder should be similar to the one in Figure 1-8.
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig8_HTML.jpg
Figure 1-8

Virtual environment directory structure

Here is what each folder in the structure contains:
  1. 1.

    bin: Files to interact with the virtual environment.

     
  2. 2.

    include: C headers to compile the Python packages.

     
  3. 3.

    lib: This folder contains a copy of the Python version and all the other third-party modules.

     
Next, there are copies of, or symlinks to, different Python tools to ensure all the Python code and commands are executed within the current environment. The important part here is the activation scripts in bin folder, which sets the shell to use the virtual environment’s Python and site packages. In order to do so, you need to activate the virtual environment by typing the following command in your terminal.
$ source venv/bin/activate
Once this command is executed, your shell prompt will be prefixed with the name of the virtual environment, just as in Figure 1-9.
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig9_HTML.jpg
Figure 1-9

Activating virtual environment

Now, let’s install Flask in our virtual environment using the following command:
$ pip install flask
The preceding command should install Flask in our virtual environment. We’ll use the same code we did in our sample Flask application.
$ nano app.py
And type the following code in the nano text editor:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
    return 'Hello, From Flask!'
Now,  try running your app.py using python app.py command.
$ FLASK_APP=app.py flask run
With the preceding command, you should be able to run the simple Flask application, and you should see similar output in your terminal (Figure 1-10).
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig10_HTML.jpg
Figure 1-10

Running Flask application in virtual environment

Now, to deactivate the virtual environment, you need to execute the following command:
$ deactivate
After this command executes, (venv) prefix from the shell will go away, and if you try running the application again, it will throw an error (Figure 1-11).
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig11_HTML.jpg
Figure 1-11

Running Flask application without virtual environment

So now you understand the concept of virtual environments, we can dig a little deeper and understand what’s happening inside the virtual environment.

Understanding how virtual environments work can really help you debug the application and understand the execution environment. To start with, let’s check out the Python executable with virtual environment activated and deactivated, in order to understand the basic difference.

Let’s execute the following command with virtual environment activated (Figure 1-12):
$ which python
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig12_HTML.jpg
Figure 1-12

Checking Python executable with virtual environment

As you see in the following figure, the shell is using virtual environment’s Python executable, and if you deactivate the environment and re-run the Python command, you’ll notice the shell is now using the system’s Python (Figure 1-13).
../images/479840_1_En_1_Chapter/479840_1_En_1_Fig13_HTML.jpg
Figure 1-13

Checking Python executable without virtual environment

So once you activate the virtual environment, the $path environment variable is modified to point at our virtual environment, and thus the Python in our virtual environment is used rather than the system one. However, an important thing to notice here is that it is basically a copy of, or a symlink to, the system’s Python executable.

Setting Up Flask

We have already installed Flask in the earlier module, but let’s start over and setup the Flask microframework.

Installing Flask

With virtual environment activated, execute the following command to install the latest version of Flask.
$pip install flask

The preceding command will install Flask in your virtual environment.

However, if you wish to work with the latest Flask before release, install/update the Flask module using the master branch of its repository by executing the following command:
$pip install -U https://github.com/pallets/flask/archive/master.tar.gz
When you install Flask, the following distributions are installed with the main framework:
  1. 1.

    Werkzeug ( http://werkzeug.pocoo.org/ ): Werkzeug implements WSGI, the standard Python interface between the application and the server.

     
  2. 2.

    Jinja ( http://jinja.pocoo.org/ ): Jinja is the templating engine in Flask which renders the pages for the application.

     
  3. 3.

    MarkupSafe ( https://pypi.org/project/MarkupSafe/ ): Markupsafe comes preshipped with Jinja, which helps escape an untrusted user input to escalate injection attacks.

     
  4. 4.

    ItsDangerous( https://pythonhosted.org/itsdangerous/ ): ItsDangerous is responsible for securely signing data to ensure data integrity and is used to protect Flask session cookies.

     
  5. 5.

    Click ( http://click.pocoo.org/ ): Click is a framework to write CLI applications. It provides the “Flask” CLI command.

     

Conclusion

Once you have Flask installed in your virtual environment, you are ready to go to the next step of the development phase. Before we do that, we’ll discuss about MySQL and Flask-SQLAlchemy which is the ORM that we’ll use in our Flask application. Database is an essential part of a REST application, and in the next chapter, we’ll discuss the MySQL database and Flask-SQLAchemy ORM and also learn how to connect our Flask application with Flask-SQLAlchemy.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.174.195