{"id":31742873,"url":"https://github.com/tzickel/justredis","last_synced_at":"2025-10-09T11:19:45.067Z","repository":{"id":43685904,"uuid":"268568142","full_name":"tzickel/justredis","owner":"tzickel","description":"A Redis client for Python","archived":false,"fork":false,"pushed_at":"2022-02-23T22:18:36.000Z","size":195,"stargazers_count":41,"open_issues_count":2,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-09-28T08:23:21.461Z","etag":null,"topics":["async","asyncio","curio","python","python-3","python3","redis","redis-client","redis-cluster","redis-cluster-client","resp3","trio"],"latest_commit_sha":null,"homepage":null,"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/tzickel.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}},"created_at":"2020-06-01T16:01:40.000Z","updated_at":"2024-09-26T00:34:34.000Z","dependencies_parsed_at":"2022-08-22T10:30:43.810Z","dependency_job_id":null,"html_url":"https://github.com/tzickel/justredis","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/tzickel/justredis","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tzickel%2Fjustredis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tzickel%2Fjustredis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tzickel%2Fjustredis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tzickel%2Fjustredis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tzickel","download_url":"https://codeload.github.com/tzickel/justredis/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tzickel%2Fjustredis/sbom","scorecard":{"id":905794,"data":{"date":"2025-08-11","repo":{"name":"github.com/tzickel/justredis","commit":"12c63bba8a83f1c7934d34ea081ada6efbc563ad"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.1,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/24 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/release.yml:1","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/tzickel/justredis/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/tzickel/justredis/release.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/tzickel/justredis/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/tzickel/justredis/release.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:38: update your workflow using https://app.stepsecurity.io/secureworkflow/tzickel/justredis/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:45: update your workflow using https://app.stepsecurity.io/secureworkflow/tzickel/justredis/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:9: update your workflow using https://app.stepsecurity.io/secureworkflow/tzickel/justredis/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/tzickel/justredis/test.yml/master?enable=pin","Warn: containerImage not pinned by hash: Dockerfile.test:2","Warn: pipCommand not pinned by hash: Dockerfile.test:20","Warn: pipCommand not pinned by hash: .github/workflows/release.yml:19","Warn: pipCommand not pinned by hash: .github/workflows/test.yml:20","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned","Info:   0 out of   3 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v0.0.1a3 not signed: https://api.github.com/repos/tzickel/justredis/releases/32183556","Warn: release artifact v0.0.1a2 not signed: https://api.github.com/repos/tzickel/justredis/releases/31862077","Warn: release artifact v0.0.1a3 does not have provenance: https://api.github.com/repos/tzickel/justredis/releases/32183556","Warn: release artifact v0.0.1a2 does not have provenance: https://api.github.com/repos/tzickel/justredis/releases/31862077"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-24T17:18:16.544Z","repository_id":43685904,"created_at":"2025-08-24T17:18:16.544Z","updated_at":"2025-08-24T17:18:16.544Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279001290,"owners_count":26083058,"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","status":"online","status_checked_at":"2025-10-09T02:00:07.460Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["async","asyncio","curio","python","python-3","python3","redis","redis-client","redis-cluster","redis-cluster-client","resp3","trio"],"created_at":"2025-10-09T11:19:43.609Z","updated_at":"2025-10-09T11:19:45.060Z","avatar_url":"https://github.com/tzickel.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"## What ?\n\nA Redis client for Python supporting many Redis features and Python synchronous (Python 3.5+) and asynchronous (Python 3.6+) communication.\n\n## [Why](https://xkcd.com/927/) ?\n\n- Transparent API (Just call the Redis commands, and the library will figure out cluster routing, script caching, etc...)\n- Per context and command properties (database #, decoding, RESP3 attributes)\n- Asynchronous I/O support with the same exact API (but with the await keyword), targeting asyncio, trio and curio (using [AnyIO](https://github.com/agronholm/anyio) which needs to be installed as well if you want async I/O)\n- Modular API allowing for easy support for multiple synchronous and asynchronous event loops and disabling of unneeded features\n- CI Testing for CPython 3.5, 3.6, 3.7, 3.8, 3.9 and PyPy3 with Redis 5 and Redis 6\n- No legacy support for old language features\n\n## Redis features support table\n\n| Feature | Supported | Notes |\n| --- | --- | --- |\n| [Transactions](https://redis.io/topics/transactions) | V | See [examples](#examples) and [Transaction section](#redis-command-replacements) |\n| [Pub/Sub](https://redis.io/topics/pubsub) | V | See [examples](#examples) and [Pub/Sub and monitor section](#redis-command-replacements) |\n| [Pipelining](https://redis.io/topics/pipelining) | V | See [examples](#examples) and [Pipelining section](#pipelining) |\n| [Cluster](https://redis.io/topics/cluster-spec) | V | See [Cluster commands](#cluster-commands) |\n| [RESP3 support](https://github.com/antirez/RESP3/blob/master/spec.md) | V | See [RESP2 and RESP3 difference section](#resp2-and-resp3-difference) |\n| [SSL](https://redis.io/topics/encryption) | V | See the [SSL connection parameters](#settings-options) |\n| [Script caching](https://redis.io/commands/evalsha) | X |  |\n| [Client side caching](https://redis.io/topics/client-side-caching) | X |  |\n| [Sentinel](https://redis.io/topics/sentinel) | X |  |\n\n## Roadmap\n\nGetting it out of alpha:\n- [ ] API Finalization (your feedback is appreciated)\n  - [ ] Should calling redis be using \\_\\_call__() or a special method such as \"command\" for refactoring ?\n  - [ ] Is the modify() API flexiable enough ?\n  - [ ] Is it easy to extend the module ?\n  - [ ] Should we add helper functions for SCAN iterators and other API ?\n- [ ] Resolving some of the TODO in code\n\nGeneral:\n- More features in the support table\n- Better test coverage\n- Resolve all TODO in code\n- Move documentation to topics + docstrings\n- Automate code convertion between sync and async\n- More CI checks such as flake8, pylint, etc..\n\n## Not on roadmap (for now?)\n\n- High level features which are not part of the Redis specification (such as locks, retry transactions, etc...)\n- Manual command interface (maybe for special stuff like bit operations ?)\n- Python 2 support (can be added, but after removing some misc syntax features)\n\n## Installing\n\nThe project can be found in PyPI as justredis. Install it via pip or requierments.txt file:\n\n```bash\npip install justredis\n```\n\nIf you want to use asynchronous I/O frameworks asyncio or trio or curio with this library, you need to install the AnyIO library as well:\n\n```bash\npip install anyio\n```\n\n## Examples\n\n```python\nfrom justredis import Redis\n\n\n# Let's connect to localhost:6379 and decode the string results as utf-8 strings\nr = Redis(decoder=\"utf8\")\nassert r(\"set\", \"a\", \"b\") == \"OK\"\nassert r(\"get\", \"a\") == \"b\"\nassert r(\"get\", \"a\", decoder=None) == b\"b\" # But this can be changed per command\n\n\n# We can even run commands on a different database number\nwith r.modify(database=1) as r1:\n    assert r1(\"get\", \"a\") == None # In this database, a was not set to b\n\n\n# Here we can use a transactional set of commands\n# Notice that when we take a connection, if we plan on cluster support, we need\n# to tell it a key we plan on using inside, or a specific server address\nwith r.connection(key=\"a\") as c:\n    c(\"multi\")\n    c(\"set\", \"a\", \"b\")\n    c(\"get\", \"a\")\n    assert c(\"exec\") == [\"OK\", \"b\"]\n\n\n# Or we can just pipeline the commands from before\nwith r.connection(key=\"a\") as c:\n    result = c((\"multi\", ), (\"set\", \"a\", \"b\"), (\"get\", \"a\"), (\"exec\", ))[-1]\n    assert result == [\"OK\", \"b\"]\n\n\n# Here is the famous increment example\n# Notice we take the connection inside the loop,\n# this is to make sure if the cluster moved the keys, it will still be ok\nwhile True:\n    with r.connection(key=\"counter\") as c:\n        c(\"watch\", \"counter\")\n        value = int(c(\"get\", \"counter\") or 0)\n        c(\"multi\")\n        c(\"set\", \"counter\", value + 1)\n        if c(\"exec\") is None: # Redis returns None in EXEC command when the transaction failed\n            continue\n        value += 1 # The value is updated if we got here\n        break\n\n\n# Let's show some publish and subscribe commands,\n# here we use a push connection (where commands have no direct response)\nwith r.connection(push=True) as p:\n    p(\"subscribe\", \"hello\")\n    assert p.next_message() == [\"subscribe\", \"hello\", 1]\n    assert p.next_message(timeout=0.1) == None # Let's wait 0.1 seconds for another result\n    r(\"publish\", \"hello\", \", World !\")\n    assert p.next_message() == [\"message\", \"hello\", \", World !\"]\n```\n\n## API\n\n```python\nRedis(**kwargs)\n    @classmethod\n    from_url(url, **kwargs)\n    __enter__() / __exit__()\n    close()\n    # kwargs options = endpoint, decoder, attributes, database\n    __call__(*cmd, **kwargs)\n    endpoints()\n    # kwargs options = decoder, attributes, database\n    modify(**kwargs) # Returns a modified settings instance (while sharing the pool)\n    # kwargs options = key, endpoint, decoder, attributes, database\n    connection(push=False, **kwargs)\n        __enter__() / __exit__()\n        close()\n        # kwargs options = decoder, attributes, database\n        __call__(*cmd, **kwargs) # On push connection no result for calls\n        # kwargs options = decoder, attributes, database\n        modify(**kwargs) # Returns a modified settings instance (while sharing the connection)\n\n        # Push connection only commands\n        # kwargs options = decoder, attributes\n        next_message(timeout=None, **kwargs)\n        __iter__()\n        __next__()\n```\n\n### URI connection options\n\n```Redis.from_url()``` options are:\n\nRegular TCP connection\n```\nredis://[[username:]password@]host[:port][/database][[?option1=value1][\u0026option2=value2]]\n```\n\nSSL TCP connection (you can use ssl instead of rediss)\n```\nrediss://[[username:]password@]host[:port][/database][[?option1=value1][\u0026option2=value2]]\n```\n\nUnix domain connection (you can use unix instead of redis-socket)\n```\nredis-socket://[[username:]password@]path][[?option1=value1][\u0026option2=value2]]\n```\n\nFor cluster, you can replace host:port with a list of host1:port1,host2:port2,... if you want fallback options for backup.\n\nYou can add options in the end from the Redis constructor options below.\n\n### Settings options\n\nThis are the ```Redis()``` constructor options:\n```\npool_factory (\"auto\")\n    \"auto\" / \"cluster\" - Try to figure out automatically what the Redis server type is (currently cluster / no-cluster)\n    \"pool\" - Force non cluster aware connection pool (simpler code)\naddress (None)\n    An (address, port) tuple for tcp sockets, the default is (localhost, 6379)\n    An string if it's a path for unix domain sockets, the default is \"/tmp/redis.sock\"\nusername (None)\n    If you have an ACL username, specify it here\npassword (None)\n    If you have an AUTH / ACL password, specify it here\nclient_name (None)\n    If you want your client to be named on connection specify it here\nresp_version (2)\n    Specifies which RESP protocol version to use for connections\n    -1 = auto detect\n    2 = RESP2\n    3 = RESP3\nsocket_factory (\"tcp\")\n    Specifies which socket type to use to connect to the redis server\n    \"tcp\" tcp socket\n    \"unix\" unix domain socket\n    \"ssl\" tcp ssl socket\nconnect_retry (2)\n    How many attempts to retry connecting when establishing a new connection\nmax_connections (None)\n    How many maximum concurrent connections to keep to a server in the connection pool, the default is unlimited\nwait_timeout (None)\n    How long (float seconds) to wait for a connection when the connection pool is full before returning an timeout error, the default is unlimited\ncutoff_size (6000)\n    The maximum ammount of bytes that will be appended together instead of sent seperatly before sending data to the socket, 0 to disable this feature\ncustom_command_class (None)\n    Register a custom class to extend redis server commands handling\nencoder (None)\n    Specify how to encode strings to bytes, it can be a string, list or dictionary that are passed directly as the parameters to str.encode, the default is \"utf8\"\nconnect_timeout (None)\n    How many (float seconds) to wait for a connection with a server to be established, the default is unlimited\nsocket_timeout (None)\n    How many (float seconds) to wait for a socket operation (read/write) with a server, the default is unlimited    \n```\n\nThis parameters can be passed to the ```Redis()``` constructor, or to the ```modify()``` method or per ```__call__()```:\n```\ndecoder (None)\n    Specify how to decode the string results from the server, it can be a string, list or dictionary that are passed directly as parameters to bytes.decode, the default is normal bytes conversion\nattributes (False)\n    Specify if you want to handle the attributes fields from the RESP3 protocol (read the special section about this in the readme)\ndatabase (None)\n    Set which database to operate on the server, the default is 0\n```\n\nThis can be provided to the ```Redis()``` constructor if you are using the cluster pool_factory:\n```\naddresses (None)\n    Multiple (address, port) tuples for cluster ips for fallback. The default is ((localhost, 6379), )\n```\n\nThis can be provided to the ```Redis()``` constructor for tcp and ssl socket_factory:\n```\ntcp_keepalive (None)\n    How many seconds to check the TCP connection liveness, the default is disabled\ntcp_nodelay (True)\n    Enable or disable the TCP nodelay algorithm\n```\n\nThis can be provided to the ```Redis()``` constructor for ssl socket_factory:\n```\nssl_context (None)\n    An Python SSL context object, the default is Python's ssl.create_default_context()\nssl_cafile (None)\n    A path to the CA certificate file on disk, works only if ssl_context is None\nssl_certfile (None)\n    A path to the server certificate file on disk, works only if ssl_context is None\nssl_keyfile (None)\n    A path to the server key file on disk, works only if ssl_context is None\n```\n\nRead the cluster and connection documentation below for the options for the ```connection()``` and ```__call__()``` API\n\n### Exceptions\n\n```\nValueError - Will be thrown when an invalid input was given. Nothing will be sent to the server.\nError - Will be thrown when the server returned an error to a request.\nPipelinedExceptions - Will be thrown when some of the pipeline failed.\nRedisError - Will be thrown when an internal logic error has happened.\n    CommunicationError - An I/O error has occured.\n    ConnectionPoolError - The connection pool could not get a new connection.\n    ProtocolError - Invalid input from the server.\n```\n\n## Redis command replacements\n\nThe following Redis commands should not be called directly, but via the library API:\n\n### Username and Password (AUTH / ACL)\n\nIf you have a username or/and password you want to use, pass them to the connection constructor, such as:\n\n```python\nr = Redis(username=\"your_username\", password=\"your_password\")\n```\n\n### Database selection (SELECT)\n\nYou can specify the default database you want to use at the constructor:\n\n```python\nr = Redis(database=1)\n```\n\nIf you want to modify it afterwards for a specific set of commands, you can use a modify context for it:\n\n```python\nwith r.modify(database=2) as r1:\n    r1(\"set\", \"a\", \"b\")\n```\n\n### Transaction (WATCH / MULTI / EXEC / DISCARD)\n\nTo use the transaction commands, you must take a connection, and use all the commands inside. Please read the [Connection section](#connection-commands) below for more details.\n\n### Pub/Sub and monitor (SUBSCRIBE / PSUBSCRIBE / UNSUBSCRIBE / PUNSUBSCRIBE / MONITOR)\n\nTo use push commands, you must take a push connection, and use all the commands inside. Please read the [Connection section](#connection-commands) below for more details.\n\n## Usage\n\n### Connection commands\n\nThe ```connection()``` method is required to be used for sending multiple commands to the same server (such as transactions) or to talk to the server in push mode (pub/sub and monitor).\n\nYou can pass to the method ```push=True``` for push mode where commands have no direct response (else it defaults to a normal connection).\n\nWhile you do not have to pass a ```key=```, it's better to provide one you are about to use inside, in case you want to talk to a cluster later on.\n\nThere are some instances you might want to talk to a specific server in a cluster (like getting keyspace notifications from it), so you can pass ```endpoint=``` instead of ```key=``` with that server's address.\n\nCheck the [transaction or pubsub examples](#examples) above for syntax usage.\n\n### Pipelining\n\nYou can pipeline multiple commands together by passing an list of commands to be sent together. This is usually to have better latency.\n\nNotice that if you are talking to a cluster, the pipeline must contain commands which handle keys in the same keyslots of a given server.\n\nIf some of the commands failed, an PipelinedExceptions exception will be thrown, with it's args pointing to the result of each command.\n\nCheck the [pipeline example](#examples) above for syntax usage.\n\n### Cluster commands\n\nCurrently the library supports talking to Redis master servers only. It knows automatically when you are connected to a cluster (unless you disabled that feature in the constructor settings explicitly).\n\nIf you want to specify multiple addresses for redundency, you can do so:\n\n```python\nr = Redis(addresses=(('host1', port1), ('host2', port2)))\n```\n\nYou can get the list of servers with the ```endpoints()``` method.\n\nYou can also send a command to all the masters by adding ```endpoint='masters'``` to the ```__call__()```:\n\n```python\nr(\"cluster\", \"info\", endpoint=\"masters\")\n```\n\nYou can also open a connection to a specific instance, for example to get key space notifications or monitor it by adding ```endpoint=\u003cthe server address\u003e``` to the ```connection()``` method).\n\n### RESP2 and RESP3 difference\n\nThe library supports talking both in RESP2 and RESP3. By default it will use RESP2, because this way you'll get same response whether you are talking to a RESP3 supporting server (Redis server version 6 and above) or not.\n\nYou can still tell it to use RESP3 or to auto negotiate the highest version with the specific server:\n\n```python\nr = Redis(resp_version=2) # Talk RESP2 only\nr = Redis(resp_version=3) # Talk RESP3 only (will throw an Exception if server does not support it)\nr = Redis(resp_version=-1) # Talk in the highest version possible\n```\n\nYou can read about RESP3 protocol and responses in the [Redis documentation](https://github.com/antirez/RESP3/blob/master/spec.md).\n\nRESP3 allows the clients to know the response type (such as strings, lists, dictionaries, sets...), and Justredis supports all of the response types.\n\nRESP3 provides an option to get with the results extra attributes. Since Python's type system cannot add the attributes easily, another configuration value was added, ```attributes``` which specifies if you care about getting this information or not, the default is False:\n\n```python\nr = Redis(attributes=True)\n```\n\nIf attributes is disabled, you will get the direct Python mapping of the results (set, list, dict, string, numbers, etc...) and if enabled, you will get a special object which will hold the raw data in the ```data``` attribute, and the attributes in the ```attrs``` attribute. Notice that this feature is orthogonal to choosing RESP2 / RESP3 (but in RESP2 the attrs will always be empty), for ease of development.\n\nHere is an example of the difference in Redis version 6, with and without attributes:\n```python\n\u003e\u003e\u003e import justredis\n\u003e\u003e\u003e r = justredis.Redis() # By default it connects via RESP2\n\u003e\u003e\u003e r(\"hgetall\", \"aaa\")\n[b'bbb', b'ccc', b'ccc', b'ddd']\n\u003e\u003e\u003e r(\"hgetall\", \"aaa\", attributes=True) # This is RESP2 with attributes, it has .data and .attrs\nArray: [String: b'bbb' , String: b'ccc' , String: b'ccc' , String: b'ddd' ] \n\u003e\u003e\u003e r = justredis.Redis(resp_version=-1) # This will connect to Redis 6 with RESP3\n\u003e\u003e\u003e r(\"hgetall\", \"aaa\")\nOrderedDict([(b'bbb', b'ccc'), (b'ccc', b'ddd')]) # This is Python's OrderedDict\n\u003e\u003e\u003e r(\"hgetall\", \"aaa\", attributes=True)\nMap: OrderedDict([(String: b'bbb' , String: b'ccc' ), (String: b'ccc' , String: b'ddd' )])\n```\n\n### Thread and async safety\n\nThe library is thread safe and async safe. Do not pass Connection objects between different threads or coroutines.\n\n### Modify\n\nYou can change some of the settings on a per ```__call()__``` call, or if you want multiple calls to have different settings, you can use the ```modify()``` method on a Connection or Redis objects.\n\nCurrently you can change the string decoder used, the database number and the attributes flag. Check the [examples](#examples) above to see how it's done.\n\n### Serialization and deserialization\n\nThe library supports as inputs only this types: bytes, bytearray, memoryview, str, int and float. If you pass a string, it will be encoded to bytes by the given encoder option (default is utf-8). Passing anything else will result in a ValueError.\n\nThe library will return the data types that the RESP protocol returns as described in the RESP section. Exceptions will always be utf-8 string encoded and for other string results, you can decide to keep them as bytes, or to decode them to a string.\n\n### Async support\n\nThe API for the asynchronous commands is exactly the same, just adding \"await\" where it's needed:\n\n```python\nAsyncRedis(**kwargs)\n    @classmethod\n    from_url(url, **kwargs)\n    async __aenter__() / __aexit__()\n    async aclose()\n    # kwargs options = endpoint, decoder, attributes, database\n    async __call__(*cmd, **kwargs)\n    async endpoints()\n    # kwargs options = decoder, attributes, database\n    modify(**kwargs) # Returns a modified settings instance (while sharing the pool)\n    # kwargs options = key, endpoint, decoder, attributes, database\n    async connection(push=False, **kwargs)\n        async __aenter__() / async __aexit__()\n        async aclose()\n        # kwargs options = decoder, attributes, database\n        async __call__(*cmd, **kwargs) # On push connection no result for calls\n        # kwargs options = decoder, attributes, database\n        modify(**kwargs) # Returns a modified settings instance (while sharing the connection)\n\n        # Push connection only commands\n        # kwargs options = decoder, attributes\n        async next_message(timeout=None, **kwargs)\n        __iter__()\n        async __next__()\n```\n\nDon't forget there is no ```__del__()``` method in async code, so call ```aclose()``` or use async context managers when needed.\n\n### Extending the library with more command support\n\nYou can extend the Redis object to support real redis commands, and not just calling them raw, here is an example:\n\n```python\nfrom justredis import Redis\n\n\nclass CustomCommands:\n    def __init__(self, base):\n        self._base = base\n\n    def get(self, key, **kwargs):\n        return self._base(\"get\", key, **kwargs)\n\n    def set(self, key, value, **kwargs):\n        return self._base(\"set\", key, value, **kwargs)\n\n\nr = Redis(custom_command_class=CustomCommands)\nr.set(\"hi\", \"there\")\nassert r.get(\"hi\", decoder=\"utf8\") == \"hi\"\nwith r.modify(database=1) as r1:\n    assert r1.get(\"hi\") == None\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftzickel%2Fjustredis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftzickel%2Fjustredis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftzickel%2Fjustredis/lists"}