This tutorial will walk you through installing Redis OM, creating your first model, and using it to save and validate data.
## Prerequisites
Redis OM requires Python version 3.9 or above and a Redis instance to connect to.
## Python
Make sure you are running **Python version 3.9 or higher**:
```
python --version
Python 3.9.0
```
If you don't have Python installed, you can download it from [Python.org](https://www.python.org/downloads/), use [Pyenv](https://github.com/pyenv/pyenv), or install Python with your operating system's package manager.
## Redis
Redis OM saves data in Redis, so you will need Redis installed and running to complete this tutorial.
### Downloading Redis
The latest version of Redis is available from [Redis.io](https://redis.io/). You can also install Redis with your operating system's package manager.
**NOTE:** This tutorial will guide you through starting Redis locally, but the instructions will also work if Redis is running on a remote server.
### Installing Redis On Windows
Redis doesn't run directly on Windows, but you can use Windows Subsystem for Linux (WSL) to run Redis. See [our video on YouTube](https://youtu.be/_nFwPTHOMIY) for a walk-through.
Windows users can also use Docker. See the next section on running Redis with Docker for more information.
Instead of installing Redis manually or with a package manager, you can run Redis with Docker. The official Redis Docker image is hosted on [Docker Hub](https://hub.docker.com/_/redis).
**TIP:** If you plan on using Docker, we recommend the [redismod](https://hub.docker.com/r/redislabs/redismod) image because it includes the RediSearch and RedisJSON modules.
## Recommended: RediSearch and RedisJSON
Redis OM relies on the [RediSearch][redisearch-url] and [RedisJSON][redis-json-url] Redis modules to support [rich queries](querying.md) and [embedded models](embedded_models.md).
You don't need these Redis modules to use Redis OM's data modeling, validation, and persistence features, but we recommend them to get the most out of Redis OM.
The easiest way to run these Redis modules during local development is to use the [redismod](https://hub.docker.com/r/redislabs/redismod) Docker image.
You can quickly start Redis with the redismod Docker image by running the following command:
We're almost ready to create a Redis OM model! But first, we need to make sure that Redis OM knows how to connect to Redis.
By default, Redis OM tries to connect to Redis on your localhost at port 6379. Most local install methods will result in Redis running at this location, in which case you don't need to do anything special.
However, if you configured Redis to run on a different port, or if you're using a remote Redis server, you'll need to set the `REDIS_URL` environment variable.
The `REDIS_URL` environment variable follows the redis-py URL format:
For more details about how to connect to Redis with Redis OM, see the [connections documentation](connections.md).
### Redis Cluster Support
Redis OM supports connecting to Redis Cluster, but this preview release does not support doing so with the `REDIS_URL` environment variable. However, you can connect by manually creating a connection object.
See the [connections documentation](connections.md) for examples of connecting to Redis Cluster.
In this tutorial, we'll create a `Customer` model that validates and saves data. Let's start with a basic definition of the model. We'll add features as we go along.
1. Our `Customer` model extends the `HashModel` class. This means that it will be saved to Redis as a hash. The other model class that Redis OM provides is `JsonModel`, which we'll discuss later.
2. We've specified the model's fields using Python type annotations.
This means that you can use Pydantic field validations with your Redis OM models, which we'll cover later, when we talk about validation. But this also means you can use Redis OM models anywhere you would use a Pydantic model, like in your FastAPI applications. 🤯
### Type Annotations
The type annotations you add to your model fields are used for a few purposes:
* Validating data with Pydantic validators
* Serializing data Redis
* Deserializing data from Redis
We'll see examples of these throughout the course of this tutorial.
An important detail about the `HashModel` class is that it does not support `list`, `set`, or mapping (like `dict`) types. This is because Redis hashes cannot contain lists, sets, or other hashes.
If you want to model fields with a list, set, or mapping type, or another model, you'll need to use the `JsonModel` class, which can support these types, as well as embedded models.
## Creating Models
Let's see what creating a model object looks like:
The default ID generation function creates [ULIDs](https://github.com/ulid/spec), though you can change the function that generates the primary key for models if you'd like to use a different kind of primary key.
Redis OM uses [Pydantic][pydantic-url] to validate data based on the type annotations you assign to fields in a model class.
This validation ensures that fields like `first_name`, which the `Customer` model marked as a `str`, are always strings. **But every Redis OM model is also a Pydantic model**, so you can use Pydantic validators like `EmailStr`, `Pattern`, and many more for complex validations!
For example, we defined the `join_date` for our `Customer` model earlier as a `datetime.date`. So, if we try to create a model with a `join_date` that isn't a date, we'll get a validation error.
Let's try it now:
```python
import datetime
from typing import Optional
from redis_om.model import HashModel
from pydantic import ValidationError
class Customer(HashModel):
first_name: str
last_name: str
email: str
join_date: datetime.date
age: int
bio: Optional[str] = "Super dope"
try:
Customer(
first_name="Andrew",
last_name="Brookins",
email="a@example.com",
join_date="not a date!", # <-Theproblemline!
age=38
)
except ValidationError as e:
print(e)
"""
pydantic.error_wrappers.ValidationError: 1 validation error for Customer
join_date
invalid date format (type=value_error.date)
"""
```
### Models Coerce Values By Default
You might wonder what qualifies as a "date" in our last validation example. By default, Redis OM will try to coerce input values to the correct type. That means we can pass a date string for `join_date` instead of a `date` object:
#> datetime.date # The model parsed the string automatically!
```
This ability to combine parsing (in this case, a YYYY-MM-DD date string) with validation can save you a lot of work.
However, you can turn off coercion -- check the next section on using strict validation.
### Strict Validation
You can turn on strict validation to reject values for a field unless they match the exact type of the model's type annotations.
You do this by changing a field's type annotation to use one of the ["strict" types provided by Pydantic](https://pydantic-docs.helpmanual.io/usage/types/#strict-types).
Redis OM supports all of Pydantic's strict types: `StrictStr`, `StrictBytes`, `StrictInt`, `StrictFloat`, and `StrictBool`.
If we wanted to make sure that the `age` field only accepts integers and doesn't try to parse a string containing an integer, like "1", we'd use the `StrictInt` class.
```python
import datetime
from typing import Optional
from pydantic import StrictInt, ValidationError
from redis_om.model import HashModel
class Customer(HashModel):
first_name: str
last_name: str
email: str
join_date: datetime.date
age: StrictInt # <-Insteadofint,weuseStrictInt
bio: Optional[str]
# Now if we use a string instead of an integer for `age`,
pydantic.error_wrappers.ValidationError: 1 validation error for Customer
join_date
Value must be a datetime.date object (type=value_error)
"""
```
Pydantic doesn't include a `StrictDate` class, but we can create our own. In this example, we create a `StrictDate` type that we'll use to validate that `join_date` is a `datetime.date` object.
You can view the data stored in Redis for any Redis OM model.
First, get the key of a model instance you want to inspect. The `key()` method will give you the exact Redis key used to store the model.
**NOTE:** The naming of this method may be confusing. This is not the primary key, but is instead the Redis key for this model. For this reason, the method name may change.
In this example, we're looking at the key created for the `Customer` model we've been building:
```python
import datetime
from typing import Optional
from redis_om.model import HashModel
class Customer(HashModel):
first_name: str
last_name: str
email: str
join_date: datetime.date
age: int
bio: Optional[str] = "Super dope"
andrew = Customer(
first_name="Andrew",
last_name="Brookins",
email="andrew.brookins@example.com",
join_date=datetime.date.today(),
age=38)
andrew.save()
andrew.key()
#> 'mymodel.Customer:01FKGX1DFEV9Z2XKF59WQ6DC9T'
```
With the model's Redis key, you can start `redis-cli` and inspect the data stored under that key. Here, we run `JSON.GET` command with `redis-cli` using the running "redis" container that this project's Docker Compose file defines:
If you have the primary key of a model, you can call the `get()` method on the model class to get the model's data.
```python
import datetime
from typing import Optional
from redis_om.model import HashModel
class Customer(HashModel):
first_name: str
last_name: str
email: str
join_date: datetime.date
age: int
bio: Optional[str] = "Super dope"
andrew = Customer(
first_name="Andrew",
last_name="Brookins",
email="andrew.brookins@example.com",
join_date=datetime.date.today(),
age=38)
andrew.save()
assert Customer.get(andrew.pk) == andrew
```
## Querying for Models With Expressions
Redis OM comes with a rich query language that allows you to query Redis with Python expressions.
To show how this works, we'll make a small change to the `Customer` model we defined earlier. We'll add `Field(index=True)` to tell Redis OM that we want to index the `last_name` and `age` fields:
```python
import datetime
from typing import Optional
from pydantic import EmailStr
from redis_om.model import (
Field,
HashModel,
Migrator
)
class Customer(HashModel):
first_name: str
last_name: str = Field(index=True)
email: EmailStr
join_date: datetime.date
age: int = Field(index=True)
bio: Optional[str]
# Now, if we use this model with a Redis deployment that has the
# RediSearch module installed, we can run queries like the following.
# Before running queries, we need to run migrations to set up the
# indexes that Redis OM will use. You can also use the `migrate`
# CLI tool for this!
Migrator().run()
# Find all customers with the last name "Brookins"