5

I'm building a sort of Youtube audio downloader api and I would like to validate video ids (using youtube_dl). How do I add custom validation in FastAPI?

@router.get(
    "/audio/{video_id}",
    response_description="Redirects to the static url of the audio file.",
)
async def download_audio(
    video_id: str = Path(None, title="ID of the video (what you can see in the url)"),  # <-- How to validate?
):
...
# Here's some logic to download the audio of the video and save it. After that a `RedirectResponse` gets returned.

I know I could validate it in the function, but I think FastAPI has a better alternative.

1 Answer 1

5

FastAPI is using pydantic for data validations so you can either use Standard Library Types, Pydantic Types or Constrained Types for path params.

Example:

from fastapi import FastAPI
from pydantic import constr, NegativeInt


app = FastAPI(title="Test")


@app.get("/01/{test}")
async def test01(test: NegativeInt):
    return {"test": test}


@app.get("/02/{test}")
async def test02(test: constr(regex=r"^apple (pie|tart|sandwich)$")):
    return {"test": test}

Test:

$ curl -Ss localhost:8000/01/1 | python -m json.tool
{
    "detail": [
        {
            "loc": [
                "path",
                "test"
            ],
            "msg": "ensure this value is less than 0",
            "type": "value_error.number.not_lt",
            "ctx": {
                "limit_value": 0
            }
        }
    ]
}
$ curl -Ss localhost:8000/01/-1 | python -m json.tool
{
    "test": -1
}


$ curl -Ss localhost:8000/02/-1 | python -m json.tool
{
    "detail": [
        {
            "loc": [
                "path",
                "test"
            ],
            "msg": "string does not match regex \"^apple (pie|tart|sandwich)$\"",
            "type": "value_error.str.regex",
            "ctx": {
                "pattern": "^apple (pie|tart|sandwich)$"
            }
        }
    ]
}
$ curl -Ss localhost:8000/02/apple%20pie | python -m json.tool
{
    "test": "apple pie"
}

That having been said, I guess your best bet is a RegEx, something like constr(regex=r"^[0-9A-Za-z_-]{10}[048AEIMQUYcgkosw]$"), see Format for ID of YouTube video for details.

Update Thu 22 Apr 22:16:14 UTC 2021:

You can try something like this (this is just an example of course):

from __future__ import unicode_literals

import youtube_dl

from fastapi import FastAPI, HTTPException
from fastapi.concurrency import run_in_threadpool
from fastapi.responses import FileResponse


URL = "https://www.youtube.com/watch?v="


app = FastAPI(title="Test")

ydl_opts = {
    "format": "bestaudio/best",
    "outtmpl": "%(id)s.%(ext)s",
    "postprocessors": [
        {
            "key": "FFmpegExtractAudio",
            "preferredcodec": "mp3",
            "preferredquality": "192",
        }
    ],
    "quiet": True,
}


def get_audio(video_id: str):
    with youtube_dl.YoutubeDL(ydl_opts) as ydl:
        try:
            yinfo = ydl.extract_info(f"{URL}{video_id}")
        except youtube_dl.DownloadError:
            ret = None
        else:
            ret = (f"{yinfo['title']}.mp3", f"{yinfo['id']}.mp3")

    return ret


@app.get("/audio/{video_id}")
async def download_audio(video_id: str):
    ret = await run_in_threadpool(get_audio, video_id)

    if not ret:
        raise HTTPException(status_code=418, detail="Download error or invalid ID")

    title, filename = ret

    return FileResponse(filename, filename=title)

Test:

$ time curl -Ss -D - -o rickroll.mp3 localhost:8000/audio/dQw4w9WgXcQ
HTTP/1.1 200 OK
date: Thu, 22 Apr 2021 22:26:50 GMT
server: uvicorn
content-type: audio/mpeg
content-disposition: attachment; filename*=utf-8''Rick%20Astley%20-%20Never%20Gonna%20Give%20You%20Up%20%28Video%29.mp3
content-length: 5090733
last-modified: Mon, 13 Jan 2020 17:04:18 GMT
etag: e26e47edb1401e6e65e4c8eb221f3419


real    0m11.883s
user    0m0.047s
sys 0m0.057s
$ file rickroll.mp3 
rickroll.mp3: Audio file with ID3 version 2.4.0, contains:MPEG ADTS, layer III, v1, 192 kbps, 48 kHz, Stereo
Sign up to request clarification or add additional context in comments.

3 Comments

That looks very good, thank you very much! But I would like to check if it's actual a registered id. I'm going to try to get the title using YoutubeDL and if it success, the video id must be valid - how would I use a custom function?
Is there no way to check the id in a Path class? I'd like to use a more "fastapi-ish-way"
See this issue. You can either use dependencies or custom validators but you still need to to the final error check in the path operation function.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.