Skip to content

Redis Types

Automatic Type Conversion

AtomicRedisModel automatically converts all types to their Redis equivalents recursively, providing seamless atomic operations without changing your code. This conversion happens transparently:

  • listRedisList
  • dictRedisDict
  • strRedisStr
  • intRedisInt
  • bytesRedisBytes
  • datetimeRedisDatetime
  • BaseModelAtomicRedisModel (nested models)

This is completely seamless - since RedisList inherits from list, RedisDict inherits from dict, etc., your existing code continues to work exactly as before, but now with atomic Redis operations available.

class User(AtomicRedisModel):
    name: str           # Automatically becomes RedisStr
    age: int            # Automatically becomes RedisInt  
    tags: List[str]     # Automatically becomes RedisList[str]
    settings: Dict[str, str]  # Automatically becomes RedisDict[str, str]

# Use exactly like regular Python types
user = User(name="Alice", age=25, tags=["python"], settings={"theme": "dark"})

# But now atomic Redis operations are available
await user.tags.aappend("redis")  # Atomic list append
await user.settings.aupdate(language="en")  # Atomic dict update

Type System Overview

Rapyer's type system provides seamless integration between Python types and Redis operations through automatic conversion. Whether you use standard Python types or explicit Redis types, you get the same powerful atomic operations while maintaining full compatibility with your existing code.

RedisStr

Inherits from: str, RedisType
Redis Storage: Native string values
Use Case: Text data that benefits from atomic operations

from rapyer.types import RedisStr  # Recommended: TypeAlias 
# from rapyer.types import RedisStr     # Alternative: Direct class

class User(AtomicRedisModel):
    name: RedisStr = ""              # Flexible typing with union support
    status: RedisStr = "active"      # Accepts both str and RedisStr

Available Operations

