{"id":13501251,"url":"https://github.com/stereobutter/reddish","last_synced_at":"2025-03-21T03:31:21.871Z","repository":{"id":43191995,"uuid":"403291214","full_name":"stereobutter/reddish","owner":"stereobutter","description":"An async redis client for python with minimal API","archived":false,"fork":false,"pushed_at":"2023-08-07T20:33:01.000Z","size":189,"stargazers_count":27,"open_issues_count":5,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-14T11:16:11.470Z","etag":null,"topics":["redis-client"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stereobutter.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-09-05T11:31:56.000Z","updated_at":"2024-11-23T14:55:40.000Z","dependencies_parsed_at":"2024-01-16T10:35:20.330Z","dependency_job_id":"7d537b2a-979b-4a2d-a0c1-29ee15b208c6","html_url":"https://github.com/stereobutter/reddish","commit_stats":{"total_commits":149,"total_committers":5,"mean_commits":29.8,"dds":0.06711409395973156,"last_synced_commit":"09e53a04095e12da1f819cd1e54ce0adf806d745"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stereobutter%2Freddish","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stereobutter%2Freddish/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stereobutter%2Freddish/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stereobutter%2Freddish/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stereobutter","download_url":"https://codeload.github.com/stereobutter/reddish/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244733597,"owners_count":20501011,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["redis-client"],"created_at":"2024-07-31T22:01:30.634Z","updated_at":"2025-03-21T03:31:21.599Z","avatar_url":"https://github.com/stereobutter.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# reddish - a friendly redis client with sync and async api\n\n[![PyPI](https://img.shields.io/pypi/v/reddish?color=blue)](https://pypi.org/project/reddish/)\n\n* [Features](#features)\n* [Installation](#installation)\n* [Minimal Example](#minimal-example)\n* [Usage](#usage)\n\n## Features\n* both sync and async API\n* *bring your own connection* - supports TCP, TCP+TLS and Unix domain sockets\n* supports multiple networking libaries and event loops\n    * sync API using the standard library `socket` module\n    * async API using `asyncio`'s, `trio`'s or `anyio`'s stream primitives\n* minimal API so you don't have to relearn how to write redis commands\n* supports allmost every redis command (including modules) except for `SUBSCRIBE`, `PSUBSCRIBE`, `SSUBSCRIBE`, `MONITOR` and `CLIENT TRACKING` [^footnote]\n* parses replies into python types if you like (powered by [pydantic](https://github.com/samuelcolvin/pydantic))\n* works with every redis version and supports both `RESP2`and `RESP3` protocols\n\n[^footnote]: Commands like `SUBSCRIBE` or `MONITOR` take over the redis connection for listening to new events barring regular commands from being issued over the connection. \n\n## Installation\n```\npip install reddish  # install just with support for socket and asyncio\npip install reddish[trio]  # install with support for trio\npip install reddish[anyio]  # install with support for anyio\n```\n\n## Minimal Example - sync version\n```python\nimport socket\nfrom reddish import Command\nfrom reddish.clients.socket import Redis\n\nredis = Redis(socket.create_connection(('localhost', 6379)))\n\nassert b'PONG' == redis.execute(Command('PING'))\n```\n\n## Minimal Example - async version (asyncio)\n```python\nimport asyncio\nfrom reddish.clients.asyncio import Redis\n\nredis = Redis(await asyncio.open_connection('localhost', 6379))\n\nassert b'PONG' == await redis.execute(Command('PING'))\n```\n\n## Minimal Example - async version (trio)\n```python\nimport trio\nfrom reddish.clients.trio import Redis\n\nredis = Redis(await trio.open_tcp_stream('localhost', 6379))\n\nassert b'PONG' == await redis.execute(Command('PING'))\n```\n\n## Minimal Example - async version (anyio)\n```python\nimport trio\nfrom reddish.backends.anyio import Redis\n\nredis = Redis(await anyio.connect_tctp('localhost', 6379))\n\nassert b'PONG' == await redis.execute(Command('PING'))\n```\n\n## Usage\n\n### Command with a fixed number of arguments\n```python\n# simple command without any arguments\nCommand('PING')\n\n# commands with positional arguments\nCommand('ECHO {}', 'hello world')\n\n# commands with keyword arguments\nCommand('SET {key} {value}', key='foo', value=42)\n```\n\n### Handling invalid commands\n```python\nfrom reddish import CommandError\n\ntry:\n    await redis.execute(Command(\"foo\"))\nexcept CommandError as error:\n    print(error.message)  # \u003e\u003e\u003e ERR unknown command `foo`, with args beginning with:\n```\n\n### Parsing replies\n```python\n# return response unchanged from redis\nassert b'42' == await redis.execute(Command('ECHO {}', 42))\n\n# parse response as type\nassert 42 == await redis.execute(Command('ECHO {}', 42).into(int))\n\n# handling replies that won't parse correctly\nfrom reddish import ParseError\n\ntry:\n    await redis.execute(Command('PING').into(int))\nexcept ParseError as error:\n    print(error.reply)\n\n# use any type that works with pydantic\nfrom pydantic import Json\nimport json\n\ndata = json.dumps({'alice': 30, 'bob': 42})\nresponse == await redis.execute(Command('ECHO {}', data).into(Json))\nassert response == json.loads(data)\n```\n\n### Commands with variadic arguments\n```python\nfrom reddish import Args\n\n# inlining arguments\nCommand('DEL {keys}', keys=Args(['foo', 'bar']))  # DEL foo bar\n\n# inlining pairwise arguments \ndata = {'name': 'bob', 'age': 42}\nCommand('XADD foo * {fields}', fields=Args.from_dict(data))  # XADD foo * name bob age 42\n``` \n\n### Pipelining commands\n```python\nfoo, bar = await redis.execute_many(Command('GET', 'foo'), Command('GET', 'bar'))\n\n# handling errors in a pipeline\nfrom reddish import PipelineError\n\ntry:\n    foo, bar = await redis.execute_many(*commands)\nexcept PipelineError as error:\n    for outcome in error.outcomes:\n        try:\n            # either returns the reply if it was successful \n            # or raises the original exception if not\n            value = outcome.unwrap() \n            ...\n        except CommandError:\n            ...\n        except ParseError:\n            ...\n```\n\n### Transactions\n```python\nfrom reddish import MultiExec\n\ntx = MultiExec(\n    Command('ECHO {}', 'foo'),\n    Command('ECHO {}', 'bar')\n)\n\nfoo, bar = await redis.execute(tx)\n\n# handling errors with transactions\ntry:\n    await redis.execute(some_tx)\nexcept CommandError as error:\n    # The exception as a whole failed and redis replied with an EXECABORT error\n    cause = error.__cause__  # original CommandError that caused the EXECABORT\n    print(f'Exception aborted due to {cause.code}:{cause.message}')\nexcept TransactionError as error:\n    # Some command inside the transaction failed \n    # but the transaction as a whole succeeded\n    # to get at the partial results of the transaction and the errors \n    # you can iterate over the outcomes of the transaction\n    for outcome in error.outcomes:\n        try:\n            outcome.unwrap()  # get the value or raise the original error\n        except CommandError:\n            ...\n        except ParseError:\n            ...\n\n\n# pipelining together with transactions\n[foo, bar], baz = await redis.execute_many(tx, Command('ECHO {}', 'baz'))\n\n# handling errors with transactions inside a pipeline\ntry:\n    res: list[int] = await redis.execute_many(*cmds)\nexcept PipelineError as error:\n    # handle the outcomes of the pipeline\n    for outcome in error.outcomes:\n        try:\n            outcome.unwrap()\n        except CommandError:\n            ...\n        except ParseError:\n            ...\n        except TransactionError as tx_error:\n            # handle errors inside the transaction\n            for tx_outcome in tx_error.outcomes:\n                ...\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstereobutter%2Freddish","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstereobutter%2Freddish","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstereobutter%2Freddish/lists"}