# Unit 6: Path Parameters

## Introduction

Path parameters are essentially parts of the URL path that are variables. You can use these variables to capture values specified at specific positions in the path. This capability is crucial for creating RESTful APIs where the URL path includes identifiers that point to specific resources or data. For instance, in a blog API, you might have a URL path like `/posts/{post_id}`, where `{post_id}` is a path parameter representing the ID of a post.

FastAPI simplifies the declaration and extraction of path parameters using Python type annotations, providing automatic data validation, serialization, and documentation features. This not only boosts development speed but also ensures that APIs are more reliable and easier to use.

***

## Path parameters

You can declare path *parameters* (or variables) with the same syntax used by Python format strings:

{% code lineNumbers="true" %}

```python
from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id):
    return {"item_id": item_id}
```

{% endcode %}

The value of the path parameter `item_id` will be passed to your function as the argument `item_id`.

{% hint style="info" %}
To run the above program, open a CMD/Terminal and execute the command below:

`uvicorn main:app --reload`
{% endhint %}

When you go to `http://127.0.0.1:8000/items/foo`, you will see a response of `{"item_id":"foo"}`.

### Path parameters with types

You can declare the type of a path parameter in the function, using standard Python type annotations:

<pre class="language-python" data-line-numbers><code class="lang-python">from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(<a data-footnote-ref href="#user-content-fn-1">item_id: int</a>):
    return {"item_id": item_id}
</code></pre>

In this case, `item_id` is declared to be an `int`. If you request an `item_id` that is not an integer value, such as `http://127.0.0.1:8000/items/foo`, you will receive a descriptive HTTP error as follows:

{% code overflow="wrap" %}

```json
{
    "detail": [
        {
            "type": "int_parsing",
            "loc": [
                "path",
                "item_id"
            ],
            "msg": "Input should be a valid integer, unable to parse string as an integer",
            "input": "foo",
            "url": "https://errors.pydantic.dev/2.5/v/int_parsing"
        }
    ]
}
```

{% endcode %}

{% hint style="info" %}
So, with the same Python type declaration, FastAPI gives you data validation. Notice that the error also clearly states exactly the point where the validation didn't pass. This is incredibly helpful while developing and debugging code that interacts with your API.
{% endhint %}

### Documentation

You can get an automatic, interactive, API documentation by visiting `http://127.0.0.1:8000/docs` in your browser, as shown in Fig. 1.

<figure><img src="https://4188609209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fhp4eambztJcKKLa7ysKG%2Fuploads%2FNPZUeWenlaHV6xNQE8Kd%2FScreenshot%202024-02-02%20at%209.08.02%20PM.png?alt=media&#x26;token=cd94a1a5-1738-4926-811c-d40706296f49" alt=""><figcaption><p>Fig. 1. An interactive API Docs generated by FastAPI</p></figcaption></figure>

### Order matters

When creating *path operations*, you can find situations where you have a fixed path. For example:

* `/users/me` is used to get data about the current user.
* `/users/{user_id}` is used to get data about a specific user by giving an `user_id`.

{% code lineNumbers="true" %}

```python
from fastapi import FastAPI

app = FastAPI()


@app.get("/users/me")
async def read_user_me():
    return {"user_id": "the current user"}


@app.get("/users/{user_id}")
async def read_user(user_id: str):
    return {"user_id": user_id}
```

{% endcode %}

### Predefined values

If your path operation requires a specific set of values for a path parameter, leveraging a standard Python `Enum` allows you to predefine these valid options.

Begin by creating a new Python file and naming it *ex01\_enum.py*. Then, proceed to add the following code to the file:

<pre class="language-python" data-line-numbers><code class="lang-python"><strong># Filename: ex01_enum.py
</strong><strong>from fastapi import FastAPI
</strong>from enum import Enum


class MyColor(str, Enum):
    red = "red"
    green = "green"
    blue = "blue"


app = FastAPI()


@app.get("/colors/{color}")
async def get_color(color: MyColor):
    if color is MyColor.red:
        return {"color": color, "message": "red"}
    elif color.value == "green":
        return {"color": color, "message": "green"}
    elif color is MyColor.blue:
        return {"color": color, "message": "blue"}
    else:
        return {"message": "color is not defined."}
</code></pre>

You can interact with the program mentioned above in two scenarios. First, visit `http://127.0.0.1:8000/colors/red`; this is a valid case, and you will receive a response as defined in the program. Second, visit `http://127.0.0.1:8000/colors/foo`; in this case, you will receive an error message, as follows:

```json
{
    "detail": [
        {
            "type": "enum",
            "loc": [
                "path",
                "color"
            ],
            "msg": "Input should be 'red', 'green' or 'blue'",
            "input": "foo",
            "ctx": {
                "expected": "'red', 'green' or 'blue'"
            }
        }
    ]
}
```

Since we have defined a list of possible values for the `color` variable, the API Docs generated by FastAPI can show them nicely, as shown in Fig. 2.

<figure><img src="https://4188609209-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fhp4eambztJcKKLa7ysKG%2Fuploads%2FTf41NLoSYzfWZNL5CUEz%2FScreenshot%202024-02-02%20at%209.31.00%20PM.png?alt=media&#x26;token=8289b9b5-9d52-411b-9a04-2489150c643b" alt=""><figcaption><p>Fig. 2. An interactive API Docs generated by FastAPI</p></figcaption></figure>

***

## Summary

Path parameters in FastAPI provide a method for defining dynamic and flexible routes in your API. By leveraging Python's type annotations, FastAPI offers a straightforward, efficient, and error-resistant way to utilize path parameters, enhancing both the developer experience and the functionality of the APIs built with this framework.

***

## Exercise

**1. Greeting with Name**

**Task:** Create a FastAPI endpoint `/hello/{name}` that takes a path parameter `name` and returns a JSON response greeting the user.

**Example:**

* Request: `GET /hello/Alice`
* Response:

```json
{
  "message": "Hello, Alice!"
}
```

***

**2. Square of a Number**

**Task:** Create an endpoint `/square/{number}` that takes an integer path parameter `number` and returns its square.

**Example:**

* Request: `GET /square/7`
* Response:

```json
{
  "number": 7,
  "square": 49
}
```

***

**3. Get Product by ID**

**Task:** Suppose you have a small in-memory product list:

```python
products = {
    1: {"name": "Laptop", "price": 1200},
    2: {"name": "Phone", "price": 800},
    3: {"name": "Tablet", "price": 500}
}
```

Create an endpoint `/products/{product_id}` that returns the product information if the `product_id` exists, otherwise return a JSON error message.

**Example:**

* Request: `GET /products/2`
* Response:

```json
{
  "id": 2,
  "name": "Phone",
  "price": 800
}
```

* Request: `GET /products/99`
* Response:

```json
{
  "error": "Product not found"
}
```

***

**4. File as a Path Parameter**

Let's assume you have a path such as `/files/{file_path}`, where `{file_path}` includes a directory path like `home/usr/myfile.txt`. Consequently, the URL for that file would be `/files/home/usr/myfile.txt`. However, OpenAPI does not support a method to declare a path parameter that contains a path within it. To address this issue, you can utilize one of the internal tools provided by Starlette. Implement a program to demonstrate how Starlette can be used in this scenario.

[^1]: Using Python Type Hints