Operation Method Description
save await user.name.save() Save field value to Redis
load await user.name.load() Load field value from Redis (returns value, doesn't update model)

All standard str methods are available (upper, lower, strip, etc.) but operate on the local copy.


RedisInt

Inherits from: int, RedisType
Redis Storage: Numeric values with atomic increment support
Use Case: Counters, IDs, scores, and numeric data requiring atomic updates

from rapyer.types import RedisInt  # Recommended: TypeAlias
# from rapyer.types import RedisInt     # Alternative: Direct class

class Counter(AtomicRedisModel):
    count: RedisInt = 0              # Flexible typing with union support
    score: RedisInt = 100            # Accepts both int and RedisInt

Available Operations

Operation Method Description
save await counter.count.save() Save field value to Redis
load await counter.count.load() Load field value from Redis (returns value, doesn't update model)
increase await counter.count.increase(5) Atomically increment by amount (default: 1)

Non-mutating Operation

The increase() method returns the new value from Redis but does not update the local instance. You need to reload the model or field to see the updated value locally.


RedisList

Inherits from: list[T], GenericRedisType[T]
Redis Storage: JSON arrays with full list operation support
Use Case: Collections, queues, and ordered data requiring atomic list operations

from rapyer.types import RedisList  # Recommended: TypeAlias
# from rapyer.types import RedisList     # Alternative: Direct class

class UserProfile(AtomicRedisModel):
    tags: RedisList[str] = Field(default_factory=list)    # Flexible typing with union support
    scores: RedisList[int] = Field(default_factory=list)  # Accepts both list[int] and RedisList[int]

Available Operations

Operation Method Description
save await user.tags.save() Save entire list to Redis
load await user.tags.load() Load entire list from Redis (returns value, doesn't update model)
append await user.tags.aappend("python") Atomically append single item
extend await user.tags.aextend(["redis", "async"]) Atomically append multiple items
insert await user.tags.ainsert(0, "first") Atomically insert at index
pop item = await user.tags.apop() Atomically remove and return item
clear await user.tags.aclear() Atomically clear entire list

All standard list methods are available (append, extend, pop, etc.) and work on both local copy and Redis when used with pipelines.


RedisDict

Inherits from: dict[str, T], GenericRedisType[T]
Redis Storage: JSON objects with full dictionary operation support
Use Case: Key-value mappings, settings, and structured data requiring atomic updates

from rapyer.types import RedisDict  # Recommended: TypeAlias
# from rapyer.types import RedisDict     # Alternative: Direct class

class UserSettings(AtomicRedisModel):
    preferences: RedisDict[str, str] = Field(default_factory=dict)  # Flexible typing with union support
    metadata: RedisDict[str, int] = Field(default_factory=dict)     # Accepts both dict and RedisDict

Available Operations

Operation Method Description
save await user.preferences.save() Save entire dict to Redis
load await user.preferences.load() Load entire dict from Redis (returns value, doesn't update model)
update await user.preferences.aupdate(theme="dark") Atomically update multiple keys
set item await user.preferences.aset_item("lang", "en") Atomically set single key-value
delete item await user.preferences.adel_item("old_key") Atomically delete key
pop val = await user.preferences.apop("key") Atomically remove and return value
pop item key, val = await user.preferences.apopitem() Atomically remove and return arbitrary key-value pair
clear await user.preferences.aclear() Atomically clear entire dict

All standard dict methods are available (update, pop, clear, etc.) and work on both local copy and Redis when used with pipelines.


RedisBytes

Inherits from: bytes, RedisType
Redis Storage: Base64 encoded strings (via pickle serialization)
Use Case: Binary data, images, or any bytes-like data

from rapyer.types import RedisBytes  # Recommended: TypeAlias
# from rapyer.types import RedisBytes     # Alternative: Direct class

class FileModel(AtomicRedisModel):
    content: RedisBytes = b""          # Flexible typing with union support
    thumbnail: RedisBytes = b""        # Accepts both bytes and RedisBytes

Available Operations

Operation Method Description
save await file.content.save() Save bytes to Redis
load await file.content.load() Load bytes from Redis (returns value, doesn't update model)

All standard bytes methods are available but operate on the local copy.


RedisDatetime

Inherits from: datetime, RedisType
Redis Storage: ISO format strings with automatic conversion
Use Case: Timestamps, dates, and time-based data

from rapyer.types import RedisDatetime  # Recommended: TypeAlias
# from rapyer.types import RedisDatetime     # Alternative: Direct class
from datetime import datetime

class Event(AtomicRedisModel):
    created_at: RedisDatetime = Field(default_factory=datetime.now)  # Flexible typing with union support
    updated_at: RedisDatetime                                        # Accepts both datetime and RedisDatetime

Available Operations

Operation Method Description
save await event.created_at.save() Save datetime to Redis
load await event.created_at.load() Load datetime from Redis (returns value, doesn't update model)

All standard datetime methods are available (strftime, replace, etc.) but operate on the local copy.


Nested Models (BaseModel → AtomicRedisModel)

All Pydantic BaseModel classes are automatically converted to AtomicRedisModel, enabling atomic operations on nested structures:

from pydantic import BaseModel

class Address(BaseModel):  # Automatically becomes AtomicRedisModel
    street: str
    city: str
    country: str

class Profile(BaseModel):  # Automatically becomes AtomicRedisModel  
    bio: str
    preferences: Dict[str, str] = Field(default_factory=dict)

class User(AtomicRedisModel):
    name: str
    address: Address  # Nested model with atomic operations
    profile: Profile  # Nested model with atomic operations

Available Operations on Nested Models

Nested BaseModel instances support these atomic Redis operations:

Operation Method Description
save await user.address.save() Save only this nested model to Redis (other parent fields unchanged)
load await user.address.load() Load nested model from Redis (returns value, doesn't update model)
aupdate await user.address.aupdate(street="New St") Atomically update specific fields in the nested model

Scoped Save Operation

When you call await user.address.save(), only the address nested model is saved to Redis. Other fields in the parent user model remain unchanged in Redis, even if they were modified locally.

# Atomic operations on nested models
await user.address.aupdate(street="123 New St", city="Boston")  # Update specific fields
await user.address.save()  # Save entire address model only

# All fields within nested models support their respective Redis operations
await user.profile.preferences.aupdate(theme="dark", lang="en")  # Dict operations available

Generic Type Support

For any other Python type, Rapyer provides automatic serialization via pickle:

from enum import Enum
from dataclasses import dataclass

class Status(Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"

@dataclass
class CustomData:
    value: str
    count: int

class MyModel(AtomicRedisModel):
    status: Status = Status.ACTIVE  # Pickled automatically
    data: CustomData  # Pickled automatically

Limited Operations

Custom types cannot perform atomic Redis operations like lists or dicts.


Best Practices

1. Use Appropriate Types

  • RedisInt for counters and numeric operations
  • RedisList for ordered collections needing atomic operations
  • RedisDict for key-value data requiring atomic updates
  • Standard types for simple data that doesn't need atomic operations

2. Atomic vs Local Operations

  • Use a-prefixed methods (aappend, aupdate) for immediate Redis operations
  • Use standard methods when working within pipelines or transactions
  • Remember that atomic operations may not update the local instance

3. Performance Considerations

  • Atomic operations create individual Redis commands
  • Use pipelines for bulk operations
  • Consider the trade-off between atomicity and performance

4. Legacy Note

Legacy Direct Redis Classes

Direct Redis classes (RedisDict, RedisList, etc.) still work but TypeAlias types are now recommended for better MyPy compatibility and IDE support.