from rapyer import AtomicRedisModel
Rapyer Functions¶
This page documents the global functions available in the rapyer package for working with Redis models.
ainsert()¶
Performs bulk insertion of multiple Redis models in a single transaction, supporting models of different types.
Description¶
The ainsert() function provides a global way to insert multiple Redis models in a single atomic transaction. Unlike the class-specific ainsert() method, this global function can handle models of different types in a single operation.
Example¶
import asyncio
import rapyer
from rapyer import AtomicRedisModel
class User(AtomicRedisModel):
name: str
age: int
email: str
class Product(AtomicRedisModel):
name: str
price: float
in_stock: bool
class Order(AtomicRedisModel):
user_id: str
product_id: str
quantity: int
async def main():
# Create instances of different model types
user = User(name="Alice", age=30, email="alice@example.com")
product1 = Product(name="Laptop", price=999.99, in_stock=True)
product2 = Product(name="Mouse", price=29.99, in_stock=True)
order = Order(user_id=user.key, product_id=product1.key, quantity=1)
# Insert all models in a single transaction
await rapyer.ainsert(user, product1, product2, order)
print("All models inserted atomically")
# Verify all models were saved
saved_user = await User.aget(user.key)
saved_product1 = await Product.aget(product1.key)
saved_order = await Order.aget(order.key)
print(f"User: {saved_user.name}")
print(f"Product: {saved_product1.name}")
print(f"Order quantity: {saved_order.quantity}")
if __name__ == "__main__":
asyncio.run(main())
Performance Benefits¶
The global ainsert() function is particularly useful when:
- You need to insert multiple models of different types
- You want atomic guarantees across different model types
- You're initializing related data that spans multiple model classes
- You need optimal performance for bulk insertions
Comparison with Class-Specific ainsert()¶
async def comparison_example():
users = [User(name=f"User{i}", age=20+i, email=f"user{i}@example.com") for i in range(3)]
products = [Product(name=f"Product{i}", price=10.0*i, in_stock=True) for i in range(3)]
# ❌ Less efficient: Multiple transactions
await User.ainsert(*users)
await Product.ainsert(*products)
# ✅ More efficient: Single transaction for all models
await rapyer.ainsert(*users, *products)
aget()¶
Retrieves a model instance from Redis by its key, automatically determining the correct model class.
Parameters¶
- redis_key (
str): The Redis key of the model instance to retrieve
Returns¶
- AtomicRedisModel: The model instance corresponding to the Redis key
Raises¶
- KeyError: If the model class cannot be determined from the key
- ValueError: If the key format is invalid
- CantSerializeRedisValueError: If the value in Redis cannot be deserialized (Corruption or missing resources)
Description¶
The aget() function provides a global way to retrieve any model instance from Redis without knowing its specific class beforehand. It works by:
- Extracting the class name from the Redis key format (
ClassName:instance_id) - Looking up the appropriate model class from the registered Redis models
- Calling the class-specific
aget()method to retrieve and deserialize the instance
This is particularly useful when you have multiple model types and want a unified retrieval mechanism, or when working with keys of unknown model types.
Example¶
import asyncio
import rapyer
from rapyer import AtomicRedisModel
class User(AtomicRedisModel):
name: str
age: int
email: str
class Product(AtomicRedisModel):
name: str
price: float
in_stock: bool
async def main():
# Create and save different model types
user = User(name="Alice", age=30, email="alice@example.com")
product = Product(name="Laptop", price=999.99, in_stock=True)
await user.asave()
await product.asave()
# Retrieve using global aget function
retrieved_user = await rapyer.aget(user.key)
retrieved_product = await rapyer.aget(product.key)
print(f"User: {retrieved_user.name}, Age: {retrieved_user.age}")
print(f"Product: {retrieved_product.name}, Price: {retrieved_product.price}")
# The function automatically returns the correct model type
print(f"User type: {type(retrieved_user).__name__}")
print(f"Product type: {type(retrieved_product).__name__}")
if __name__ == "__main__":
asyncio.run(main())
alock_from_key()¶
async def alock_from_key(
key: str, action: str = "default", save_at_end: bool = False
) -> AbstractAsyncContextManager[AtomicRedisModel | None]
Creates a lock context manager for any Redis model by its key, without needing to know the specific model class.
Parameters¶
- key (
str): The Redis key of the model to lock - action (
str, optional): The operation name for the lock. Default is "default". Different operation names allow concurrent execution on the same model instance - save_at_end (
bool, optional): If True, automatically saves the model when the context exits. Default is False
Description¶
The global alock_from_key() function provides a way to create locks on Redis models without knowing their specific class. This is particularly useful when:
- Working with keys from different or unknown model types
- Building generic utilities that operate on any model
- The model class is not imported in the current module
- You need graceful handling of non-existent keys
Unlike the class-specific Model.alock_from_key() method, this global function:
- Automatically discovers the correct model type from the key
- Returns None if the key doesn't exist (instead of raising KeyNotFound)
- Works with any AtomicRedisModel subclass
Example¶
import asyncio
import rapyer
from rapyer import AtomicRedisModel, alock_from_key
class User(AtomicRedisModel):
name: str
balance: int = 0
class Product(AtomicRedisModel):
name: str
stock: int = 0
async def generic_update(key: str, operation: str):
# Works with any model type
async with alock_from_key(key, operation, save_at_end=True) as model:
if model is None:
print(f"Key not found: {key}")
return
# Check what type of model we're working with
if hasattr(model, 'balance'):
model.balance += 100
print(f"Updated balance for {model.name}")
elif hasattr(model, 'stock'):
model.stock -= 1
print(f"Reduced stock for {model.name}")
# Model is automatically saved when context exits due to save_at_end=True
async def cross_model_transaction(user_key: str, product_key: str):
# Lock multiple models of different types
async with alock_from_key(user_key, "purchase") as user:
async with alock_from_key(product_key, "purchase") as product:
if not user or not product:
raise ValueError("User or product not found")
# Both models are locked and can be modified atomically
if user.balance >= 50 and product.stock > 0:
user.balance -= 50
product.stock -= 1
print(f"{user.name} purchased {product.name}")
else:
print("Insufficient balance or out of stock")
# Changes are saved when contexts exit
async def main():
# Create and save models
user = User(name="Alice", balance=100)
product = Product(name="Book", stock=10)
await user.asave()
await product.asave()
# Use generic update function with different model types
await generic_update(user.key, "credit")
await generic_update(product.key, "restock")
# Perform cross-model transaction
await cross_model_transaction(user.key, product.key)
# Try with non-existent key
await generic_update("NonExistentModel:12345", "test")
if __name__ == "__main__":
asyncio.run(main())
Comparison with Class Method¶
| Feature | rapyer.alock_from_key() (Global) |
Model.alock_from_key() (Class Method) |
|---|---|---|
| Model type knowledge | Not required | Required |
| Non-existent keys | Returns None | Raises KeyNotFound |
| Type hints | Generic AtomicRedisModel | Specific model type |
| Use case | Generic utilities, unknown types | Type-safe operations |
find_redis_models()¶
Returns a list of all registered Redis model classes.
Parameters¶
None
Returns¶
- list[type[AtomicRedisModel]]: A list containing all model classes that inherit from
AtomicRedisModel
Description¶
The find_redis_models() function provides access to all Redis model classes that have been defined and registered in the application.
afind()¶
Retrieves multiple models of different types by their keys in a single bulk operation.
Parameters¶
- redis_keys (
str): Variable number of Redis keys to retrieve (e.g.,"UserModel:123","OrderModel:456")
Returns¶
- list[AtomicRedisModel]: A list of model instances in the same order as the input keys
Raises¶
- KeyNotFound: If any of the specified keys is missing in Redis
- RapyerModelDoesntExist: If a key refers to an unregistered model class (the class name prefix doesn't match any known model)
Description¶
The global rapyer.afind() function provides a way to retrieve multiple models of heterogeneous types in a single bulk operation. Unlike the class-specific Model.afind() method (which retrieves all instances of one model type), this function:
- Accepts explicit Redis keys as arguments
- Supports fetching different model types in one call
- Automatically refreshes TTL for models with
refresh_ttlenabled - Raises errors for missing keys or unknown model types
This is particularly useful when you need to fetch related models of different types in a single efficient operation.
Example¶
import asyncio
import rapyer
from rapyer import AtomicRedisModel
class User(AtomicRedisModel):
name: str
email: str
class Order(AtomicRedisModel):
user_id: str
total: float
class Product(AtomicRedisModel):
name: str
price: float
async def main():
# Create and save models of different types
user = User(name="Alice", email="alice@example.com")
order = Order(user_id=user.key, total=150.00)
product = Product(name="Laptop", price=999.99)
await rapyer.ainsert(user, order, product)
# Retrieve multiple models of different types in one call
models = await rapyer.afind(user.key, order.key, product.key)
print(f"Retrieved {len(models)} models:")
for model in models:
print(f" - {type(model).__name__}: {model.key}")
# Models are returned in the same order as keys
retrieved_user, retrieved_order, retrieved_product = models
print(f"User: {retrieved_user.name}")
print(f"Order total: ${retrieved_order.total}")
print(f"Product: {retrieved_product.name}")
if __name__ == "__main__":
asyncio.run(main())
Error Handling¶
from rapyer.errors import KeyNotFound, RapyerModelDoesntExistError
async def safe_fetch():
try:
models = await rapyer.afind("User:123", "Order:456")
except KeyNotFound as e:
print(f"Key not found: {e}")
except RapyerModelDoesntExistError as e:
print(f"Unknown model type: {e}")
Model.afind() (Class Method)¶
Retrieves all instances of a specific model class from Redis.
Parameters¶
- args: Optional. Can be:
- Empty: Returns all instances of the model
- Keys (
str): One or more Redis keys to retrieve specific models - Expressions (
Expression): Filter conditions for indexed fields
Returns¶
- list[AtomicRedisModel]: A list of model instances in the order corresponding to the input keys (when keys are provided)
Raises¶
- KeyNotFound: If any specified key is missing in Redis (only when keys are provided)
- CantSerializeRedisValueError: If a value cannot be deserialized
Description¶
The afind() class method retrieves instances of a model class from Redis with three modes:
- No arguments: Retrieves all instances matching the model's key pattern
- With keys: Retrieves specific instances by their Redis keys (full key or just primary key)
- With expressions: Filters instances using indexed field conditions
When passing keys, the method raises KeyNotFound if any key is missing.
Note¶
The Model.afind() method only returns instances of the specific model class it's called on. To retrieve models of different types by their keys, use the global rapyer.afind() function instead.