Amazing Python Package Showcase (6) – FastAPI: A Modern, Fast (High-Performance) Web Framework for Building APIs Based on Standard Python Type Hints
FastAPI has emerged as a modern, high-performance web framework, gaining popularity for its ease of use and speed.
Table of Contents
What is FastAPI?
FastAPI is a modern, fast (high-performance) web framework for building APIs with Python, leveraging the power of standard Python type hints. It is designed to be easy to use and learn, while also offering high performance, comparable to frameworks like Node.js and Go. Created by Sebastián Ramírez, FastAPI provides an asynchronous approach to handling requests, making it incredibly efficient.
Key Features
High Performance
- FastAPI is one of the fastest Python frameworks available, largely due to its support for asynchronous programming using
async
andawait
. - Performance benchmarks show that it is comparable to frameworks like Node.js and Go, ensuring your applications are ready for high traffic and low latency.
Easy to Use
- The framework is designed to be intuitive and easy to learn, with a strong emphasis on developer productivity.
- It uses standard Python type hints, allowing for more readable code and powerful autocompletion features in IDEs.
Automatic Interactive API Documentation
- FastAPI automatically generates interactive API documentation using Swagger UI and ReDoc.
- This feature is immensely helpful for both developers and clients to understand and test the API endpoints effortlessly.
Validation and Serialization
- With FastAPI, you get automatic validation and serialization using Python type hints and Pydantic models.
- This ensures that the data passed to and from your API endpoints adheres to the defined schemas, reducing errors and improving reliability.
Dependency Injection
- FastAPI includes a powerful dependency injection system, which makes it easy to manage dependencies and improve code modularity and testability.
Asynchronous Support
- FastAPI natively supports asynchronous request handling, making it suitable for high-performance applications that require concurrency.
Security and Authentication
- FastAPI provides tools to easily implement security features, including OAuth2, JWT (JSON Web Tokens), and others.
Background Tasks
- Run tasks in the background without blocking the main application flow, useful for long-running operations.
WebSockets
- Create real-time communication channels using WebSockets for applications that require real-time updates.
GraphQL Integration
- FastAPI can be integrated with GraphQL for more flexible and efficient querying of your API.
Advantages
- Speed: Thanks to its asynchronous capabilities, FastAPI is extremely fast, which is crucial for high-performance applications.
- Ease of Use: The use of Python type hints and Pydantic makes code more readable and reduces bugs.
- Automatic Documentation: The automatically generated interactive documentation saves time and improves collaboration with API users.
- Extensibility: With its dependency injection system, FastAPI allows for easy and clean extensibility.
- Comprehensive: FastAPI supports a wide range of features, from WebSockets to GraphQL, making it suitable for various use cases.
Disadvantages
- Learning Curve: While designed to be user-friendly, developers new to asynchronous programming might face a learning curve.
- Ecosystem: Although growing rapidly, the ecosystem around FastAPI is still smaller compared to more established frameworks like Flask or Django.
- Complexity: For very simple applications, FastAPI might be overkill compared to lighter frameworks like Flask.
Alternatives
Flask
- Advantages: Simple, easy to learn, large community and ecosystem.
- Disadvantages: Less performant for asynchronous operations, lacks some built-in features like automatic documentation.
Django
- Advantages: Full-fledged web framework, includes ORM, authentication, and other built-in features.
- Disadvantages: Heavier and more complex, not as fast for API-specific tasks.
Tornado
- Advantages: Designed for asynchronous networking, high performance.
- Disadvantages: More complex API, less intuitive for beginners.
Sanic
- Advantages: Asynchronous, high performance, similar to FastAPI.
- Disadvantages: Smaller community and ecosystem compared to FastAPI.
Starlette
- Advantages: Lightweight, fast, and the foundation upon which FastAPI is built.
- Disadvantages: Less feature-rich out of the box compared to FastAPI.
Getting Started with FastAPI
Installation
To get started with Mimesis, create an environment first:
dynotes@P2021:~/projects/python/fastapi$ virtualenv fastapi
created virtual environment CPython3.10.12.final.0-64 in 934ms
creator CPython3Posix(dest=~/projects/python/fastapi/fastapi, clear=False, no_vcs_ignore=False, global=False)
seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/dynotes/.local/share/virtualenv)
added seed packages: pip==24.1.2, setuptools==70.2.0, wheel==0.43.0
activators BashActivator,CShellActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator
dynotes@WIN-P2021:~/projects/python/fastapi$ source ./fastapi/bin/activate
(fastapi) dynotes@P2021:~/projects/python/fastapi$
Then installfastapi
viapip
:
(fastapi) dynotes@P2021:~/projects/python/fastapi$ pip install fastapi
Collecting fastapi
Downloading fastapi-0.111.1-py3-none-any.whl.metadata (26 kB)
Collecting starlette<0.38.0,>=0.37.2 (from fastapi)
Using cached starlette-0.37.2-py3-none-any.whl.metadata (5.9 kB)
Collecting pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4 (from fastapi)
Using cached pydantic-2.8.2-py3-none-any.whl.metadata (125 kB)
Collecting typing-extensions>=4.8.0 (from fastapi)
Using cached typing_extensions-4.12.2-py3-none-any.whl.metadata (3.0 kB)
Collecting fastapi-cli>=0.0.2 (from fastapi)
Using cached fastapi_cli-0.0.4-py3-none-any.whl.metadata (7.0 kB)
Collecting httpx>=0.23.0 (from fastapi)
Using cached httpx-0.27.0-py3-none-any.whl.metadata (7.2 kB)
Collecting jinja2>=2.11.2 (from fastapi)
Using cached jinja2-3.1.4-py3-none-any.whl.metadata (2.6 kB)
Collecting python-multipart>=0.0.7 (from fastapi)
Using cached python_multipart-0.0.9-py3-none-any.whl.metadata (2.5 kB)
Collecting email_validator>=2.0.0 (from fastapi)
Using cached email_validator-2.2.0-py3-none-any.whl.metadata (25 kB)
Collecting uvicorn>=0.12.0 (from uvicorn[standard]>=0.12.0->fastapi)
Downloading uvicorn-0.30.3-py3-none-any.whl.metadata (6.5 kB)
Collecting dnspython>=2.0.0 (from email_validator>=2.0.0->fastapi)
Using cached dnspython-2.6.1-py3-none-any.whl.metadata (5.8 kB)
Collecting idna>=2.0.0 (from email_validator>=2.0.0->fastapi)
Using cached idna-3.7-py3-none-any.whl.metadata (9.9 kB)
Collecting typer>=0.12.3 (from fastapi-cli>=0.0.2->fastapi)
Using cached typer-0.12.3-py3-none-any.whl.metadata (15 kB)
Collecting anyio (from httpx>=0.23.0->fastapi)
Using cached anyio-4.4.0-py3-none-any.whl.metadata (4.6 kB)
Collecting certifi (from httpx>=0.23.0->fastapi)
Using cached certifi-2024.7.4-py3-none-any.whl.metadata (2.2 kB)
Collecting httpcore==1.* (from httpx>=0.23.0->fastapi)
Using cached httpcore-1.0.5-py3-none-any.whl.metadata (20 kB)
Collecting sniffio (from httpx>=0.23.0->fastapi)
Using cached sniffio-1.3.1-py3-none-any.whl.metadata (3.9 kB)
Collecting h11<0.15,>=0.13 (from httpcore==1.*->httpx>=0.23.0->fastapi)
Using cached h11-0.14.0-py3-none-any.whl.metadata (8.2 kB)
Collecting MarkupSafe>=2.0 (from jinja2>=2.11.2->fastapi)
Using cached MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.0 kB)
Collecting annotated-types>=0.4.0 (from pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4->fastapi)
Using cached annotated_types-0.7.0-py3-none-any.whl.metadata (15 kB)
Collecting pydantic-core==2.20.1 (from pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4->fastapi)
Using cached pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)
Collecting click>=7.0 (from uvicorn>=0.12.0->uvicorn[standard]>=0.12.0->fastapi)
Using cached click-8.1.7-py3-none-any.whl.metadata (3.0 kB)
Collecting httptools>=0.5.0 (from uvicorn[standard]>=0.12.0->fastapi)
Using cached httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.6 kB)
Collecting python-dotenv>=0.13 (from uvicorn[standard]>=0.12.0->fastapi)
Using cached python_dotenv-1.0.1-py3-none-any.whl.metadata (23 kB)
Collecting pyyaml>=5.1 (from uvicorn[standard]>=0.12.0->fastapi)
Using cached PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.1 kB)
Collecting uvloop!=0.15.0,!=0.15.1,>=0.14.0 (from uvicorn[standard]>=0.12.0->fastapi)
Using cached uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB)
Collecting watchfiles>=0.13 (from uvicorn[standard]>=0.12.0->fastapi)
Using cached watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB)
Collecting websockets>=10.4 (from uvicorn[standard]>=0.12.0->fastapi)
Downloading websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)
Collecting exceptiongroup>=1.0.2 (from anyio->httpx>=0.23.0->fastapi)
Using cached exceptiongroup-1.2.2-py3-none-any.whl.metadata (6.6 kB)
Collecting shellingham>=1.3.0 (from typer>=0.12.3->fastapi-cli>=0.0.2->fastapi)
Using cached shellingham-1.5.4-py2.py3-none-any.whl.metadata (3.5 kB)
Collecting rich>=10.11.0 (from typer>=0.12.3->fastapi-cli>=0.0.2->fastapi)
Using cached rich-13.7.1-py3-none-any.whl.metadata (18 kB)
Collecting markdown-it-py>=2.2.0 (from rich>=10.11.0->typer>=0.12.3->fastapi-cli>=0.0.2->fastapi)
Using cached markdown_it_py-3.0.0-py3-none-any.whl.metadata (6.9 kB)
Collecting pygments<3.0.0,>=2.13.0 (from rich>=10.11.0->typer>=0.12.3->fastapi-cli>=0.0.2->fastapi)
Using cached pygments-2.18.0-py3-none-any.whl.metadata (2.5 kB)
Collecting mdurl~=0.1 (from markdown-it-py>=2.2.0->rich>=10.11.0->typer>=0.12.3->fastapi-cli>=0.0.2->fastapi)
Using cached mdurl-0.1.2-py3-none-any.whl.metadata (1.6 kB)
Downloading fastapi-0.111.1-py3-none-any.whl (92 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 92.2/92.2 kB 1.1 MB/s eta 0:00:00
Using cached email_validator-2.2.0-py3-none-any.whl (33 kB)
Using cached fastapi_cli-0.0.4-py3-none-any.whl (9.5 kB)
Using cached httpx-0.27.0-py3-none-any.whl (75 kB)
Using cached httpcore-1.0.5-py3-none-any.whl (77 kB)
Using cached jinja2-3.1.4-py3-none-any.whl (133 kB)
Using cached pydantic-2.8.2-py3-none-any.whl (423 kB)
Using cached pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB)
Using cached python_multipart-0.0.9-py3-none-any.whl (22 kB)
Using cached starlette-0.37.2-py3-none-any.whl (71 kB)
Using cached typing_extensions-4.12.2-py3-none-any.whl (37 kB)
Downloading uvicorn-0.30.3-py3-none-any.whl (62 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.8/62.8 kB 1.1 MB/s eta 0:00:00
Using cached annotated_types-0.7.0-py3-none-any.whl (13 kB)
Using cached anyio-4.4.0-py3-none-any.whl (86 kB)
Using cached click-8.1.7-py3-none-any.whl (97 kB)
Using cached dnspython-2.6.1-py3-none-any.whl (307 kB)
Using cached h11-0.14.0-py3-none-any.whl (58 kB)
Using cached httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (341 kB)
Using cached idna-3.7-py3-none-any.whl (66 kB)
Using cached MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (25 kB)
Using cached python_dotenv-1.0.1-py3-none-any.whl (19 kB)
Using cached PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (705 kB)
Using cached sniffio-1.3.1-py3-none-any.whl (10 kB)
Using cached typer-0.12.3-py3-none-any.whl (47 kB)
Using cached uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.4 MB)
Using cached watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.2 MB)
Downloading websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (130 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 130.2/130.2 kB 1.2 MB/s eta 0:00:00
Using cached certifi-2024.7.4-py3-none-any.whl (162 kB)
Using cached exceptiongroup-1.2.2-py3-none-any.whl (16 kB)
Using cached rich-13.7.1-py3-none-any.whl (240 kB)
Using cached shellingham-1.5.4-py2.py3-none-any.whl (9.8 kB)
Using cached markdown_it_py-3.0.0-py3-none-any.whl (87 kB)
Using cached pygments-2.18.0-py3-none-any.whl (1.2 MB)
Using cached mdurl-0.1.2-py3-none-any.whl (10.0 kB)
Installing collected packages: websockets, uvloop, typing-extensions, sniffio, shellingham, pyyaml, python-multipart, python-dotenv, pygments, mdurl, MarkupSafe, idna, httptools, h11, exceptiongroup, dnspython, click, certifi, annotated-types, uvicorn, pydantic-core, markdown-it-py, jinja2, httpcore, email_validator, anyio, watchfiles, starlette, rich, pydantic, httpx, typer, fastapi-cli, fastapi
Successfully installed MarkupSafe-2.1.5 annotated-types-0.7.0 anyio-4.4.0 certifi-2024.7.4 click-8.1.7 dnspython-2.6.1 email_validator-2.2.0 exceptiongroup-1.2.2 fastapi-0.111.1 fastapi-cli-0.0.4 h11-0.14.0 httpcore-1.0.5 httptools-0.6.1 httpx-0.27.0 idna-3.7 jinja2-3.1.4 markdown-it-py-3.0.0 mdurl-0.1.2 pydantic-2.8.2 pydantic-core-2.20.1 pygments-2.18.0 python-dotenv-1.0.1 python-multipart-0.0.9 pyyaml-6.0.1 rich-13.7.1 shellingham-1.5.4 sniffio-1.3.1 starlette-0.37.2 typer-0.12.3 typing-extensions-4.12.2 uvicorn-0.30.3 uvloop-0.19.0 watchfiles-0.22.0 websockets-12.0
Verify the installation:
(fastapi) dynotes@P2021:~/projects/python/fastapi$ fastapi --help
Usage: fastapi [OPTIONS] COMMAND [ARGS]...
FastAPI CLI - The fastapi command line app. 😎
Manage your FastAPI projects, run your FastAPI apps, and more.
Read more in the docs: https://fastapi.tiangolo.com/fastapi-cli/.
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --version Show the version and exit. │
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy it or customize the installation. │
│ --help Show this message and exit. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ──────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ dev Run a FastAPI app in development mode. 🧪 │
│ run Run a FastAPI app in production mode. 🚀 │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Voilà!
Creating a Simple API
Save the following code into example1.py
# this sample code is modified from https://fastapi.tiangolo.com/#create-it
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
In this example, we have created a simple FastAPI application with two endpoints.
- The
@app.get("/")
decorator defines a GET endpoint at the root URL, and, @app.get("/items/{item_id}")
defines a GET endpoint that accepts a path parameter and an optional query parameter, i.e.,/items/1?q=test
.
Running the API
(fastapi) dynotes@P2021:~/projects/python/fastapi$ fastapi dev example1.py
INFO Using path example1.py
INFO Resolved absolute path ~/projects/python/fastapi/example1.py
INFO Searching for package file structure from directories with __init__.py files
INFO Importing from ~/projects/python/fastapi
╭─ Python module file ─╮
│ │
│ 🐍 example1.py │
│ │
╰──────────────────────╯
INFO Importing module example1
INFO Found importable FastAPI app
╭── Importable FastAPI app ──╮
│ │
│ from example1 import app │
│ │
╰────────────────────────────╯
INFO Using import string example1:app
╭────────── FastAPI CLI - Development mode ───────────╮
│ │
│ Serving at: http://127.0.0.1:8000 │
│ │
│ API docs: http://127.0.0.1:8000/docs │
│ │
│ Running in development mode, for production use: │
│ │
│ fastapi run │
│ │
╰─────────────────────────────────────────────────────╯
INFO: Will watch for changes in these directories: ['~/projects/python/fastapi']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [213690] using WatchFiles
INFO: Started server process [213692]
INFO: Waiting for application startup.
INFO: Application startup complete.
A quick test
Open a browser and enter: http://127.0.0.1:8000
, then you will see the “Hello Word”
Open the FastAPI’s Swagger page by entering http://127.0.0.1:8000/docs
Expend /items/{item_id}
, click Try it out
button, enter 45
for item_id
and This is a test
for q
, then click Execute
A Full CRUD Example with FastAPI
Create the FastAPI Application
Create a file named full_crud.py
and add the following code:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI()
# Pydantic model to define the structure of an Item
class Item(BaseModel):
id: int
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
# In-memory store for items
items = []
# Create
@app.post("/items/", response_model=Item)
def create_item(item: Item):
items.append(item)
return item
# Read All
@app.get("/items/", response_model=List[Item])
def read_items():
return items
# Read One
@app.get("/items/{item_id}", response_model=Item)
def read_item(item_id: int):
for item in items:
if item.id == item_id:
return item
raise HTTPException(status_code=404, detail="Item not found")
# Update
@app.put("/items/{item_id}", response_model=Item)
def update_item(item_id: int, item: Item):
for index, existing_item in enumerate(items):
if existing_item.id == item_id:
items[index] = item
return item
raise HTTPException(status_code=404, detail="Item not found")
# Delete
@app.delete("/items/{item_id}", response_model=Item)
def delete_item(item_id: int):
for index, item in enumerate(items):
if item.id == item_id:
deleted_item = items.pop(index)
return deleted_item
raise HTTPException(status_code=404, detail="Item not found")
Run the API
(fastapi) dynotes@P2021:~/projects/python/fastapi$ fastapi dev full_crud.py
INFO Using path full_crud.py
INFO Resolved absolute path ~/projects/python/fastapi/full_crud.py
INFO Searching for package file structure from directories with __init__.py files
INFO Importing from ~/projects/python/fastapi
╭─ Python module file ─╮
│ │
│ 🐍 full_crud.py │
│ │
╰──────────────────────╯
INFO Importing module full_crud
INFO Found importable FastAPI app
╭── Importable FastAPI app ───╮
│ │
│ from full_crud import app │
│ │
╰─────────────────────────────╯
INFO Using import string full_crud:app
╭────────── FastAPI CLI - Development mode ───────────╮
│ │
│ Serving at: http://127.0.0.1:8000 │
│ │
│ API docs: http://127.0.0.1:8000/docs │
│ │
│ Running in development mode, for production use: │
│ │
│ fastapi run │
│ │
╰─────────────────────────────────────────────────────╯
INFO: Will watch for changes in these directories: ['~/projects/python/fastapi']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [213780] using WatchFiles
INFO: Started server process [213782]
INFO: Waiting for application startup.
INFO: Application startup complete.
Test the API
Navigate to http://127.0.0.1:8000/docs
to access Swagger UI and interact with the API:
CRUD Operations
Create an Item:
- Endpoint:
POST /items/
- Request Body:
{
"id": 1,
"name": "Item 1",
"description": "Description of Item 1",
"price": 10.5,
"tax": 1.5
}
- Response:
{
"id": 1,
"name": "Item 1",
"description": "Description of Item 1",
"price": 10.5,
"tax": 1.5
}
Read All Items:
- Endpoint:
GET /items/
- Response:
[
{
"id": 1,
"name": "Item 1",
"description": "Description of Item 1",
"price": 10.5,
"tax": 1.5
}
]
Read One Item:
- Endpoint:
GET /items/{item_id}
- Response:
{
"id": 1,
"name": "Item 1",
"description": "Description of Item 1",
"price": 10.5,
"tax": 1.5
}
Update an Item:
- Endpoint:
PUT /items/{item_id}
- Request Body:
{
"id": 1,
"name": "Updated Item 1",
"description": "Updated Description",
"price": 12.0,
"tax": 2.0
}
- Response:
{
"id": 1,
"name": "Updated Item 1",
"description": "Updated Description",
"price": 12.0,
"tax": 2.0
}
Delete an Item:
- Endpoint:
DELETE /items/{item_id}
- Response:
{
"id": 1,
"name": "Item 1",
"description": "Description of Item 1",
"price": 10.5,
"tax": 1.5
}
FastAPI is a powerful, modern framework that simplifies the process of building APIs with Python. Its combination of high performance, ease of use, and robust feature set makes it an excellent choice for both beginners and experienced developers. Whether you’re building a simple project or a complex microservices architecture, FastAPI can help you achieve your goals efficiently.
If you’re passionate about Python and always on the lookout for new tools to enhance your development experience, look no further than the “Amazing Python Package Showcase” series. This collection of articles is dedicated to uncovering some of the most powerful and innovative Python libraries available today. From virtualenv to PassLib, Pyodide, PySnooper, Mimesis, and now FastAPI, each article delves deep into the functionalities, benefits, and practical applications of these exceptional packages. Whether you’re a seasoned developer or a Python enthusiast, this series is designed to equip you with the knowledge and tools to elevate your projects and streamline your coding processes. Don’t miss out on exploring these game-changing Python packages that can transform the way you code!
Leave a Reply