Path Parameters

You can validate Path parameters in your route by adding standard type hints to the function signature. flask_typed_routes ensures that parameters are correctly converted and validated based on their type annotations.

Warning

If no type hint is provided, the Path parameters are not validated.

Basic Usage

import typing as t

import flask
import flask_typed_routes as ftr

app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)


@app.route('/items/<category_id>/<lang>/')
def read_items(category_id: int, lang: t.Literal['es', 'en']):
    data = {'category_id': category_id, 'lang': lang}
    return flask.jsonify(data)

How It Works:

  • category_id is automatically converted to an integer.
  • lang is restricted to the values 'es' or 'en'.

Valid request GET http://127.0.0.1:5000/items/12/es

{
  "category_id": 12,
  "lang": "es"
}

Bad request: (wrong type for category_id) GET http://127.0.0.1:5000/items/abc/es

{
  "errors": [
    {
      "input": "abc",
      "loc": [
        "path",
        "category_id"
      ],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "type": "int_parsing",
      "url": "https://errors.pydantic.dev/2.9/v/int_parsing"
    }
  ]
}

Custom Validations

You can apply additional validation using Pydantic's custom types with constraints, or define your own custom data types

import typing as t

import annotated_types as at
import flask
import flask_typed_routes as ftr

app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)


@app.route('/items/<category_id>/')
def read_items(category_id: t.Annotated[int, at.Ge(1), at.Le(100)]):
    data = {'category_id': category_id}
    return flask.jsonify(data)

Alternatively, you can use the Path field. This field is an extension of Pydantic's field, offering powerful validation capabilities. This flexibility allows you to tailor path parameter validation to your application's specific needs.

import typing as t

import flask
import flask_typed_routes as ftr

app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)


@app.route('/items/<category_id>/')
def read_items(category_id: t.Annotated[int, ftr.Path(ge=1, le=100)]):
    data = {'category_id': category_id}
    return flask.jsonify(data)

Validation Rules:

  • category_id must be an integer between 1 and 100.

Aliasing

Warning

Aliases defined in Path type hints are ignored to maintain consistency with the Flask route parameter names.

Parsing Arrays

Path parameters can be parsed as Arrays using set, tuple, or list. The library follows the simple style of OpenAPI parameter serialization for arrays, using commas as separators.

Tip

You can use the set type hint to validate that the values are unique.

import flask
import flask_typed_routes as ftr

app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)


@app.get('/users/<user_ids>/')
def get_users(user_ids: list[int]):
    return flask.jsonify({'user_ids': user_ids})

Example request: GET http://127.0.0.1:5000/users/1,2,3/

{
  "user_ids": [1, 2, 3]
}

Parsing Objects

Path parameters can be parsed as Objects using dictionaries or Pydantic models. The library follows the simple style of OpenAPI parameter serialization for objects.

Using a Dictionary:

import flask
import flask_typed_routes as ftr

app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)


@app.get('/users/<user_info>/')
def get_users(user_info: dict[str, str]):
    return flask.jsonify({'user_info': user_info})

Example request: GET http://127.0.0.1:5000/users/role,admin,first_name,Alex

{
  "user_info": {
    "role": "admin",
    "first_name": "Alex"
  }
}

Using a Pydantic model:

import typing as t

import flask
import flask_typed_routes as ftr
import pydantic

app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)


class User(pydantic.BaseModel):
    model_config = pydantic.ConfigDict(extra="forbid")  # Prevents extra fields
    name: str
    age: int = 100


@app.get('/users/<user_info>/')
def get_users(user_info: t.Annotated[User, ftr.Path()]):
    return flask.jsonify({'user_info': user_info.model_dump()})

Example request: GET http://127.0.0.1:5000/users/name,Alex,age,25/

{
  "user_info": {
    "age": 25,
    "name": "Alex"
  }
}

Handling Extra Fields

The extra="forbid" configuration prevents additional fields:

GET http://127.0.0.1:5000/users/name,Alex,age,25,role,Admin/

{
  "errors": [
    {
      "input": "Admin",
      "loc": [
        "path",
        "user_info",
        "role"
      ],
      "msg": "Extra inputs are not permitted",
      "type": "extra_forbidden",
      "url": "https://errors.pydantic.dev/2.10/v/extra_forbidden"
    }
  ]
}

Handling incomplete key-value pairs

If the number of key-value pairs is not even, the library uses the last key as a key with an empty value, for example: GET 127.0.0.1:5000/users/name,

{
  "user_info": {
    "age": 100,
    "name": ""
  }
}

Handling explode parameter

With explode=True, keys and values are separated by = in the URL.

import typing as t

import flask
import flask_typed_routes as ftr

app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)

@app.get('/users/<user_info>/')
def get_users(user_info: t.Annotated[dict, ftr.Path(explode=True)]):
    return flask.jsonify({'user_info': user_info})

Example Request GET http://127.0.0.1:5000/users/name=Alex,age=25,role=Admin

{
  "user_info": {
    "name": "Alex",
    "age": "25",
    "role": "Admin"
  }
}

Incorrect Parsing

If you pass explode=True, the library misinterprets this structure:

❌ GET http://127.0.0.1:5000/users/name,Alex,age,25,role,Admin/

The result is a dictionary with empty values because the library interprets the comma as a separator.

{
  "user_info": {
    "25": "",
    "Admin": "",
    "Alex": "",
    "age": "",
    "name": "",
    "role": ""
  }
}