A Comprehensive Example#
Flask API Server Code#
app.py
1# -*- coding: utf-8 -*-
2
3# standard library
4import enum
5from pathlib import Path
6from datetime import datetime
7
8# third-party
9import flask
10import flask_restless
11import flask_sqlalchemy
12import sqlalchemy.orm as orm
13
14# Create the Flask application and the Flask-SQLAlchemy object.
15app = flask.Flask(__name__)
16# Tell flask-sqlalchemy how to connect to the database
17path_db = Path(__file__).absolute().parent / "app.sqlite"
18app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{path_db}"
19# Create the virtual database object (it's a flask-sqlalchemy concepts).
20db = flask_sqlalchemy.SQLAlchemy(app)
21
22
23# Declare data models
24#
25# - User: a YouTube user
26# - Video: a video uploaded by user, one video can have only one user
27# - Playlist: a playlist created by user, it could have many videos.
28# - User / Video authorship is one-to-many relationship
29# - User / Playlist ownership is one-to-many relationship
30# - Playlist / Video is many-to-many relationship
31class User(db.Model):
32 __tablename__ = "user"
33
34 id: orm.Mapped[str] = db.Column(db.Unicode, primary_key=True)
35 name: orm.Mapped[str] = db.Column(db.Unicode)
36 create_at: orm.Mapped[datetime] = db.Column(db.DateTime)
37 update_at: orm.Mapped[datetime] = db.Column(db.DateTime)
38 deleted: orm.Mapped[int] = db.Column(db.Integer)
39
40
41# for many-to-many relationship, define an association table
42# and then pick one of the involved entity model, set a relationship there
43# in this example, we will set a relationship in Playlist at videos attribute
44playlist_video = db.Table(
45 # table name doesn't matter
46 "playlist_video",
47 db.Column(
48 # column name doesn't matter
49 "playlist_id",
50 db.Unicode,
51 # foreign key does matter, it should be ${table_name}.${primary_key_name}
52 db.ForeignKey("playlist.id"),
53 ),
54 db.Column(
55 # column name doesn't matter
56 "video_id",
57 db.Unicode,
58 # foreign key does matter, it should be ${table_name}.${primary_key_name}
59 db.ForeignKey("video.id"),
60 ),
61)
62
63
64# for one-to-many relationship, define a relationship in the 'many' entity.
65# in this example, one user can have many videos, then the 'many' entity is the Video,
66# so we will set a relationship in 'Video' at 'author' attribute
67class Video(db.Model):
68 __tablename__ = "video"
69
70 id: orm.Mapped[str] = db.Column(db.Unicode, primary_key=True)
71 title: orm.Mapped[str] = db.Column(db.Unicode)
72 create_at: orm.Mapped[datetime] = db.Column(db.DateTime)
73 update_at: orm.Mapped[datetime] = db.Column(db.DateTime)
74 deleted: orm.Mapped[int] = db.Column(db.Integer)
75 author_id: orm.Mapped[str] = db.Column(db.Unicode, db.ForeignKey("user.id"))
76
77 # This is not a column, it is a relationship
78 author: orm.Mapped[User] = db.relationship(
79 User,
80 # backref will generate a new attribute (not column) in User model
81 # you can use this attribute to get all videos uploaded by this user.
82 # note that you have to declare this backref attribute in User model
83 # with original sqlalchemy.orm
84 # (see example: https://github.com/MacHu-GWU/learn_sqlalchemy-project/blob/master/docs/source/02-orm/02-relationship-configuration/02-one-to-many/e1_simple_one_to_many.py#L23)
85 # but you don't do (you cannot) this and flask-sqlalchemy will do this for you
86 backref=db.backref("owned_videos"),
87 )
88
89
90class Playlist(db.Model):
91 __tablename__ = "playlist"
92
93 id: orm.Mapped[str] = db.Column(db.Unicode, primary_key=True)
94 title: orm.Mapped[str] = db.Column(db.Unicode)
95 create_at: orm.Mapped[datetime] = db.Column(db.DateTime)
96 update_at: orm.Mapped[datetime] = db.Column(db.DateTime)
97 deleted: orm.Mapped[int] = db.Column(db.Integer)
98 owner_id: orm.Mapped[str] = db.Column(db.Unicode, db.ForeignKey("user.id"))
99
100 # this one is similar to Video.author
101 owner: orm.Mapped[User] = db.relationship(
102 User, backref=db.backref("owned_playlists")
103 )
104 # backref will generate a new attribute (not column) in Video model
105 # you can use this attribute to get all playlists that include this video.
106 # note that you have to declare this backref attribute in Video model
107 # with original sqlalchemy.orm
108 # (see example: https://github.com/MacHu-GWU/learn_sqlalchemy-project/blob/master/docs/source/02-orm/02-relationship-configuration/03-many-to-many/e1_many_to_many.py#L51)
109 # but you don't do (you cannot) this and flask-sqlalchemy will do this for you
110 videos = db.relationship(
111 Video,
112 # for many-to-many, use secondary to specify the association table
113 secondary=playlist_video,
114 backref="playlists",
115 )
116
117
118# Define an enumeration for the type of entity.
119class EntityTypeEnum(str, enum.Enum):
120 user = "user"
121 video = "video"
122 playlist = "playlist"
123
124
125ETE = EntityTypeEnum
126
127
128# Create the database tables.
129with app.app_context():
130 db.create_all()
131
132 # Create the Flask-Restless API manager.
133 manager = flask_restless.APIManager(app, session=db.session)
134
135 # Create API endpoints, which will be available at /api/<tablename> by
136 # default. Allowed HTTP methods can be specified as well.
137 manager.create_api(
138 User,
139 methods=["GET", "POST", "PATCH", "DELETE"],
140 # if you use auto-increment integer primary key, you should set allow_client_generated_ids=False
141 # if you use string primary key, you can set allow_client_generated_ids=True
142 allow_client_generated_ids=True,
143 )
144 manager.create_api(
145 Video,
146 # Get = retrieve
147 # Post = create
148 # Patch = update
149 # Delete = delete
150 methods=["GET", "POST", "PATCH", "DELETE"],
151 allow_client_generated_ids=True,
152 # if you want to replace many videos owned by a user
153 # or many videos in a playlist in one API call, you should set allow_to_many_replacement=True
154 allow_to_many_replacement=True,
155 )
156 manager.create_api(
157 Playlist,
158 methods=["GET", "POST", "PATCH", "DELETE"],
159 allow_client_generated_ids=True,
160 allow_to_many_replacement=True,
161 )
Import Dependencies and ORM Model#
[1]:
import typing as T
import json
import uuid
import requests
from datetime import datetime
import sqlalchemy as sa
import sqlalchemy.orm as orm
from rich import print as rprint
from rich.console import Console
from rich.panel import Panel
from app import path_db, User, Video, Playlist, playlist_video, ETE
Define Some Variables and Helper Functions#
[2]:
engine = sa.create_engine(f"sqlite:///{path_db}")
host = "http://127.0.0.1:5000"
post_headers = {
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
}
get_headers = {
"Accept": "application/vnd.api+json",
}
console = Console()
def get_utc_now():
return datetime.utcnow()
def clear_data():
with engine.connect() as conn:
conn.execute(Video.__table__.delete())
conn.execute(User.__table__.delete())
conn.execute(Playlist.__table__.delete())
conn.execute(playlist_video.delete())
conn.commit()
def make_request(meth: T.Callable, kwargs: T.Dict[str, T.Any]) -> requests.Response:
"""
A helper function that make HTTP request, and automatically print request and response.
"""
console.rule("API Request")
rprint(kwargs)
response: requests.Response = meth(**kwargs)
console.rule("API Response status code")
rprint(f"{response.status_code = }")
console.rule("API Response headers")
rprint(dict(response.headers))
console.rule("API Response data")
if response.status_code in [200, 201]:
rprint(json.loads(response.text))
elif response.status_code == 204:
print("No content")
else:
rprint(response.text)
return response
Clear Data for a Fresh Start#
[3]:
clear_data()
Create User#
[4]:
rprint(Panel("Create a user"))
user_id_1 = str(uuid.uuid4())
user_id_1_create_at = get_utc_now()
url = f"{host}/api/{ETE.user.value}"
data = {
"data": {
"type": ETE.user.value,
"id": user_id_1,
"attributes": {
"name": "Alice",
"create_at": user_id_1_create_at.isoformat(),
"update_at": user_id_1_create_at.isoformat(),
"deleted": 0,
},
},
}
kwargs = dict(
url=url,
headers=post_headers,
json=data,
)
response = make_request(requests.post, kwargs)
# verify the data in the database
with orm.Session(engine) as ses:
user = ses.get(User, user_id_1)
assert user.id == user_id_1
assert user.name == "Alice"
assert user.create_at == user_id_1_create_at
assert user.update_at == user_id_1_create_at
assert user.deleted == 0
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Create a user │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
─────────────────────────────────────────────────── API Request ───────────────────────────────────────────────────
{ 'url': 'http://127.0.0.1:5000/api/user', 'headers': {'Content-Type': 'application/vnd.api+json', 'Accept': 'application/vnd.api+json'}, 'json': { 'data': { 'type': 'user', 'id': '62608ecc-5809-4534-a97e-246185f8e876', 'attributes': { 'name': 'Alice', 'create_at': '2024-06-02T04:44:04.906714', 'update_at': '2024-06-02T04:44:04.906714', 'deleted': 0 } } } }
──────────────────────────────────────────── API Response status code ─────────────────────────────────────────────
response.status_code = 201
────────────────────────────────────────────── API Response headers ───────────────────────────────────────────────
{ 'Server': 'Werkzeug/3.0.3 Python/3.8.13', 'Date': 'Sun, 02 Jun 2024 04:44:04 GMT', 'Location': 'http://127.0.0.1:5000/api/user/62608ecc-5809-4534-a97e-246185f8e876', 'Content-Type': 'application/vnd.api+json', 'Content-Length': '318', 'Connection': 'close' }
──────────────────────────────────────────────── API Response data ────────────────────────────────────────────────
{ 'data': { 'attributes': { 'create_at': '2024-06-02T04:44:04.906714', 'deleted': 0, 'name': 'Alice', 'update_at': '2024-06-02T04:44:04.906714' }, 'id': '62608ecc-5809-4534-a97e-246185f8e876', 'relationships': {'owned_playlists': {'data': []}, 'owned_videos': {'data': []}}, 'type': 'user' }, 'jsonapi': {'version': '1.0'} }
Get user 1#
[5]:
rprint(Panel("Get user 1"))
url = f"{host}/api/{ETE.user.value}/{user_id_1}"
kwargs = dict(
url=url,
headers=get_headers,
)
response = make_request(requests.get, kwargs)
data = json.loads(response.text)
assert data["data"]["id"] == user_id_1
assert data["data"]["attributes"]["name"] == "Alice"
assert data["data"]["attributes"]["deleted"] == 0
assert data["data"]["attributes"]["create_at"] == user_id_1_create_at.isoformat()
assert data["data"]["attributes"]["update_at"] == user_id_1_create_at.isoformat()
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Get user 1 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
─────────────────────────────────────────────────── API Request ───────────────────────────────────────────────────
{ 'url': 'http://127.0.0.1:5000/api/user/62608ecc-5809-4534-a97e-246185f8e876', 'headers': {'Accept': 'application/vnd.api+json'} }
──────────────────────────────────────────── API Response status code ─────────────────────────────────────────────
response.status_code = 200
────────────────────────────────────────────── API Response headers ───────────────────────────────────────────────
{ 'Server': 'Werkzeug/3.0.3 Python/3.8.13', 'Date': 'Sun, 02 Jun 2024 04:44:30 GMT', 'Content-Type': 'application/vnd.api+json', 'Content-Length': '318', 'Connection': 'close' }
──────────────────────────────────────────────── API Response data ────────────────────────────────────────────────
{ 'data': { 'attributes': { 'create_at': '2024-06-02T04:44:04.906714', 'deleted': 0, 'name': 'Alice', 'update_at': '2024-06-02T04:44:04.906714' }, 'id': '62608ecc-5809-4534-a97e-246185f8e876', 'relationships': {'owned_playlists': {'data': []}, 'owned_videos': {'data': []}}, 'type': 'user' }, 'jsonapi': {'version': '1.0'} }
Update the user 1#
[6]:
rprint(Panel("Update the user 1"))
url = f"{host}/api/{ETE.user.value}/{user_id_1}"
user_id_1_update_at = get_utc_now()
data = {
"data": {
"type": ETE.user.value,
"id": user_id_1,
"attributes": {
"name": "Bob",
"update_at": user_id_1_update_at.isoformat(),
},
},
}
kwargs = dict(
url=url,
headers=post_headers,
json=data,
)
response = make_request(requests.patch, kwargs)
# verify the data in the database
with orm.Session(engine) as ses:
user = ses.get(User, user_id_1)
assert user.id == user_id_1
assert user.name == "Bob"
assert user.create_at == user_id_1_create_at
assert user.update_at == user_id_1_update_at
assert user.deleted == 0
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Update the user 1 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
─────────────────────────────────────────────────── API Request ───────────────────────────────────────────────────
{ 'url': 'http://127.0.0.1:5000/api/user/62608ecc-5809-4534-a97e-246185f8e876', 'headers': {'Content-Type': 'application/vnd.api+json', 'Accept': 'application/vnd.api+json'}, 'json': { 'data': { 'type': 'user', 'id': '62608ecc-5809-4534-a97e-246185f8e876', 'attributes': {'name': 'Bob', 'update_at': '2024-06-02T04:44:52.987591'} } } }
──────────────────────────────────────────── API Response status code ─────────────────────────────────────────────
response.status_code = 204
────────────────────────────────────────────── API Response headers ───────────────────────────────────────────────
{ 'Server': 'Werkzeug/3.0.3 Python/3.8.13', 'Date': 'Sun, 02 Jun 2024 04:44:53 GMT', 'Content-Type': 'application/vnd.api+json', 'Connection': 'close' }
──────────────────────────────────────────────── API Response data ────────────────────────────────────────────────
No content
Create a video#
[7]:
rprint(Panel("Create a video"))
video_id_1 = str(uuid.uuid4())
video_id_1_create_at = get_utc_now()
url = f"{host}/api/{ETE.video.value}"
data = {
"data": {
"type": ETE.video.value,
"id": video_id_1,
"attributes": {
"title": "user 1 video 1",
"create_at": video_id_1_create_at.isoformat(),
"update_at": video_id_1_create_at.isoformat(),
"deleted": 0,
"author_id": user_id_1,
},
},
}
kwargs = dict(
url=url,
headers=post_headers,
json=data,
)
response = make_request(requests.post, kwargs)
# verify the data in the database
with orm.Session(engine) as ses:
video = ses.get(Video, video_id_1)
assert video.id == video_id_1
assert video.title == "user 1 video 1"
assert video.create_at == video_id_1_create_at
assert video.update_at == video_id_1_create_at
assert video.deleted == 0
assert video.author_id == user_id_1
assert video.author.id == user_id_1
assert video.author.name == "Bob"
user = ses.get(User, user_id_1)
owned_videos = user.owned_videos
assert len(owned_videos) == 1
video = owned_videos[0]
assert video.id == video_id_1
assert video.title == "user 1 video 1"
assert video.create_at == video_id_1_create_at
assert video.update_at == video_id_1_create_at
assert video.deleted == 0
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Create a video │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
─────────────────────────────────────────────────── API Request ───────────────────────────────────────────────────
{ 'url': 'http://127.0.0.1:5000/api/video', 'headers': {'Content-Type': 'application/vnd.api+json', 'Accept': 'application/vnd.api+json'}, 'json': { 'data': { 'type': 'video', 'id': 'ca585605-67d8-4e77-becc-8afa09a2fd82', 'attributes': { 'title': 'user 1 video 1', 'create_at': '2024-06-02T04:45:05.647971', 'update_at': '2024-06-02T04:45:05.647971', 'deleted': 0, 'author_id': '62608ecc-5809-4534-a97e-246185f8e876' } } } }
──────────────────────────────────────────── API Response status code ─────────────────────────────────────────────
response.status_code = 201
────────────────────────────────────────────── API Response headers ───────────────────────────────────────────────
{ 'Server': 'Werkzeug/3.0.3 Python/3.8.13', 'Date': 'Sun, 02 Jun 2024 04:45:05 GMT', 'Location': 'http://127.0.0.1:5000/api/video/ca585605-67d8-4e77-becc-8afa09a2fd82', 'Content-Type': 'application/vnd.api+json', 'Content-Length': '377', 'Connection': 'close' }
──────────────────────────────────────────────── API Response data ────────────────────────────────────────────────
{ 'data': { 'attributes': { 'create_at': '2024-06-02T04:45:05.647971', 'deleted': 0, 'title': 'user 1 video 1', 'update_at': '2024-06-02T04:45:05.647971' }, 'id': 'ca585605-67d8-4e77-becc-8afa09a2fd82', 'relationships': { 'author': {'data': {'id': '62608ecc-5809-4534-a97e-246185f8e876', 'type': 'user'}}, 'playlists': {'data': []} }, 'type': 'video' }, 'jsonapi': {'version': '1.0'} }
Update video ownership relationship#
[8]:
rprint(Panel("Update video ownership relationship"))
# create a new user, so we can switch the ownership to this new user
user_id_2 = str(uuid.uuid4())
user_id_2_create_at = get_utc_now()
url = f"{host}/api/{ETE.user.value}"
data = {
"data": {
"type": ETE.user.value,
"id": user_id_2,
"attributes": {
"name": "Bella",
"create_at": user_id_2_create_at.isoformat(),
"update_at": user_id_2_create_at.isoformat(),
"deleted": 0,
},
},
}
kwargs = dict(
url=url,
headers=post_headers,
json=data,
)
requests.post(**kwargs)
# update video ownership
url = f"{host}/api/{ETE.video.value}/{video_id_1}/relationships/author"
data = {
"data": {
"type": ETE.user.value,
"id": user_id_2,
},
}
kwargs = dict(
url=url,
headers=post_headers,
json=data,
)
response = make_request(requests.patch, kwargs)
# verify the data in the database
with orm.Session(engine) as ses:
video = ses.get(Video, video_id_1)
assert video.author_id == user_id_2
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Update video ownership relationship │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
─────────────────────────────────────────────────── API Request ───────────────────────────────────────────────────
{ 'url': 'http://127.0.0.1:5000/api/video/ca585605-67d8-4e77-becc-8afa09a2fd82/relationships/author', 'headers': {'Content-Type': 'application/vnd.api+json', 'Accept': 'application/vnd.api+json'}, 'json': {'data': {'type': 'user', 'id': '3d8897cd-af7f-4739-9921-069844277f5d'}} }
──────────────────────────────────────────── API Response status code ─────────────────────────────────────────────
response.status_code = 204
────────────────────────────────────────────── API Response headers ───────────────────────────────────────────────
{ 'Server': 'Werkzeug/3.0.3 Python/3.8.13', 'Date': 'Sun, 02 Jun 2024 04:45:21 GMT', 'Content-Type': 'application/vnd.api+json', 'Connection': 'close' }
──────────────────────────────────────────────── API Response data ────────────────────────────────────────────────
No content
Create playlist video relationship#
[9]:
rprint(Panel("Create playlist video relationship"))
# first, create two more videos and two playlists
# now we have 3 videos and 2 playlists
video_id_2 = str(uuid.uuid4())
video_id_3 = str(uuid.uuid4())
playlist_id_1 = str(uuid.uuid4())
playlist_id_2 = str(uuid.uuid4())
utc_now = get_utc_now()
url = f"{host}/api/{ETE.video.value}"
for ith, video_id in enumerate([video_id_2, video_id_3], start=2):
data = {
"data": {
"type": ETE.video.value,
"id": video_id,
"attributes": {
"title": f"user 1 video {ith}",
"create_at": utc_now.isoformat(),
"update_at": utc_now.isoformat(),
"deleted": 0,
"author_id": user_id_1,
},
},
}
kwargs = dict(
url=url,
headers=post_headers,
json=data,
)
requests.post(**kwargs)
url = f"{host}/api/{ETE.playlist.value}"
for ith, playlist_id in enumerate([playlist_id_1, playlist_id_2], start=1):
data = {
"data": {
"type": ETE.playlist.value,
"id": playlist_id,
"attributes": {
"title": f"user 1 playlist {ith}",
"create_at": utc_now.isoformat(),
"update_at": utc_now.isoformat(),
"deleted": 0,
"owner_id": user_id_1,
},
},
}
kwargs = dict(
url=url,
headers=post_headers,
json=data,
)
requests.post(**kwargs)
# playlist 1 includes video 1 and video 2
# playlist 2 includes video 2 and video 3
url = f"{host}/api/{ETE.playlist.value}/{playlist_id_1}/relationships/videos"
data = {
"data": [
{
"type": ETE.video.value,
"id": video_id_1,
},
{
"type": ETE.video.value,
"id": video_id_2,
},
],
}
kwargs = dict(
url=url,
headers=post_headers,
json=data,
)
response = make_request(requests.patch, kwargs)
url = f"{host}/api/{ETE.playlist.value}/{playlist_id_2}/relationships/videos"
data = {
"data": [
{
"type": ETE.video.value,
"id": video_id_2,
},
{
"type": ETE.video.value,
"id": video_id_3,
},
],
}
kwargs = dict(
url=url,
headers=post_headers,
json=data,
)
response = make_request(requests.patch, kwargs)
# verify the data in the database
with orm.Session(engine) as ses:
playlist = ses.get(Playlist, playlist_id_1)
assert {video.id for video in playlist.videos} == {
video_id_1,
video_id_2,
}
video = ses.get(Video, video_id_2)
assert {playlist.id for playlist in video.playlists} == {
playlist_id_1,
playlist_id_2,
}
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Create playlist video relationship │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
─────────────────────────────────────────────────── API Request ───────────────────────────────────────────────────
{ 'url': 'http://127.0.0.1:5000/api/playlist/3e42c410-bf51-4fa2-ab3d-fd19d8ac327a/relationships/videos', 'headers': {'Content-Type': 'application/vnd.api+json', 'Accept': 'application/vnd.api+json'}, 'json': { 'data': [ {'type': 'video', 'id': 'ca585605-67d8-4e77-becc-8afa09a2fd82'}, {'type': 'video', 'id': '73cc46c0-5aff-47f4-bc1b-4a5a9d816164'} ] } }
──────────────────────────────────────────── API Response status code ─────────────────────────────────────────────
response.status_code = 204
────────────────────────────────────────────── API Response headers ───────────────────────────────────────────────
{ 'Server': 'Werkzeug/3.0.3 Python/3.8.13', 'Date': 'Sun, 02 Jun 2024 04:45:38 GMT', 'Content-Type': 'application/vnd.api+json', 'Connection': 'close' }
──────────────────────────────────────────────── API Response data ────────────────────────────────────────────────
No content
─────────────────────────────────────────────────── API Request ───────────────────────────────────────────────────
{ 'url': 'http://127.0.0.1:5000/api/playlist/18ab3ee2-8f44-45b7-8add-ed4687e43aca/relationships/videos', 'headers': {'Content-Type': 'application/vnd.api+json', 'Accept': 'application/vnd.api+json'}, 'json': { 'data': [ {'type': 'video', 'id': '73cc46c0-5aff-47f4-bc1b-4a5a9d816164'}, {'type': 'video', 'id': '4e02748a-21a9-45d7-a0b6-ba41aedfd944'} ] } }
──────────────────────────────────────────── API Response status code ─────────────────────────────────────────────
response.status_code = 204
────────────────────────────────────────────── API Response headers ───────────────────────────────────────────────
{ 'Server': 'Werkzeug/3.0.3 Python/3.8.13', 'Date': 'Sun, 02 Jun 2024 04:45:38 GMT', 'Content-Type': 'application/vnd.api+json', 'Connection': 'close' }
──────────────────────────────────────────────── API Response data ────────────────────────────────────────────────
No content
Update playlist video relationship#
[10]:
rprint(Panel("Update playlist video relationship"))
url = f"{host}/api/{ETE.playlist.value}/{playlist_id_1}/relationships/videos"
data = {
"data": [
{
"type": ETE.video.value,
"id": video_id_1,
},
{
"type": ETE.video.value,
"id": video_id_3,
},
],
}
kwargs = dict(
url=url,
headers=post_headers,
json=data,
)
response = make_request(requests.patch, kwargs)
# verify the data in the database
with orm.Session(engine) as ses:
playlist = ses.get(Playlist, playlist_id_1)
assert {video.id for video in playlist.videos} == {
video_id_1,
video_id_3,
}
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ Update playlist video relationship │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
─────────────────────────────────────────────────── API Request ───────────────────────────────────────────────────
{ 'url': 'http://127.0.0.1:5000/api/playlist/3e42c410-bf51-4fa2-ab3d-fd19d8ac327a/relationships/videos', 'headers': {'Content-Type': 'application/vnd.api+json', 'Accept': 'application/vnd.api+json'}, 'json': { 'data': [ {'type': 'video', 'id': 'ca585605-67d8-4e77-becc-8afa09a2fd82'}, {'type': 'video', 'id': '4e02748a-21a9-45d7-a0b6-ba41aedfd944'} ] } }
──────────────────────────────────────────── API Response status code ─────────────────────────────────────────────
response.status_code = 204
────────────────────────────────────────────── API Response headers ───────────────────────────────────────────────
{ 'Server': 'Werkzeug/3.0.3 Python/3.8.13', 'Date': 'Sun, 02 Jun 2024 04:45:49 GMT', 'Content-Type': 'application/vnd.api+json', 'Connection': 'close' }
──────────────────────────────────────────────── API Response data ────────────────────────────────────────────────
No content
[ ]: