Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions api-integration-in-python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Python and REST APIs: Interacting With Web Services

This folder provides the code examples for the Real Python tutorial [Python and REST APIs: Interacting With Web Services](https://realpython.com/api-integration-in-python/).

The examples are grouped into one subfolder per section of the tutorial, because the Flask and FastAPI examples both use a file called `app.py`, and because the tutorial itself advises you to keep each example in its own folder:

- `consuming-apis/`: the `requests` examples from **REST and Python: Consuming APIs**. The tutorial shows these in the REPL, so here they're runnable scripts that `print()` the results, one script per HTTP method section.
- `flask-api/`: the Flask countries API from **Tools of the Trade → Flask**.
- `django-api/`: the `countryapi` Django project with Django REST framework from **Tools of the Trade → Django REST Framework**.
- `fastapi-api/`: the FastAPI countries API from **Tools of the Trade → FastAPI**.

## Setup

Create and activate a virtual environment:

```console
$ python -m venv venv
$ source venv/bin/activate
```

Install the pinned dependencies:

```console
(venv) $ python -m pip install -r requirements.txt
```

The single `requirements.txt` covers all four examples. If you'd rather isolate them, then create one virtual environment per subfolder and install only the packages that example needs.

## Consuming APIs With `requests`

Each script sends one kind of request to [JSONPlaceholder](https://jsonplaceholder.typicode.com/) and prints the response, so you need an internet connection to run them:

```console
(venv) $ cd consuming-apis/
(venv) $ python get_request.py
{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}
200
application/json; charset=utf-8
```

The other scripts are `post_request.py`, `put_request.py`, `patch_request.py`, and `delete_request.py`.

## Flask

```console
(venv) $ cd flask-api/
(venv) $ export FLASK_APP=app.py
(venv) $ export FLASK_DEBUG=1
(venv) $ flask run
```

Then request the endpoint at `http://127.0.0.1:5000/countries`.

## Django REST Framework

The `django-api/` folder is the `countryapi` project that the tutorial creates with `django-admin startproject countryapi` and `python manage.py startapp countries`. Set up its database and load the fixture before you start the server:

```console
(venv) $ cd django-api/
(venv) $ python manage.py migrate
(venv) $ python manage.py loaddata countries.json
Installed 3 object(s) from 1 fixture(s)
(venv) $ python manage.py runserver
```

Then request the endpoint at `http://127.0.0.1:8000/countries/`.

## FastAPI

```console
(venv) $ cd fastapi-api/
(venv) $ uvicorn app:app --reload
```

Then request the endpoint at `http://127.0.0.1:8000/countries`.
12 changes: 12 additions & 0 deletions api-integration-in-python/consuming-apis/delete_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Send a DELETE request to JSONPlaceholder to remove a to-do.

From the "DELETE" section of the tutorial.
"""

import requests

api_url = "https://jsonplaceholder.typicode.com/todos/10"
response = requests.delete(api_url)
print(response.json())

print(response.status_code)
14 changes: 14 additions & 0 deletions api-integration-in-python/consuming-apis/get_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Send a GET request to JSONPlaceholder.

From the "GET" section of the tutorial.
"""

import requests

api_url = "https://jsonplaceholder.typicode.com/todos/1"
response = requests.get(api_url)
print(response.json())

# Beyond the JSON data, you can inspect the response itself.
print(response.status_code)
print(response.headers["Content-Type"])
13 changes: 13 additions & 0 deletions api-integration-in-python/consuming-apis/patch_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Send a PATCH request to JSONPlaceholder to modify one field.

From the "PATCH" section of the tutorial.
"""

import requests

api_url = "https://jsonplaceholder.typicode.com/todos/10"
todo = {"title": "Mow lawn"}
response = requests.patch(api_url, json=todo)
print(response.json())

print(response.status_code)
23 changes: 23 additions & 0 deletions api-integration-in-python/consuming-apis/post_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Send a POST request to JSONPlaceholder to create a new to-do.

From the "POST" section of the tutorial.
"""

import json

import requests

api_url = "https://jsonplaceholder.typicode.com/todos"
todo = {"userId": 1, "title": "Buy milk", "completed": False}
response = requests.post(api_url, json=todo)
print(response.json())

print(response.status_code)

# An equivalent version that serializes the JSON and sets the
# Content-Type header manually instead of using the json argument.
headers = {"Content-Type": "application/json"}
response = requests.post(api_url, data=json.dumps(todo), headers=headers)
print(response.json())

print(response.status_code)
16 changes: 16 additions & 0 deletions api-integration-in-python/consuming-apis/put_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Send a PUT request to JSONPlaceholder to replace an existing to-do.

From the "PUT" section of the tutorial.
"""

import requests

api_url = "https://jsonplaceholder.typicode.com/todos/10"
response = requests.get(api_url)
print(response.json())

todo = {"userId": 1, "title": "Wash car", "completed": True}
response = requests.put(api_url, json=todo)
print(response.json())

print(response.status_code)
Empty file.
3 changes: 3 additions & 0 deletions api-integration-in-python/django-api/countries/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin # noqa: F401

# Register your models here.
5 changes: 5 additions & 0 deletions api-integration-in-python/django-api/countries/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class CountriesConfig(AppConfig):
name = "countries"
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[
{
"model": "countries.country",
"pk": 1,
"fields": {
"name": "Thailand",
"capital": "Bangkok",
"area": 513120
}
},
{
"model": "countries.country",
"pk": 2,
"fields": {
"name": "Australia",
"capital": "Canberra",
"area": 7617930
}
},
{
"model": "countries.country",
"pk": 3,
"fields": {
"name": "Egypt",
"capital": "Cairo",
"area": 1010408
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 6.1 on 2026-09-16 15:01

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Country',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('capital', models.CharField(max_length=100)),
('area', models.IntegerField(help_text='(in square kilometers)')),
],
),
]
Empty file.
7 changes: 7 additions & 0 deletions api-integration-in-python/django-api/countries/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.db import models


class Country(models.Model):
name = models.CharField(max_length=100)
capital = models.CharField(max_length=100)
area = models.IntegerField(help_text="(in square kilometers)")
9 changes: 9 additions & 0 deletions api-integration-in-python/django-api/countries/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from rest_framework import serializers

from .models import Country


class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = ["id", "name", "capital", "area"]
3 changes: 3 additions & 0 deletions api-integration-in-python/django-api/countries/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase # noqa: F401

# Create your tests here.
9 changes: 9 additions & 0 deletions api-integration-in-python/django-api/countries/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter

from .views import CountryViewSet

router = DefaultRouter()
router.register(r"countries", CountryViewSet)

urlpatterns = [path("", include(router.urls))]
9 changes: 9 additions & 0 deletions api-integration-in-python/django-api/countries/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from rest_framework import viewsets

from .models import Country
from .serializers import CountrySerializer


class CountryViewSet(viewsets.ModelViewSet):
serializer_class = CountrySerializer
queryset = Country.objects.all()
Empty file.
16 changes: 16 additions & 0 deletions api-integration-in-python/django-api/countryapi/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
ASGI config for countryapi project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/6.1/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "countryapi.settings")

application = get_asgi_application()
Loading
Loading