Skip to content

Commit f5f34ff

Browse files
committed
🚧 Working on core documentation
1 parent d6a31c3 commit f5f34ff

14 files changed

+292
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
title: Path Parameters
3+
wip: true
4+
---
5+
6+
## Introduction
7+
8+
This section will guide you through creating robust and well-defined API endpoints using Flama.
9+
We'll start with fundamental concepts like handling parameters and request data, move into data validation,
10+
and then cover more advanced features like pagination and modularizing your application.
11+
12+
## Path Parameters
13+
14+
Path parameters are variable segments in the URL path. Flama allows you to define these in your route's path string and access them as arguments in your view function.
15+
16+
In Flama, you define path parameters using curly braces {} in the path string of a route. The name inside the braces must correspond to a parameter in your view function's signature.
17+
18+
### Defining a route with path parameters
19+
20+
You can use either the `@app.route()` decorator or the `app.add_route()` method:
21+
22+
```python
23+
import flama
24+
from flama import Flama
25+
26+
app = Flama()
27+
28+
@app.route("/items/{item_id}")
29+
async def read_item(item_id: int):
30+
return {"item_id": item_id}
31+
32+
# Alternatively, using app.add_route:
33+
async def read_another_item(item_id: int, name: str):
34+
return {"item_id": item_id, "name": name}
35+
app.add_route("/other_items/{item_id}/by_name/{name}", read_another_item)
36+
37+
if __name__ == "__main__":
38+
flama.run(flama_app=app)
39+
```
40+
41+
In the example above:
42+
43+
- `/items/{item_id}`: The path contains one parameter, item_id.
44+
- `async def read_item(item_id: int)`: The view function `read_item` expects an argument named item_id. Flama will automatically validate that the path segment corresponding to item_id can be converted to an int. If the conversion fails (e.g., `/items/foo`), Flama will return a `400 Bad Request error`.
45+
46+
Flama uses type hints for automatic data validation and conversion of path parameters.
47+
The `ValidatePathParamsComponent` is responsible for this validation.
48+
The supported primitive types for path parameters are defined in `flama.types.http.PARAMETERS_TYPES`.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
title: Query Parameters
3+
wip: true
4+
---
5+
6+
Query parameters are optional key-value pairs that appear at the end of a URL after a question mark `?`
7+
and are separated by ampersands `&`.
8+
In Flama, query parameters are defined as parameters in your view function that are not part of the path.
9+
10+
## Defining a route with query parameters
11+
12+
Let's proceed with a simple example to show what we are discussing:
13+
14+
```python
15+
import typing as t
16+
import flama
17+
from flama import Flama
18+
19+
app = Flama()
20+
21+
@app.route("/users/")
22+
async def list_users(skip: int = 0, limit: int = 10, name: t.Optional[str] = None):
23+
# In a real application, you would fetch users from a database
24+
# using skip, limit, and name for filtering and pagination.
25+
return {"skip": skip, "limit": limit, "name": name, "users": []}
26+
27+
if __name__ == "__main__":
28+
flama.run(flama_app=app)
29+
```
30+
31+
In this example:
32+
33+
- `skip: int = 0`: `skip` is a query parameter of type int with a default value of `0`.
34+
- `limit: int = 10`: `limit` is a query parameter of type int with a default value of `10`.
35+
- `name: t.Optional[str] = None`: `name` is an optional query parameter of type str.
36+
37+
If you call `/users/`, it will use the default values: `skip=0, limit=10`.
38+
If you call `/users/?skip=5&limit=20&name=Alice`, these values will be passed to the function.
39+
40+
Flama automatically validates query parameters based on their type hints and default values.
41+
The `ValidateQueryParamsComponent` handles this.
42+
If a query parameter cannot be converted to its annotated type, Flama returns a `400 Bad Request error`.
43+
Basic types are supported similarly to path parameters.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
title: Validation with Basic Types
3+
wip: true
4+
---
5+
6+
As seen in the Path Parameters and Query Parameters sections, Flama provides automatic validation for basic Python types
7+
used in your view function signatures for path and query parameters.
8+
9+
Supported basic types generally include:
10+
11+
- int
12+
- float
13+
- str
14+
- bool
15+
- uuid.UUID
16+
- datetime.date, datetime.datetime, datetime.time
17+
18+
These are listed in flama.types.http.PARAMETERS_TYPES.
19+
20+
## Example
21+
22+
```python
23+
import typing as t
24+
import uuid
25+
import datetime
26+
import flama
27+
from flama import Flama
28+
29+
app = Flama()
30+
31+
@app.route("/process/{item_uuid}")
32+
async def process_data(
33+
item_uuid: uuid.UUID,
34+
count: int,
35+
notify: bool = False,
36+
scheduled_date: t.Optional[datetime.date] = None
37+
):
38+
return {
39+
"item_uuid": item_uuid,
40+
"count": count,
41+
"notify": notify,
42+
"scheduled_date": scheduled_date,
43+
}
44+
45+
if __name__ == "__main__":
46+
flama.run(flama_app=app)
47+
```
48+
49+
If you make a request like `/process/not-a-uuid?count=not-an-int`, Flama will respond with a `400 Bad Request` error because:
50+
51+
- `"not-a-uuid"` cannot be converted to a `uuid.UUID`.
52+
- `"not-an-int"` cannot be converted to an `int`.
53+
54+
The validation components (`ValidatePathParamsComponent`, `ValidateQueryParamsComponent`, and `PrimitiveParamComponent`) handle
55+
the conversion and validation for these basic types.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
title: Validation with Schema Types
3+
wip: true
4+
---
5+
6+
For more complex data structures, especially in request bodies or responses, Flama leverages its schema system.
7+
You can use `typing.Annotated` along with `flama.schemas.SchemaMetadata` to explicitly define how data should be validated and serialized.
8+
This is particularly useful when you want to use Flama's generic `SchemaType` for type hinting but still provide specific schema
9+
information for OpenAPI documentation and validation.
10+
11+
The `flama.schemas` module provides the core structures like `Schema`, `Field`, and `SchemaMetadata`.
12+
Besides, `flama.schemas.adapter` allows plugging in different schema libraries.
13+
14+
## Example using typing.Annotated and SchemaMetadata
15+
16+
Let's assume you have a Pydantic model `Puppy`:
17+
18+
```python
19+
import typing as t
20+
import flama
21+
from flama import Flama, schemas
22+
import pydantic
23+
24+
app = Flama()
25+
26+
class Puppy(pydantic.BaseModel):
27+
id: int
28+
name: str
29+
age: int
30+
31+
@pydantic.field_validator("age")
32+
def minimum_age_validation(cls, v):
33+
if v < 0:
34+
raise ValueError("Age must be positive")
35+
return v
36+
37+
app.schema.register_schema("Puppy", Puppy) # Important for OpenAPI
38+
39+
@app.route("/puppies/", methods=["POST"])
40+
async def create_puppy(
41+
puppy_data: t.Annotated[schemas.SchemaType, schemas.SchemaMetadata(Puppy)]
42+
) -> t.Annotated[schemas.SchemaType, schemas.SchemaMetadata(Puppy)]:
43+
"""
44+
Create a new puppy.
45+
"""
46+
# puppy_data is a dict here, validated against the Puppy schema.
47+
# You might convert it to a Puppy instance if needed, or work with the dict.
48+
# For this example, let's assume we return the validated dict.
49+
# In a real app, you might do: validated_puppy = Puppy(**puppy_data)
50+
return puppy_data
51+
52+
@app.route("/puppies_list/", methods=["GET"])
53+
async def list_puppies() -> t.Annotated[list[schemas.SchemaType], schemas.SchemaMetadata(Puppy)]:
54+
"""
55+
List puppies.
56+
"""
57+
# This would come from a database
58+
example_puppies_data = [
59+
{"id": 1, "name": "Buddy", "age": 2},
60+
{"id": 2, "name": "Lucy", "age": 3}
61+
]
62+
return example_puppies_data
63+
64+
if __name__ == "__main__":
65+
flama.run(flama_app=app)
66+
```
67+
68+
In this example:
69+
70+
- `t.Annotated[schemas.SchemaType, schemas.SchemaMetadata(Puppy)]`:
71+
- `schemas.SchemaType` is a generic type hint (often dict[str, t.Any]) indicating that the function will receive or return data that conforms to a schema.
72+
- `schemas.SchemaMetadata(Puppy)` provides the actual `Puppy` schema (which could be a Pydantic model, Marshmallow schema, etc.) to Flama for validation, serialization, and OpenAPI schema generation.
73+
- For create_puppy:
74+
- The input puppy_data is expected to be a JSON payload. Flama validates it against the Puppy schema. If valid, puppy_data will be a dictionary.
75+
- The return value is also annotated, so Flama will ensure the returned dictionary conforms to Puppy and serialize it as JSON.
76+
- For list_puppies:
77+
- The return type t.Annotated[list[schemas.SchemaType], schemas.SchemaMetadata(Puppy)] indicates that the endpoint will return a list of objects, each conforming to the Puppy schema.
78+
79+
This approach allows for precise schema definition while keeping the function signatures relatively clean, especially when dealing
80+
with data that might not always be directly instantiated as a Pydantic model within the handler.
81+
Flama's validation layer, particularly CompositeParamComponent for request bodies and schema processing for responses,
82+
handles these annotations.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
title: Request payload
3+
wip: true
4+
---
5+
6+
When you need to send data from a client to your API (e.g., for creating or updating resources),
7+
you typically send it as a request payload (or request body). Flama uses type hints with schema definitions
8+
(commonly `Pydantic` models or other supported schema libraries) to define the expected structure and validate the incoming request data.
9+
10+
## Defining an endpoint that expects a request payload
11+
12+
First, you need a schema to define the structure of your payload.
13+
Flama integrates with schema libraries like `Pydantic`, `Marshmallow`, and `Typesystem` through its schema adapter system.
14+
Let's see how to use a Pydantic model.
15+
16+
```python
17+
import typing as t
18+
19+
import flama
20+
import pydantic
21+
from flama import Flama, schemas
22+
23+
app = Flama()
24+
25+
26+
# Define your data model using Pydantic
27+
class Item(pydantic.BaseModel):
28+
name: str
29+
description: str | None = None
30+
price: float
31+
is_offer: bool | None = None
32+
33+
34+
# Register the schema with Flama (optional but good practice for OpenAPI)
35+
app.schema.register_schema("Item", Item)
36+
37+
38+
@app.route("/items/", methods=["POST"])
39+
async def create_item(
40+
item: t.Annotated[schemas.SchemaType, schemas.SchemaMetadata(Item)],
41+
) -> t.Annotated[schemas.SchemaType, schemas.SchemaMetadata(Item)]:
42+
# 'item' will be a dictionary compatible with the `Item` Pydantic model,
43+
# validated from the request body.
44+
# In a real app, you'd save the item to a database.
45+
return item
46+
47+
48+
if __name__ == "__main__":
49+
flama.run(flama_app=app)
50+
51+
```
52+
53+
In this example:
54+
55+
- `class Item(pydantic.BaseModel): ...`: Defines the structure of the expected JSON payload.
56+
- `async def create_item(item: t.Annotated[schemas.SchemaType, schemas.SchemaMetadata(Item)])`: The item parameter is type-hinted with the `Item` model. Flama will automatically:
57+
- Read the request body (e.g., JSON).
58+
- Validate the data against the Item schema.
59+
- If validation fails (e.g., missing name or price, or price is not a float), Flama returns a `422 Unprocessable Entity`` error with details about the validation errors.
60+
- Convert the validated data into an Item instance and pass it to your function.
61+
- `-> t.Annotated[schemas.SchemaType, schemas.SchemaMetadata(Item)]`: The return type hint indicates that the response should also conform to the Item schema. Flama will serialize the returned Item instance into a JSON response.
62+
63+
Flama's `RequestDataComponent` handles the initial parsing of the request body based on `Content-Type` (e.g., `application/json`).
64+
Then, `ValidateRequestDataComponent` and `CompositeParamComponent` use the schema to validate and convert the data.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)