Package: redis-purse
PurseHigh-Level Async-IO Python interface for Redis >6.0.x that provides useful Pythonic abstractions to simplify
the usage of Redis as a non-blocking Caching layer, or even as a first-class non-blocking datastore.Influenced and Inspired by the great librarypottery, with
a few differences in objectives and implementation detail.purseis strictly an Async-IO library that utilizes the redis library.pursetries to adhere as much as possible to familiar APIs and idioms used with familiar python structures
(dict,set,listamong others), but deviates from those conventions in many instances:Due to theasync/awaitnature of the API, it is difficult and sometimes impossible to use python language constructs such asmyhash["key"] = "value"-
as of Python 3.10, the language simply doesn't provide async-io methods for those operations and idiomspursetries to expose, as much as possible, Redis rich features such as key TTL and pattern matching, among othersOptionally, collections in this library usepydanticto serialize, validate, and deserialize Python Models part of all data storage and retrieval operationsinstallationwith pippipinstallredis-purseBasic UsageRedisListRedisList provides an API that provides most methods and features of the pythonlistanddequeimportasynciofrompurse.collectionsimportRedisListfromredis.asyncioimportRedisasyncdefmain():local_list=['a','b','c','d','e','f']# local Redis >= 6.0.x plain connection with default paramsred_con=Redis()redis_key='redis_list'# The value_type defines the class to serialize to and fromredis_list=RedisList(redis=red_con,rkey=redis_key,value_type=str)# Clear the list, in case it was previously populatedawaitredis_list.clear()# extend a Redis list with a Python listawaitredis_list.extend(local_list)# async list comprehensionprint([xasyncforxinredis_list])# containsprint(awaitredis_list.contains('f'))# Trueprint(awaitredis_list.contains('g'))# False# getting the index of a valueprint(awaitredis_list.index('c'))# 2print(awaitredis_list.index('g'))# None, unlike a Python list that raises a ValueError# slicingprint(awaitredis_list.slice(2,5))# ['c', 'd', 'e']# inserting valuesawaitredis_list.insert(2,'x')awaitredis_list.insert(-2,'y')# getitemassertawaitredis_list.getitem(2)=='x'assertawaitredis_list.getitem(-3)=='y'# some deque methodsawaitredis_list.appendleft('z')awaitredis_list.pop()awaitredis_list.popleft()asyncio.run(main())RedisHashProvides most of the functionality of the Pythondict.importasynciofrompurse.collectionsimportRedisHashfromredis.asyncioimportRedisfrompydanticimportBaseModelasyncdefmain():# Pydantic ModelclassPlant(BaseModel):name:strhealthiness:floattasty:boolred_con=Redis()redis_key='redis_hash'# This class serializes and deserializes Plant Model objects when storing and retrieving dataredis_hash=RedisHash(red_con,redis_key,Plant)awaitredis_hash.clear()plants=[Plant(name="spinach",healthiness=9.8,tasty=False),Plant(name="broccoli",healthiness=12.2,tasty=True),Plant(name="lettuce",healthiness=3,tasty=False),Plant(name="avocado",healthiness=8,tasty=True),]# update redis hash with a python dictawaitredis_hash.update({p.name:pforpinplants})awaitredis_hash.set("carrot",Plant(name="carrot",healthiness=5,tasty=False))print(awaitredis_hash.len())# currently 5 mappings in total#  RedisHash is a generic type with supports IDE intellisense and type hintsp:Plant=awaitredis_hash.get('spinach')print(p.tasty)# False# async for syntaxasyncforname,plantinredis_hash.items():print(name,plant)asyncio.run(main())RedlockDistributed, None-blocking Lock implementation according to the algorithm and logic described herehttps://redis.io/topics/distlock, and closely resembling the python implementation herehttps://github.com/brainix/pottery/blob/master/pottery/redlock.py.This none-blocking implementation is particularly efficient and attractive when a real world
distributed application is using many distributed locks over many Redis Masters,
to synchronize on many Network Resources simultaneously, due to the very small overhead associated with
asyncio tasks, and any "waiting" that may need to happen to acquire locks, since all of the above
is happening efficiently on an event-queue.This example uses 5 Redis databases on the localhost as the Redlock Masters, to synchronize on
the access of a RedisList, where multiple tasks are concurrently synchronizing getting, incrementing and appending
to the last numerical item of that Redis List, with some asyncio delay to simulate real world
latencies and data processing times.importasynciofrompurse.redlockimportRedlockfrompurse.collectionsimportRedisListfromredis.asyncioimportRedisfromrandomimportrandom# The main Redis Store that contains the data that need synchronizationredis_store=Redis(db=0)# The Redis Masters for the async Redlock# Highly Recommended to be an odd number of masters: typically 1, 3 or 5 mastersredlock_masters=[Redis(db=x)forxinrange(5)]asyncdefdo_job(n):rlock=Redlock("redlock:list_lock",redlock_masters)rlist=RedisList(redis_store,"redis_list",str)forxinrange(n):asyncwithrlock:cl=awaitrlist.len()ifcl==0:awaitrlist.append("0")current_num=0else:current_num=int(awaitrlist.getitem(-1))# This sleep simulates the processing time of the job - up to 100ms hereawaitasyncio.sleep(0.1*random())# Get the job done, which is add 1 to the last numbercurrent_num+=1print(f"the task{asyncio.current_task().get_name()}working on item #:{current_num}")awaitrlist.append(str(current_num))asyncdefmain():rlist=RedisList(redis_store,"redis_list",str)awaitrlist.clear()# run 10 async threads (or tasks) in parallel, each one to perform 10 incrementsawaitasyncio.gather(*[asyncio.create_task(do_job(10))for_inrange(10)])# should print 0 to 100 in order, which means synchronization has happenedasyncforiteminrlist:print(item)return"success"asyncio.run(main())