{"id":31804126,"url":"https://github.com/tzickel/jr","last_synced_at":"2026-05-18T09:02:57.704Z","repository":{"id":73486150,"uuid":"171323819","full_name":"tzickel/jr","owner":"tzickel","description":"An asynchronous Redis client library for Python 3.6+","archived":false,"fork":false,"pushed_at":"2020-05-31T19:55:40.000Z","size":378,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-10-11T01:57:00.615Z","etag":null,"topics":["asyncio","python","python-3","redis","redis-client","redis-cluster"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tzickel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-02-18T17:08:11.000Z","updated_at":"2020-06-01T15:59:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"83b4bd35-7a06-4013-8f64-836ab701bdca","html_url":"https://github.com/tzickel/jr","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tzickel/jr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tzickel%2Fjr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tzickel%2Fjr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tzickel%2Fjr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tzickel%2Fjr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tzickel","download_url":"https://codeload.github.com/tzickel/jr/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tzickel%2Fjr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33172173,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T05:43:36.989Z","status":"ssl_error","status_checked_at":"2026-05-18T05:43:19.133Z","response_time":71,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asyncio","python","python-3","redis","redis-client","redis-cluster"],"created_at":"2025-10-11T01:55:32.785Z","updated_at":"2026-05-18T09:02:57.697Z","avatar_url":"https://github.com/tzickel.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"## What?\nAn asynchronous redis client library for Python 3.6+\n\nPlease note that this project is currently alpha quality and the API is not finalized. Please provide feedback if you think the API is convenient enough or not. A permissive license will be chosen once the API will be more mature for wide spread consumption.\n\n## Why?\n### All commands are pipelined by default\n\nSince most Redis server commands are intended to be run for a short time on the server, sometimes you pay more time on network latency than execution time. By splitting the sending and receiving parts into seperate coroutines all commands are sent seperately from waiting for their response.\n\n### Transparent API\n\nThe library focuses on hiding from you the internals of Redis behaviour such as pipelining, cluster support, script caching, slow and blocking and multi commands, connection pooling, publish and subscribe.\n\nYou can see the example below for some examples.\n\n### Missing\n\nThe library currently does not focus on providing the following:\n\n1. A higher level API for understanding the different commands responses\n2. Sentinal support\n3. SSL connections\n4. Implementing high-level constructs such as distributed locks on top of redis (Not in the scope of this project)\n\n## Roadmap\n- [ ] API Finalization\n- [ ] Choose license\n- [ ] Resolve all TODO in code\n- [ ] More test coverage and test out network I/O failure and concurrency\n\n## Installing\nFor now you can install this via this github repository by pip installing or adding to your requirements.txt file:\n\n```\ngit+git://github.com/tzickel/justredis@master#egg=justredis\n```\n\nReplace master with the specific branch or version tag you want.\n\n## Examples\n```python\nfrom justredis import MultiplexerPool, utf8_bytes_as_strings\nimport asyncio\n\n\nasync def main():\n    # Connect to the default redis port on localhost\n    async with MultiplexerPool() as redis:\n        # Send commands to database #0 (and use by default bytes as utf8 strings decoder)\n        db = redis.database(decoder=utf8_bytes_as_strings)\n        # Shortcut so you don't have to type long words each time\n        c = db.command\n        cr = db.commandreply\n        # Send an pipelined SET request where you don't care about the result (You don't have to use bytes notation or caps)\n        await c(b'SET', 'Hello', 'World!')\n        # Send a pipelined GET request and resolve it immediately\n        print('Hello, %s' % await cr(b'GET', 'Hello'))\n\n        # You can even send both commands together atomically (so if the first fails the second won't run)\n        async with db.multi() as m:\n            m.command(b'SET', 'Hello', 'World!')\n            hello = m.command(b'GET', 'Hello')\n        print('Atomic Hello, %s' % await hello())\n\n        # This shows support in the Pooled multiplexer for blocking commands\n        waiting = await c(b'BLPOP', 'queue', 0)\n        await cr(b'RPUSH', 'queue', 'Hello, World!')\n        print('Queued %s' % (await waiting())[1])\n\n        # And even with publish \u0026 subscribe.\n        async with redis.pubsub(decoder=utf8_bytes_as_strings) as pubsub:\n            await pubsub.add('Hello')\n            await cr(b'PUBLISH', 'Hello', 'World!')\n            await pubsub.message() # This is the registration message for the Hello channel.\n            print('PubSub Hello, %s' % (await pubsub.message())[2])\n\n        # And here we can do an atomic get and increment example\n        async with db.watch('Counter') as w:\n            value = int(await w.commandreply(b'GET', 'Counter') or 0)\n            value += 1\n            async with w.multi() as m:\n                m.command(b'SET', 'Counter', value)\n            # If there is an transaction error, it will throw here\n            counter = value\n\n\nif __name__ == '__main__':\n    loop = asyncio.get_event_loop()\n    loop.run_until_complete(main())\n    loop.close()\n```\n\nYou can check the [tests](tests/test.py) for some more examples.\n\n## API\nThis is all the API in a nutshell:\n\n```python\nMultiplexerPool(configuration=None) or Multiplexer(configuration=None)\n  async __aenter__()\n  async __aexit__()\n  async aclose()\n  database(number=0, encoder=utf8_encode, decoder=None)\n    async command(*args, encoder=utf8_encode, decoder=None, throw=True)\n      async __call__() # You can also await directly on the command as well\n    async commandreply(*args, encoder=utf8_encode, decoder=None, throw=True)\n    multi()\n      async __aenter__()\n      async __aexit__()\n      # Notice that this command is not awaitable\n      command(*args, encoder=utf8_encode, decoder=None, throw=True)\n        # But the result is\n        async __call__() # You can also await directly on the command as well\n      # This command will be automatically called when leaving the context manager\n      async execute()\n      # This command will be automatically called when leaving the context manager on exception (or can be called explicitly before to abort)\n      async discard()\n    watch(*keys)\n      async __aenter__()\n      async __aexit__()\n      async aclose()\n      async command() # Like above\n      async commandreply() # like above\n      multi() # Like above\n  pubsub(encoder=utf8_encode, decoder=None)\n    async add(channels=None, patterns=None)\n    async remove(channels=None, patterns=None)\n    async message(timeout=None)\n    async ping(message=None)\n  async endpoints()\n  async run_commandreply_on_all_masters(*args, encoder=utf8_encode, decoder=None)\n```\n\nThe error model is 2 main Exceptions:\n\n```python\n# An error response from the redis server for a sent command\nRedisReplyError(Exception)\n\n# An error from this library (usually means your command might have not reached the server)\nRedisError(Exception)\n```\n\nMultiplexer configuration is a list of endpoints or a dictionary that can contain the following keys:\n\n* endpoints - The connection endpoints (a list where each element is a string for unix domain or (host, port) tuple for tcp)\n* password - The server password\n* connecttimeout - Set connect timeout in seconds\n* connectretry - Number of connection attempts before giving up\n* sockettimeout - Set socket timeout in seconds\n* recvbuffersize - Socket receive buffer size in bytes (Default 64K)\n* tcpkeepalive - Enable / Disable (Default) TCP Keep alive in seconds\n* tcpnodelay - Enable (Default) / Disable TCP no delay\n* connectionhandler - Use a custom Connection class handler\n\nMultiplexerPool also has this parameters:\n\n* maxconnections - maximum number of in use connections before blocking new requests.\n\n## Redis command replacements\nSome redis commands are not allowed to be run directly, this chapter explains which commands, why, and what is their replacment.\n\n### Database selection (SELECT)\n\nSince the client can multiplex multiple commands into one socket, it's important to keep track on which database number each command is running.\n\nEach multiplexer has a `database(number=0, encoder=utf8_encode, decoder=None)` command which keeps track on which database number the commands are running.\n\nAll the redis commands should be called via the Database object returned by database.\n\n```python\ndb = redis.database() # you can set database(2) for database #2\na = await db.cr('get', 'a')\n```\n\n### Transction (MULTI / EXEC / DISCARD)\n\nSince the client can multiplex multiple commands into one socket, and a transaction is a group of commands which is executed together atomically, it's important not to mix them up with other commands.\n\nEach database has an `multi()` command which you can run commands you want as part of a transaction together.\n\nIt's important to see that the commands API is not awaitable inside a transaction scope, since they actually run in the end (execute part). You await for their result outside the transaction scope.\n\n```python\ndb = redis.database()\nasync with db.multi() as m:\n    m.commmand('set', 'a', 'b')\n    a = m.command('get', 'a')\na = await a\n```\n\n### Conditional transaction (WATCH)\n\nConditional transactions require to send each command between the WATCH and MULTI part directly and get the result, thus that part has additional interface above the regular transaction one above.\n\nThe database has an `watch(*keys)` command which starts a conditional transaction.\n\n```python\ndb = redis.database()\nasync with db.watch('Counter') as w:\n    value = int(await w.commandreply(b'GET', 'Counter') or 0)\n    value += 1\n    async with w.multi() as m:\n        m.command(b'SET', 'Counter', value)\n    # If there is an transaction error, it will throw here\n    counter = value\n```\n\n### Password (AUTH)\n\nSince the multiplexer handles reconnection, you should not manualy call the AUTH command to authenticate with the server.\n\nIf your redis server has password authentication, then pass to the multiplexer constructor the password argument.\n\n### Subscribe (SUBSCRIBE/ PSUBSCRIBE / UNSUBSCRIBE / PUNSUBSCRIBE)\n\nThe multiplexer combines all the subscribed channels and patterns into one socket. It provides a high level API to manage it.\n\nIf you want to subscribe to topics (channels and patterns), use the multiplexer `pubsub()` command, which returns an instance you can `add(channels, patterns)` or `remove(channels, patterns)`\n\nYou can then call `message(timeout)` to wait for a message on one of the registered topics.\n\nIf there is an I/O error while waiting for a message, an Exception will be thrown. Attempting to call `message` again, will attempt to reconnect and reestablish the listened topics.\n\n```python\nasync with redis.pubsub(decoder=utf8_bytes_as_strings) as pubsub:\n    await pubsub.add('Some Channel')\n    while True:\n        msg = await pubsub.message()\n```\n\n### Scripting (EVAL)\n\nEVAL commands, are first tried to be run as EVALSHA commands (i.e. only their hash is sent to the server), if the attempt fails (the server does not know this script), the client auto sends the request as an EVAL again.\n\n### Debugging (MONITOR)\n\nNot implmented yet (can be done like Subscribe)\n\n## Additional commands\n\n### Cluster API\n\nThe Multiplexer has the command `run_commandreply_on_all_masters()` which runs the given command on all the cluster masters and returns the result as a dictionary with each server has an entry.\n\n## Partially inspired by\nThe .NET Redis client package [StackExchange.Redis](https://stackexchange.github.io/StackExchange.Redis/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftzickel%2Fjr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftzickel%2Fjr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftzickel%2Fjr/lists"}