Redis¶
Uvicore ships with a simple, async Redis service — a lightweight connection helper and passthrough to the official redis-py async client. Define your Redis servers once in config and the service hands you a ready-to-use async client for any of them, pooling and reusing each connection behind the scenes.
This is not the Cache system. Caching is a higher-level key/value abstraction that uses a Redis connection as one of its backends. The Redis service documented here gives you the raw client so you can run any Redis command — strings, hashes, lists, sets, pub/sub, streams, scripting and more.
See The Code on Github
Configuration¶
Redis connections are defined in your package's config/package.py under the top-level redis key. Apps created from the Uvicore Installer already include this block — usually with two connections, one general-purpose and one dedicated to caching.
# --------------------------------------------------------------------------
# Redis Connections
# --------------------------------------------------------------------------
'redis': {
'default': env('REDIS_DEFAULT', 'wiki'),
'connections': {
'wiki': {
'host': env('REDIS_WIKI_HOST', '127.0.0.1'),
'port': env.int('REDIS_WIKI_PORT', 6379),
'database': env.int('REDIS_WIKI_DB', 0),
'password': env('REDIS_WIKI_PASSWORD', None),
},
'cache': {
'host': env('REDIS_CACHE_HOST', '127.0.0.1'),
'port': env.int('REDIS_CACHE_PORT', 6379),
'database': env.int('REDIS_CACHE_DB', 2),
'password': env('REDIS_CACHE_PASSWORD', None),
},
},
},
Each connection is a named entry with host, port, database (the Redis logical database number) and an optional password. The default key names the connection used whenever you don't ask for one explicitly.
As always, every value is .env overridable so the same code runs in dev, staging and production untouched:
REDIS_WIKI_HOST=127.0.0.1
REDIS_WIKI_PORT=6379
REDIS_WIKI_DB=0
Behind the scenes the service builds a standard connection URL from these parts (redis://host:port/database, with ?password= appended when a password is set).
Tip
Multiple packages can each define their own redis connections. Uvicore deep-merges them all at boot, so an app that depends on other Uvicore packages ends up with every connection available under one service.
Connecting¶
Grab the service with a simple import, then await .connect() to get an async Redis client:
from uvicore.redis import Redis
# Connect to the default connection (the 'default' from your config)
redis = await Redis.connect()
# Or connect to a specific named connection
cache_redis = await Redis.connect('cache')
As with most things in Uvicore, the service is bound in the IoC Container, so if you prefer you can resolve the very same singleton with uvicore.ioc.make() instead of the import:
import uvicore
# 'redis' (or 'Redis') is the registered alias for the service
redis = await uvicore.ioc.make('redis').connect()
Redis.connect() returns a live redis-py async client (redis.asyncio.Redis). Everything from here on is plain redis-py — every command the client supports is available to you.
Note
connect() is lazy and pooled. The first call to a connection opens the pool; every later call for that same connection returns the same client instance, so it is cheap to call await Redis.connect() wherever you need it rather than passing the client around.
Using the client¶
Because the service is a thin passthrough, you are talking directly to redis-py. A few common examples:
from uvicore.redis import Redis
redis = await Redis.connect()
# Strings
await redis.set('greeting', 'hello')
await redis.get('greeting') # b'hello'
await redis.setex('session:123', 600, 'abc') # value with a 600s TTL
# Counters
await redis.incr('page:views')
await redis.incrby('page:views', 10)
# Expiry
await redis.expire('greeting', 60) # expire in 60 seconds
await redis.ttl('greeting') # seconds remaining
# Hashes
await redis.hset('user:1', mapping={'name': 'Matthew', 'role': 'admin'})
await redis.hgetall('user:1') # {b'name': b'Matthew', b'role': b'admin'}
# Lists
await redis.rpush('queue', 'job1', 'job2')
await redis.lrange('queue', 0, -1) # [b'job1', b'job2']
# Sets
await redis.sadd('tags', 'linux', 'mac')
await redis.smembers('tags') # {b'linux', b'mac'}
# Delete and existence
await redis.delete('greeting')
await redis.exists('user:1') # 1
Tip
By default redis-py returns bytes, not strings (e.g. b'hello'). Decode with .decode() when you need a string, or store and retrieve your own serialized format (JSON, pickle, etc.). For the full command reference see the redis-py command docs.
Multiple Connections¶
Define as many connections as you like — different servers, different logical databases, different credentials. Each one is isolated:
from uvicore.redis import Redis
wiki = await Redis.connect('wiki') # database 0
cache = await Redis.connect('cache') # database 2
await wiki.set('key', 'value')
await cache.exists('key') # 0 - different database, not visible here
Inspecting Connections¶
The service also exposes its configuration so you can introspect what is wired up:
from uvicore.redis import Redis
Redis.default # 'wiki' - the default connection name
Redis.connections # Dict of all connection configs by name
Redis.connection('cache') # the 'cache' connection config (host, port, database, url...)
Redis.connection() # the default connection's config
Redis.engines # Dict of currently-connected pools, keyed by URL
Asking for a connection that doesn't exist raises an exception:
Redis.connection('nope') # Exception: Redis connection nope not found
Relationship to Cache¶
The Cache system and this Redis service are related but distinct:
- Redis service (this page) — low-level. You get the raw async client and run any Redis command directly.
- Cache — high-level key/value API (
get/put/remember/forget...) with TTLs, key prefixing and pluggable backends. Itsredisbackend simply uses one of the Redisconnectionsyou defined here (thecacheconnection in the examples above).
Reach for Cache when you want simple, expiring key/value storage that could live in Redis or in-app memory. Reach for the Redis service when you need Redis-specific features — pub/sub, streams, sorted sets, atomic counters, Lua scripting — or you simply want full control over the client.
Redis tips
- Get the service with
from uvicore.redis import Redis, thenawait Redis.connect()for the default connection orawait Redis.connect('name')for a specific one. You can also resolve it from the IoC withuvicore.ioc.make('redis'). connect()is pooled — the same client is reused per connection, so call it freely.- Define connections in
config/package.pyunderredis, and override host/port/db/password per environment via.env. - The client is plain redis-py async — values come back as bytes, so
.decode()when you need strings. - Need simple expiring key/value storage instead of raw Redis? Use the Cache system.