redis 7.0.0b2__tar.gz → 7.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {redis-7.0.0b2 → redis-7.0.1}/PKG-INFO +17 -4
- {redis-7.0.0b2 → redis-7.0.1}/README.md +14 -3
- redis-7.0.1/dev_requirements.txt +30 -0
- {redis-7.0.0b2 → redis-7.0.1}/pyproject.toml +9 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/__init__.py +1 -1
- {redis-7.0.0b2 → redis-7.0.1}/redis/_parsers/base.py +6 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/_parsers/helpers.py +64 -6
- {redis-7.0.0b2 → redis-7.0.1}/redis/asyncio/client.py +14 -5
- {redis-7.0.0b2 → redis-7.0.1}/redis/asyncio/cluster.py +5 -1
- {redis-7.0.0b2 → redis-7.0.1}/redis/asyncio/connection.py +19 -1
- redis-7.0.1/redis/asyncio/http/http_client.py +265 -0
- redis-7.0.1/redis/asyncio/multidb/client.py +530 -0
- redis-7.0.1/redis/asyncio/multidb/command_executor.py +339 -0
- redis-7.0.1/redis/asyncio/multidb/config.py +210 -0
- redis-7.0.1/redis/asyncio/multidb/database.py +69 -0
- redis-7.0.1/redis/asyncio/multidb/event.py +84 -0
- redis-7.0.1/redis/asyncio/multidb/failover.py +125 -0
- redis-7.0.1/redis/asyncio/multidb/failure_detector.py +38 -0
- redis-7.0.1/redis/asyncio/multidb/healthcheck.py +285 -0
- redis-7.0.1/redis/background.py +204 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/client.py +49 -27
- {redis-7.0.0b2 → redis-7.0.1}/redis/cluster.py +9 -1
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/core.py +64 -29
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/json/commands.py +2 -2
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/__init__.py +2 -2
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/aggregation.py +24 -26
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/commands.py +10 -10
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/query.py +12 -12
- {redis-7.0.0b2 → redis-7.0.1}/redis/connection.py +1613 -1263
- redis-7.0.1/redis/data_structure.py +81 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/event.py +84 -10
- {redis-7.0.0b2 → redis-7.0.1}/redis/exceptions.py +8 -0
- redis-7.0.1/redis/http/http_client.py +425 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/maint_notifications.py +18 -7
- redis-7.0.1/redis/multidb/circuit.py +144 -0
- redis-7.0.1/redis/multidb/client.py +526 -0
- redis-7.0.1/redis/multidb/command_executor.py +350 -0
- redis-7.0.1/redis/multidb/config.py +207 -0
- redis-7.0.1/redis/multidb/database.py +130 -0
- redis-7.0.1/redis/multidb/event.py +89 -0
- redis-7.0.1/redis/multidb/exception.py +17 -0
- redis-7.0.1/redis/multidb/failover.py +125 -0
- redis-7.0.1/redis/multidb/failure_detector.py +104 -0
- redis-7.0.1/redis/multidb/healthcheck.py +282 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/retry.py +14 -1
- {redis-7.0.0b2 → redis-7.0.1}/redis/utils.py +34 -0
- redis-7.0.1/tests/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/conftest.py +16 -0
- redis-7.0.1/tests/test_asyncio/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_commands.py +60 -7
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_connection_pool.py +31 -5
- redis-7.0.1/tests/test_asyncio/test_multidb/__init__.py +0 -0
- redis-7.0.1/tests/test_asyncio/test_multidb/conftest.py +131 -0
- redis-7.0.1/tests/test_asyncio/test_multidb/test_client.py +628 -0
- redis-7.0.1/tests/test_asyncio/test_multidb/test_command_executor.py +181 -0
- redis-7.0.1/tests/test_asyncio/test_multidb/test_config.py +166 -0
- redis-7.0.1/tests/test_asyncio/test_multidb/test_failover.py +169 -0
- redis-7.0.1/tests/test_asyncio/test_multidb/test_failure_detector.py +128 -0
- redis-7.0.1/tests/test_asyncio/test_multidb/test_healthcheck.py +401 -0
- redis-7.0.1/tests/test_asyncio/test_multidb/test_pipeline.py +488 -0
- redis-7.0.1/tests/test_asyncio/test_scenario/__init__.py +0 -0
- redis-7.0.1/tests/test_asyncio/test_scenario/conftest.py +116 -0
- redis-7.0.1/tests/test_asyncio/test_scenario/test_active_active.py +420 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_search.py +17 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_vsets.py +2 -2
- redis-7.0.1/tests/test_auth/__init__.py +0 -0
- redis-7.0.1/tests/test_background.py +94 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_commands.py +108 -13
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_connection_pool.py +37 -1
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_credentials.py +6 -1
- redis-7.0.1/tests/test_data_structure.py +94 -0
- redis-7.0.1/tests/test_event.py +67 -0
- redis-7.0.1/tests/test_http/__init__.py +0 -0
- redis-7.0.1/tests/test_http/test_http_client.py +371 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_maint_notifications.py +5 -2
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_maint_notifications_handling.py +114 -98
- redis-7.0.1/tests/test_multidb/__init__.py +0 -0
- redis-7.0.1/tests/test_multidb/conftest.py +131 -0
- redis-7.0.1/tests/test_multidb/test_circuit.py +57 -0
- redis-7.0.1/tests/test_multidb/test_client.py +615 -0
- redis-7.0.1/tests/test_multidb/test_command_executor.py +173 -0
- redis-7.0.1/tests/test_multidb/test_config.py +161 -0
- redis-7.0.1/tests/test_multidb/test_failover.py +161 -0
- redis-7.0.1/tests/test_multidb/test_failure_detector.py +117 -0
- redis-7.0.1/tests/test_multidb/test_healthcheck.py +385 -0
- redis-7.0.1/tests/test_multidb/test_pipeline.py +489 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_multiprocessing.py +8 -0
- redis-7.0.1/tests/test_parsers/test_errors.py +167 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_parsers/test_helpers.py +21 -1
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_pubsub.py +10 -2
- redis-7.0.1/tests/test_scenario/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_scenario/conftest.py +119 -7
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_scenario/maint_notifications_helpers.py +29 -14
- redis-7.0.1/tests/test_scenario/test_active_active.py +460 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_scenario/test_maint_notifications.py +9 -24
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_search.py +15 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_vsets.py +2 -2
- redis-7.0.0b2/dev_requirements.txt +0 -16
- {redis-7.0.0b2 → redis-7.0.1}/.gitignore +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/LICENSE +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/_parsers/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/_parsers/commands.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/_parsers/encoders.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/_parsers/hiredis.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/_parsers/resp2.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/_parsers/resp3.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/_parsers/socket.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/asyncio/__init__.py +0 -0
- {redis-7.0.0b2/redis/auth → redis-7.0.1/redis/asyncio/http}/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/asyncio/lock.py +0 -0
- {redis-7.0.0b2/tests → redis-7.0.1/redis/asyncio/multidb}/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/asyncio/retry.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/asyncio/sentinel.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/asyncio/utils.py +0 -0
- {redis-7.0.0b2/tests/test_asyncio → redis-7.0.1/redis/auth}/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/auth/err.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/auth/idp.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/auth/token.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/auth/token_manager.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/backoff.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/cache.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/bf/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/bf/commands.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/bf/info.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/cluster.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/helpers.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/json/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/json/_util.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/json/decoders.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/json/path.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/redismodules.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/_util.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/dialect.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/document.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/field.py +2 -2
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/index_definition.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/profile_information.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/querystring.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/reducers.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/result.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/search/suggestion.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/sentinel.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/timeseries/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/timeseries/commands.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/timeseries/info.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/timeseries/utils.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/vectorset/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/vectorset/commands.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/commands/vectorset/utils.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/crc.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/credentials.py +0 -0
- {redis-7.0.0b2/tests/test_auth → redis-7.0.1/redis/http}/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/lock.py +0 -0
- {redis-7.0.0b2/tests/test_scenario → redis-7.0.1/redis/multidb}/__init__.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/ocsp.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/py.typed +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/sentinel.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/redis/typing.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/entraid_utils.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/mocks.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/ssl_utils.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/compat.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/conftest.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/mocks.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_bloom.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_cluster.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_cluster_transaction.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_connect.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_connection.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_credentials.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_cwe_404.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_encoding.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_hash.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_json.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_lock.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_monitor.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_pipeline.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_pubsub.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_retry.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_scripting.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_sentinel.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_sentinel_managed_connection.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_ssl.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_timeseries.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_usage_counter.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/test_utils.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/testdata/jsontestdata.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/testdata/titles.csv +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_asyncio/testdata/will_play_text.csv.bz2 +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_auth/test_token.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_auth/test_token_manager.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_backoff.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_bloom.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_cache.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_cluster.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_cluster_transaction.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_command_parser.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_connect.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_connection.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_encoding.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_function.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_hash.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_helpers.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_json.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_lock.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_max_connections_error.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_monitor.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_pipeline.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_retry.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_scenario/fault_injector_client.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_scripting.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_sentinel.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_sentinel_managed_connection.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_ssl.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_timeseries.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/test_utils.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/testdata/jsontestdata.py +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/testdata/titles.csv +0 -0
- {redis-7.0.0b2 → redis-7.0.1}/tests/testdata/will_play_text.csv.bz2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: redis
|
|
3
|
-
Version: 7.0.
|
|
3
|
+
Version: 7.0.1
|
|
4
4
|
Summary: Python client for Redis database and key-value store
|
|
5
5
|
Project-URL: Changes, https://github.com/redis/redis-py/releases
|
|
6
6
|
Project-URL: Code, https://github.com/redis/redis-py
|
|
@@ -28,6 +28,8 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
28
28
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
29
29
|
Requires-Python: >=3.9
|
|
30
30
|
Requires-Dist: async-timeout>=4.0.3; python_full_version < '3.11.3'
|
|
31
|
+
Provides-Extra: circuit-breaker
|
|
32
|
+
Requires-Dist: pybreaker>=1.4.0; extra == 'circuit-breaker'
|
|
31
33
|
Provides-Extra: hiredis
|
|
32
34
|
Requires-Dist: hiredis>=3.2.0; extra == 'hiredis'
|
|
33
35
|
Provides-Extra: jwt
|
|
@@ -53,8 +55,9 @@ The Python interface to the Redis key-value store.
|
|
|
53
55
|
|
|
54
56
|
---------------------------------------------
|
|
55
57
|
|
|
56
|
-
**Note:** redis-py 5.0
|
|
57
|
-
**Note:** redis-py 6.1.0
|
|
58
|
+
**Note:** redis-py 5.0 is the last version of redis-py that supports Python 3.7, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 5.1 supports Python 3.8+.<br>
|
|
59
|
+
**Note:** redis-py 6.1.0 is the last version of redis-py that supports Python 3.8, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 6.2.0 supports Python 3.9+.
|
|
60
|
+
|
|
58
61
|
---------------------------------------------
|
|
59
62
|
|
|
60
63
|
## How do I Redis?
|
|
@@ -99,7 +102,7 @@ Looking for a high-level library to handle object mapping? See [redis-om-python]
|
|
|
99
102
|
|
|
100
103
|
## Supported Redis Versions
|
|
101
104
|
|
|
102
|
-
The most recent version of this library supports Redis version [7.2](https://github.com/redis/redis/blob/7.2/00-RELEASENOTES), [7.4](https://github.com/redis/redis/blob/7.4/00-RELEASENOTES)
|
|
105
|
+
The most recent version of this library supports Redis version [7.2](https://github.com/redis/redis/blob/7.2/00-RELEASENOTES), [7.4](https://github.com/redis/redis/blob/7.4/00-RELEASENOTES), [8.0](https://github.com/redis/redis/blob/8.0/00-RELEASENOTES) and [8.2](https://github.com/redis/redis/blob/8.2/00-RELEASENOTES).
|
|
103
106
|
|
|
104
107
|
The table below highlights version compatibility of the most-recent library versions and redis versions.
|
|
105
108
|
|
|
@@ -233,6 +236,16 @@ By default, the client now overrides the server-side dialect with version 2, aut
|
|
|
233
236
|
|
|
234
237
|
You can find further details in the [query dialect documentation](https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/dialects/).
|
|
235
238
|
|
|
239
|
+
### Multi-database client (Active-Active)
|
|
240
|
+
|
|
241
|
+
The multi-database client allows your application to connect to multiple Redis databases, which are typically replicas of each other. It is designed to work with Redis Software and Redis Cloud Active-Active setups. The client continuously monitors database health, detects failures, and automatically fails over to the next healthy database using a configurable strategy. When the original database becomes healthy again, the client can automatically switch back to it.<br>
|
|
242
|
+
This is useful when:
|
|
243
|
+
|
|
244
|
+
1. You have more than one Redis deployment. This might include two independent Redis servers or two or more Redis databases replicated across multiple [active-active Redis Enterprise](https://redis.io/docs/latest/operate/rs/databases/active-active/) clusters.
|
|
245
|
+
2. You want your application to connect to one deployment at a time and to fail over to the next available deployment if the first deployment becomes unavailable.
|
|
246
|
+
|
|
247
|
+
For the complete failover configuration options and examples, see the [Multi-database client docs](https://redis.readthedocs.io/en/latest/multi_database.html).
|
|
248
|
+
|
|
236
249
|
---------------------------------------------
|
|
237
250
|
|
|
238
251
|
### Author
|
|
@@ -13,8 +13,9 @@ The Python interface to the Redis key-value store.
|
|
|
13
13
|
|
|
14
14
|
---------------------------------------------
|
|
15
15
|
|
|
16
|
-
**Note:** redis-py 5.0
|
|
17
|
-
**Note:** redis-py 6.1.0
|
|
16
|
+
**Note:** redis-py 5.0 is the last version of redis-py that supports Python 3.7, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 5.1 supports Python 3.8+.<br>
|
|
17
|
+
**Note:** redis-py 6.1.0 is the last version of redis-py that supports Python 3.8, as it has reached [end of life](https://devguide.python.org/versions/). redis-py 6.2.0 supports Python 3.9+.
|
|
18
|
+
|
|
18
19
|
---------------------------------------------
|
|
19
20
|
|
|
20
21
|
## How do I Redis?
|
|
@@ -59,7 +60,7 @@ Looking for a high-level library to handle object mapping? See [redis-om-python]
|
|
|
59
60
|
|
|
60
61
|
## Supported Redis Versions
|
|
61
62
|
|
|
62
|
-
The most recent version of this library supports Redis version [7.2](https://github.com/redis/redis/blob/7.2/00-RELEASENOTES), [7.4](https://github.com/redis/redis/blob/7.4/00-RELEASENOTES)
|
|
63
|
+
The most recent version of this library supports Redis version [7.2](https://github.com/redis/redis/blob/7.2/00-RELEASENOTES), [7.4](https://github.com/redis/redis/blob/7.4/00-RELEASENOTES), [8.0](https://github.com/redis/redis/blob/8.0/00-RELEASENOTES) and [8.2](https://github.com/redis/redis/blob/8.2/00-RELEASENOTES).
|
|
63
64
|
|
|
64
65
|
The table below highlights version compatibility of the most-recent library versions and redis versions.
|
|
65
66
|
|
|
@@ -193,6 +194,16 @@ By default, the client now overrides the server-side dialect with version 2, aut
|
|
|
193
194
|
|
|
194
195
|
You can find further details in the [query dialect documentation](https://redis.io/docs/latest/develop/interact/search-and-query/advanced-concepts/dialects/).
|
|
195
196
|
|
|
197
|
+
### Multi-database client (Active-Active)
|
|
198
|
+
|
|
199
|
+
The multi-database client allows your application to connect to multiple Redis databases, which are typically replicas of each other. It is designed to work with Redis Software and Redis Cloud Active-Active setups. The client continuously monitors database health, detects failures, and automatically fails over to the next healthy database using a configurable strategy. When the original database becomes healthy again, the client can automatically switch back to it.<br>
|
|
200
|
+
This is useful when:
|
|
201
|
+
|
|
202
|
+
1. You have more than one Redis deployment. This might include two independent Redis servers or two or more Redis databases replicated across multiple [active-active Redis Enterprise](https://redis.io/docs/latest/operate/rs/databases/active-active/) clusters.
|
|
203
|
+
2. You want your application to connect to one deployment at a time and to fail over to the next available deployment if the first deployment becomes unavailable.
|
|
204
|
+
|
|
205
|
+
For the complete failover configuration options and examples, see the [Multi-database client docs](https://redis.readthedocs.io/en/latest/multi_database.html).
|
|
206
|
+
|
|
196
207
|
---------------------------------------------
|
|
197
208
|
|
|
198
209
|
### Author
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
build
|
|
2
|
+
build==1.2.2.post1 ; platform_python_implementation == "PyPy" or python_version < "3.10"
|
|
3
|
+
click==8.0.4
|
|
4
|
+
invoke==2.2.0
|
|
5
|
+
mock
|
|
6
|
+
mock==5.1.0 ; platform_python_implementation == "PyPy" or python_version < "3.10"
|
|
7
|
+
packaging>=20.4
|
|
8
|
+
packaging==24.2 ; platform_python_implementation == "PyPy" or python_version < "3.10"
|
|
9
|
+
|
|
10
|
+
pytest
|
|
11
|
+
pytest==8.3.4 ; platform_python_implementation == "PyPy" or python_version < "3.10"
|
|
12
|
+
pytest-asyncio>=0.23.0
|
|
13
|
+
pytest-asyncio==1.1.0 ; platform_python_implementation == "PyPy" or python_version < "3.10"
|
|
14
|
+
pytest-cov
|
|
15
|
+
pytest-cov==6.0.0 ; platform_python_implementation == "PyPy" or python_version < "3.10"
|
|
16
|
+
coverage==7.6.12 ; platform_python_implementation == "PyPy" or python_version < "3.10"
|
|
17
|
+
pytest-profiling==1.8.1
|
|
18
|
+
pytest-timeout
|
|
19
|
+
pytest-timeout==2.3.1 ; platform_python_implementation == "PyPy" or python_version < "3.10"
|
|
20
|
+
|
|
21
|
+
ruff==0.9.6
|
|
22
|
+
ujson>=4.2.0
|
|
23
|
+
uvloop<=0.21.0; platform_python_implementation == "CPython"
|
|
24
|
+
vulture>=2.3.0
|
|
25
|
+
|
|
26
|
+
numpy>=1.24.0 ; platform_python_implementation == "CPython"
|
|
27
|
+
numpy>=1.24.0,<2.0 ; platform_python_implementation == "PyPy" or python_version < "3.10"
|
|
28
|
+
|
|
29
|
+
redis-entraid==1.0.0
|
|
30
|
+
pybreaker>=1.4.0
|
|
@@ -42,6 +42,9 @@ ocsp = [
|
|
|
42
42
|
jwt = [
|
|
43
43
|
"PyJWT>=2.9.0",
|
|
44
44
|
]
|
|
45
|
+
circuit_breaker = [
|
|
46
|
+
"pybreaker>=1.4.0"
|
|
47
|
+
]
|
|
45
48
|
|
|
46
49
|
[project.urls]
|
|
47
50
|
Changes = "https://github.com/redis/redis-py/releases"
|
|
@@ -80,6 +83,12 @@ filterwarnings = [
|
|
|
80
83
|
# Ignore a coverage warning when COVERAGE_CORE=sysmon for Pythons < 3.12.
|
|
81
84
|
"ignore:sys.monitoring isn't available:coverage.exceptions.CoverageWarning",
|
|
82
85
|
]
|
|
86
|
+
log_cli_level = "INFO"
|
|
87
|
+
log_cli_date_format = "%H:%M:%S:%f"
|
|
88
|
+
log_cli = false
|
|
89
|
+
log_cli_format = "%(asctime)s %(levelname)s %(threadName)s: %(message)s"
|
|
90
|
+
log_level = "INFO"
|
|
91
|
+
capture = "yes"
|
|
83
92
|
|
|
84
93
|
[tool.ruff]
|
|
85
94
|
target-version = "py39"
|
|
@@ -27,6 +27,7 @@ from ..exceptions import (
|
|
|
27
27
|
ClusterDownError,
|
|
28
28
|
ConnectionError,
|
|
29
29
|
ExecAbortError,
|
|
30
|
+
ExternalAuthProviderError,
|
|
30
31
|
MasterDownError,
|
|
31
32
|
ModuleError,
|
|
32
33
|
MovedError,
|
|
@@ -60,6 +61,10 @@ NO_AUTH_SET_ERROR = {
|
|
|
60
61
|
"Client sent AUTH, but no password is set": AuthenticationError,
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
EXTERNAL_AUTH_PROVIDER_ERROR = {
|
|
65
|
+
"problem with LDAP service": ExternalAuthProviderError,
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
logger = logging.getLogger(__name__)
|
|
64
69
|
|
|
65
70
|
|
|
@@ -81,6 +86,7 @@ class BaseParser(ABC):
|
|
|
81
86
|
NO_SUCH_MODULE_ERROR: ModuleError,
|
|
82
87
|
MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError,
|
|
83
88
|
**NO_AUTH_SET_ERROR,
|
|
89
|
+
**EXTERNAL_AUTH_PROVIDER_ERROR,
|
|
84
90
|
},
|
|
85
91
|
"OOM": OutOfMemoryError,
|
|
86
92
|
"WRONGPASS": AuthenticationError,
|
|
@@ -224,6 +224,39 @@ def zset_score_pairs(response, **options):
|
|
|
224
224
|
return list(zip(it, map(score_cast_func, it)))
|
|
225
225
|
|
|
226
226
|
|
|
227
|
+
def zset_score_for_rank(response, **options):
|
|
228
|
+
"""
|
|
229
|
+
If ``withscores`` is specified in the options, return the response as
|
|
230
|
+
a [value, score] pair
|
|
231
|
+
"""
|
|
232
|
+
if not response or not options.get("withscore"):
|
|
233
|
+
return response
|
|
234
|
+
score_cast_func = options.get("score_cast_func", float)
|
|
235
|
+
return [response[0], score_cast_func(response[1])]
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def zset_score_pairs_resp3(response, **options):
|
|
239
|
+
"""
|
|
240
|
+
If ``withscores`` is specified in the options, return the response as
|
|
241
|
+
a list of [value, score] pairs
|
|
242
|
+
"""
|
|
243
|
+
if not response or not options.get("withscores"):
|
|
244
|
+
return response
|
|
245
|
+
score_cast_func = options.get("score_cast_func", float)
|
|
246
|
+
return [[name, score_cast_func(val)] for name, val in response]
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def zset_score_for_rank_resp3(response, **options):
|
|
250
|
+
"""
|
|
251
|
+
If ``withscores`` is specified in the options, return the response as
|
|
252
|
+
a [value, score] pair
|
|
253
|
+
"""
|
|
254
|
+
if not response or not options.get("withscore"):
|
|
255
|
+
return response
|
|
256
|
+
score_cast_func = options.get("score_cast_func", float)
|
|
257
|
+
return [response[0], score_cast_func(response[1])]
|
|
258
|
+
|
|
259
|
+
|
|
227
260
|
def sort_return_tuples(response, **options):
|
|
228
261
|
"""
|
|
229
262
|
If ``groups`` is specified, return the response as a list of
|
|
@@ -349,8 +382,22 @@ def parse_zadd(response, **options):
|
|
|
349
382
|
def parse_client_list(response, **options):
|
|
350
383
|
clients = []
|
|
351
384
|
for c in str_if_bytes(response).splitlines():
|
|
352
|
-
|
|
353
|
-
|
|
385
|
+
client_dict = {}
|
|
386
|
+
tokens = c.split(" ")
|
|
387
|
+
last_key = None
|
|
388
|
+
for token in tokens:
|
|
389
|
+
if "=" in token:
|
|
390
|
+
# Values might contain '='
|
|
391
|
+
key, value = token.split("=", 1)
|
|
392
|
+
client_dict[key] = value
|
|
393
|
+
last_key = key
|
|
394
|
+
else:
|
|
395
|
+
# Values may include spaces. For instance, when running Redis via a Unix socket — such as
|
|
396
|
+
# "/tmp/redis sock/redis.sock" — the addr or laddr field will include a space.
|
|
397
|
+
client_dict[last_key] += " " + token
|
|
398
|
+
|
|
399
|
+
if client_dict:
|
|
400
|
+
clients.append(client_dict)
|
|
354
401
|
return clients
|
|
355
402
|
|
|
356
403
|
|
|
@@ -797,10 +844,14 @@ _RedisCallbacksRESP2 = {
|
|
|
797
844
|
"SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
|
|
798
845
|
),
|
|
799
846
|
**string_keys_to_dict(
|
|
800
|
-
"ZDIFF ZINTER ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYSCORE
|
|
801
|
-
"ZREVRANGEBYSCORE
|
|
847
|
+
"ZDIFF ZINTER ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYSCORE ZREVRANGE "
|
|
848
|
+
"ZREVRANGEBYSCORE ZUNION",
|
|
802
849
|
zset_score_pairs,
|
|
803
850
|
),
|
|
851
|
+
**string_keys_to_dict(
|
|
852
|
+
"ZREVRANK ZRANK",
|
|
853
|
+
zset_score_for_rank,
|
|
854
|
+
),
|
|
804
855
|
**string_keys_to_dict("ZINCRBY ZSCORE", float_or_none),
|
|
805
856
|
**string_keys_to_dict("BGREWRITEAOF BGSAVE", lambda r: True),
|
|
806
857
|
**string_keys_to_dict("BLPOP BRPOP", lambda r: r and tuple(r) or None),
|
|
@@ -844,10 +895,17 @@ _RedisCallbacksRESP3 = {
|
|
|
844
895
|
"SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
|
|
845
896
|
),
|
|
846
897
|
**string_keys_to_dict(
|
|
847
|
-
"ZRANGE ZINTER ZPOPMAX ZPOPMIN
|
|
848
|
-
"ZUNION HGETALL XREADGROUP",
|
|
898
|
+
"ZRANGE ZINTER ZPOPMAX ZPOPMIN HGETALL XREADGROUP",
|
|
849
899
|
lambda r, **kwargs: r,
|
|
850
900
|
),
|
|
901
|
+
**string_keys_to_dict(
|
|
902
|
+
"ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
|
|
903
|
+
zset_score_pairs_resp3,
|
|
904
|
+
),
|
|
905
|
+
**string_keys_to_dict(
|
|
906
|
+
"ZREVRANK ZRANK",
|
|
907
|
+
zset_score_for_rank_resp3,
|
|
908
|
+
),
|
|
851
909
|
**string_keys_to_dict("XREAD XREADGROUP", parse_xread_resp3),
|
|
852
910
|
"ACL LOG": lambda r: (
|
|
853
911
|
[
|
|
@@ -1161,9 +1161,12 @@ class PubSub:
|
|
|
1161
1161
|
return await self.handle_message(response, ignore_subscribe_messages)
|
|
1162
1162
|
return None
|
|
1163
1163
|
|
|
1164
|
-
def ping(self, message=None) -> Awaitable:
|
|
1164
|
+
def ping(self, message=None) -> Awaitable[bool]:
|
|
1165
1165
|
"""
|
|
1166
|
-
Ping the Redis server
|
|
1166
|
+
Ping the Redis server to test connectivity.
|
|
1167
|
+
|
|
1168
|
+
Sends a PING command to the Redis server and returns True if the server
|
|
1169
|
+
responds with "PONG".
|
|
1167
1170
|
"""
|
|
1168
1171
|
args = ["PING", message] if message is not None else ["PING"]
|
|
1169
1172
|
return self.execute_command(*args)
|
|
@@ -1239,6 +1242,7 @@ class PubSub:
|
|
|
1239
1242
|
*,
|
|
1240
1243
|
exception_handler: Optional["PSWorkerThreadExcHandlerT"] = None,
|
|
1241
1244
|
poll_timeout: float = 1.0,
|
|
1245
|
+
pubsub=None,
|
|
1242
1246
|
) -> None:
|
|
1243
1247
|
"""Process pub/sub messages using registered callbacks.
|
|
1244
1248
|
|
|
@@ -1263,9 +1267,14 @@ class PubSub:
|
|
|
1263
1267
|
await self.connect()
|
|
1264
1268
|
while True:
|
|
1265
1269
|
try:
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1270
|
+
if pubsub is None:
|
|
1271
|
+
await self.get_message(
|
|
1272
|
+
ignore_subscribe_messages=True, timeout=poll_timeout
|
|
1273
|
+
)
|
|
1274
|
+
else:
|
|
1275
|
+
await pubsub.get_message(
|
|
1276
|
+
ignore_subscribe_messages=True, timeout=poll_timeout
|
|
1277
|
+
)
|
|
1269
1278
|
except asyncio.CancelledError:
|
|
1270
1279
|
raise
|
|
1271
1280
|
except BaseException as e:
|
|
@@ -409,6 +409,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
409
409
|
else:
|
|
410
410
|
self._event_dispatcher = event_dispatcher
|
|
411
411
|
|
|
412
|
+
self.startup_nodes = startup_nodes
|
|
412
413
|
self.nodes_manager = NodesManager(
|
|
413
414
|
startup_nodes,
|
|
414
415
|
require_full_coverage,
|
|
@@ -2253,7 +2254,10 @@ class TransactionStrategy(AbstractStrategy):
|
|
|
2253
2254
|
await self._pipe.cluster_client.nodes_manager.initialize()
|
|
2254
2255
|
self.reinitialize_counter = 0
|
|
2255
2256
|
else:
|
|
2256
|
-
|
|
2257
|
+
if isinstance(error, AskError):
|
|
2258
|
+
self._pipe.cluster_client.nodes_manager.update_moved_exception(
|
|
2259
|
+
error
|
|
2260
|
+
)
|
|
2257
2261
|
|
|
2258
2262
|
self._executing = False
|
|
2259
2263
|
|
|
@@ -213,6 +213,7 @@ class AbstractConnection:
|
|
|
213
213
|
self._connect_callbacks: List[weakref.WeakMethod[ConnectCallbackT]] = []
|
|
214
214
|
self._buffer_cutoff = 6000
|
|
215
215
|
self._re_auth_token: Optional[TokenInterface] = None
|
|
216
|
+
self._should_reconnect = False
|
|
216
217
|
|
|
217
218
|
try:
|
|
218
219
|
p = int(protocol)
|
|
@@ -343,6 +344,12 @@ class AbstractConnection:
|
|
|
343
344
|
if task and inspect.isawaitable(task):
|
|
344
345
|
await task
|
|
345
346
|
|
|
347
|
+
def mark_for_reconnect(self):
|
|
348
|
+
self._should_reconnect = True
|
|
349
|
+
|
|
350
|
+
def should_reconnect(self):
|
|
351
|
+
return self._should_reconnect
|
|
352
|
+
|
|
346
353
|
@abstractmethod
|
|
347
354
|
async def _connect(self):
|
|
348
355
|
pass
|
|
@@ -1240,6 +1247,9 @@ class ConnectionPool:
|
|
|
1240
1247
|
# Connections should always be returned to the correct pool,
|
|
1241
1248
|
# not doing so is an error that will cause an exception here.
|
|
1242
1249
|
self._in_use_connections.remove(connection)
|
|
1250
|
+
if connection.should_reconnect():
|
|
1251
|
+
await connection.disconnect()
|
|
1252
|
+
|
|
1243
1253
|
self._available_connections.append(connection)
|
|
1244
1254
|
await self._event_dispatcher.dispatch_async(
|
|
1245
1255
|
AsyncAfterConnectionReleasedEvent(connection)
|
|
@@ -1267,6 +1277,14 @@ class ConnectionPool:
|
|
|
1267
1277
|
if exc:
|
|
1268
1278
|
raise exc
|
|
1269
1279
|
|
|
1280
|
+
async def update_active_connections_for_reconnect(self):
|
|
1281
|
+
"""
|
|
1282
|
+
Mark all active connections for reconnect.
|
|
1283
|
+
"""
|
|
1284
|
+
async with self._lock:
|
|
1285
|
+
for conn in self._in_use_connections:
|
|
1286
|
+
conn.mark_for_reconnect()
|
|
1287
|
+
|
|
1270
1288
|
async def aclose(self) -> None:
|
|
1271
1289
|
"""Close the pool, disconnecting all connections"""
|
|
1272
1290
|
await self.disconnect()
|
|
@@ -1338,7 +1356,7 @@ class BlockingConnectionPool(ConnectionPool):
|
|
|
1338
1356
|
def __init__(
|
|
1339
1357
|
self,
|
|
1340
1358
|
max_connections: int = 50,
|
|
1341
|
-
timeout: Optional[
|
|
1359
|
+
timeout: Optional[float] = 20,
|
|
1342
1360
|
connection_class: Type[AbstractConnection] = Connection,
|
|
1343
1361
|
queue_class: Type[asyncio.Queue] = asyncio.LifoQueue, # deprecated
|
|
1344
1362
|
**connection_kwargs,
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
from typing import Any, Mapping, Optional, Union
|
|
5
|
+
|
|
6
|
+
from redis.http.http_client import HttpClient, HttpResponse
|
|
7
|
+
|
|
8
|
+
DEFAULT_USER_AGENT = "HttpClient/1.0 (+https://example.invalid)"
|
|
9
|
+
DEFAULT_TIMEOUT = 30.0
|
|
10
|
+
RETRY_STATUS_CODES = {429, 500, 502, 503, 504}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AsyncHTTPClient(ABC):
|
|
14
|
+
@abstractmethod
|
|
15
|
+
async def get(
|
|
16
|
+
self,
|
|
17
|
+
path: str,
|
|
18
|
+
params: Optional[
|
|
19
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
20
|
+
] = None,
|
|
21
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
22
|
+
timeout: Optional[float] = None,
|
|
23
|
+
expect_json: bool = True,
|
|
24
|
+
) -> Union[HttpResponse, Any]:
|
|
25
|
+
"""
|
|
26
|
+
Invoke HTTP GET request."""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
async def delete(
|
|
31
|
+
self,
|
|
32
|
+
path: str,
|
|
33
|
+
params: Optional[
|
|
34
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
35
|
+
] = None,
|
|
36
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
37
|
+
timeout: Optional[float] = None,
|
|
38
|
+
expect_json: bool = True,
|
|
39
|
+
) -> Union[HttpResponse, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Invoke HTTP DELETE request."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
async def post(
|
|
46
|
+
self,
|
|
47
|
+
path: str,
|
|
48
|
+
json_body: Optional[Any] = None,
|
|
49
|
+
data: Optional[Union[bytes, str]] = None,
|
|
50
|
+
params: Optional[
|
|
51
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
52
|
+
] = None,
|
|
53
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
54
|
+
timeout: Optional[float] = None,
|
|
55
|
+
expect_json: bool = True,
|
|
56
|
+
) -> Union[HttpResponse, Any]:
|
|
57
|
+
"""
|
|
58
|
+
Invoke HTTP POST request."""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
async def put(
|
|
63
|
+
self,
|
|
64
|
+
path: str,
|
|
65
|
+
json_body: Optional[Any] = None,
|
|
66
|
+
data: Optional[Union[bytes, str]] = None,
|
|
67
|
+
params: Optional[
|
|
68
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
69
|
+
] = None,
|
|
70
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
71
|
+
timeout: Optional[float] = None,
|
|
72
|
+
expect_json: bool = True,
|
|
73
|
+
) -> Union[HttpResponse, Any]:
|
|
74
|
+
"""
|
|
75
|
+
Invoke HTTP PUT request."""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
async def patch(
|
|
80
|
+
self,
|
|
81
|
+
path: str,
|
|
82
|
+
json_body: Optional[Any] = None,
|
|
83
|
+
data: Optional[Union[bytes, str]] = None,
|
|
84
|
+
params: Optional[
|
|
85
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
86
|
+
] = None,
|
|
87
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
88
|
+
timeout: Optional[float] = None,
|
|
89
|
+
expect_json: bool = True,
|
|
90
|
+
) -> Union[HttpResponse, Any]:
|
|
91
|
+
"""
|
|
92
|
+
Invoke HTTP PATCH request."""
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
@abstractmethod
|
|
96
|
+
async def request(
|
|
97
|
+
self,
|
|
98
|
+
method: str,
|
|
99
|
+
path: str,
|
|
100
|
+
params: Optional[
|
|
101
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
102
|
+
] = None,
|
|
103
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
104
|
+
body: Optional[Union[bytes, str]] = None,
|
|
105
|
+
timeout: Optional[float] = None,
|
|
106
|
+
) -> HttpResponse:
|
|
107
|
+
"""
|
|
108
|
+
Invoke HTTP request with given method."""
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class AsyncHTTPClientWrapper(AsyncHTTPClient):
|
|
113
|
+
"""
|
|
114
|
+
An async wrapper around sync HTTP client with thread pool execution.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __init__(self, client: HttpClient, max_workers: int = 10) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Initialize a new HTTP client instance.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
client: Sync HTTP client instance.
|
|
123
|
+
max_workers: Maximum number of concurrent requests.
|
|
124
|
+
|
|
125
|
+
The client supports both regular HTTPS with server verification and mutual TLS
|
|
126
|
+
authentication. For server verification, provide CA certificate information via
|
|
127
|
+
ca_file, ca_path or ca_data. For mutual TLS, additionally provide a client
|
|
128
|
+
certificate and key via client_cert_file and client_key_file.
|
|
129
|
+
"""
|
|
130
|
+
self.client = client
|
|
131
|
+
self._executor = ThreadPoolExecutor(max_workers=max_workers)
|
|
132
|
+
|
|
133
|
+
async def get(
|
|
134
|
+
self,
|
|
135
|
+
path: str,
|
|
136
|
+
params: Optional[
|
|
137
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
138
|
+
] = None,
|
|
139
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
140
|
+
timeout: Optional[float] = None,
|
|
141
|
+
expect_json: bool = True,
|
|
142
|
+
) -> Union[HttpResponse, Any]:
|
|
143
|
+
loop = asyncio.get_event_loop()
|
|
144
|
+
return await loop.run_in_executor(
|
|
145
|
+
self._executor, self.client.get, path, params, headers, timeout, expect_json
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
async def delete(
|
|
149
|
+
self,
|
|
150
|
+
path: str,
|
|
151
|
+
params: Optional[
|
|
152
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
153
|
+
] = None,
|
|
154
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
155
|
+
timeout: Optional[float] = None,
|
|
156
|
+
expect_json: bool = True,
|
|
157
|
+
) -> Union[HttpResponse, Any]:
|
|
158
|
+
loop = asyncio.get_event_loop()
|
|
159
|
+
return await loop.run_in_executor(
|
|
160
|
+
self._executor,
|
|
161
|
+
self.client.delete,
|
|
162
|
+
path,
|
|
163
|
+
params,
|
|
164
|
+
headers,
|
|
165
|
+
timeout,
|
|
166
|
+
expect_json,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
async def post(
|
|
170
|
+
self,
|
|
171
|
+
path: str,
|
|
172
|
+
json_body: Optional[Any] = None,
|
|
173
|
+
data: Optional[Union[bytes, str]] = None,
|
|
174
|
+
params: Optional[
|
|
175
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
176
|
+
] = None,
|
|
177
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
178
|
+
timeout: Optional[float] = None,
|
|
179
|
+
expect_json: bool = True,
|
|
180
|
+
) -> Union[HttpResponse, Any]:
|
|
181
|
+
loop = asyncio.get_event_loop()
|
|
182
|
+
return await loop.run_in_executor(
|
|
183
|
+
self._executor,
|
|
184
|
+
self.client.post,
|
|
185
|
+
path,
|
|
186
|
+
json_body,
|
|
187
|
+
data,
|
|
188
|
+
params,
|
|
189
|
+
headers,
|
|
190
|
+
timeout,
|
|
191
|
+
expect_json,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
async def put(
|
|
195
|
+
self,
|
|
196
|
+
path: str,
|
|
197
|
+
json_body: Optional[Any] = None,
|
|
198
|
+
data: Optional[Union[bytes, str]] = None,
|
|
199
|
+
params: Optional[
|
|
200
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
201
|
+
] = None,
|
|
202
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
203
|
+
timeout: Optional[float] = None,
|
|
204
|
+
expect_json: bool = True,
|
|
205
|
+
) -> Union[HttpResponse, Any]:
|
|
206
|
+
loop = asyncio.get_event_loop()
|
|
207
|
+
return await loop.run_in_executor(
|
|
208
|
+
self._executor,
|
|
209
|
+
self.client.put,
|
|
210
|
+
path,
|
|
211
|
+
json_body,
|
|
212
|
+
data,
|
|
213
|
+
params,
|
|
214
|
+
headers,
|
|
215
|
+
timeout,
|
|
216
|
+
expect_json,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
async def patch(
|
|
220
|
+
self,
|
|
221
|
+
path: str,
|
|
222
|
+
json_body: Optional[Any] = None,
|
|
223
|
+
data: Optional[Union[bytes, str]] = None,
|
|
224
|
+
params: Optional[
|
|
225
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
226
|
+
] = None,
|
|
227
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
228
|
+
timeout: Optional[float] = None,
|
|
229
|
+
expect_json: bool = True,
|
|
230
|
+
) -> Union[HttpResponse, Any]:
|
|
231
|
+
loop = asyncio.get_event_loop()
|
|
232
|
+
return await loop.run_in_executor(
|
|
233
|
+
self._executor,
|
|
234
|
+
self.client.patch,
|
|
235
|
+
path,
|
|
236
|
+
json_body,
|
|
237
|
+
data,
|
|
238
|
+
params,
|
|
239
|
+
headers,
|
|
240
|
+
timeout,
|
|
241
|
+
expect_json,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
async def request(
|
|
245
|
+
self,
|
|
246
|
+
method: str,
|
|
247
|
+
path: str,
|
|
248
|
+
params: Optional[
|
|
249
|
+
Mapping[str, Union[None, str, int, float, bool, list, tuple]]
|
|
250
|
+
] = None,
|
|
251
|
+
headers: Optional[Mapping[str, str]] = None,
|
|
252
|
+
body: Optional[Union[bytes, str]] = None,
|
|
253
|
+
timeout: Optional[float] = None,
|
|
254
|
+
) -> HttpResponse:
|
|
255
|
+
loop = asyncio.get_event_loop()
|
|
256
|
+
return await loop.run_in_executor(
|
|
257
|
+
self._executor,
|
|
258
|
+
self.client.request,
|
|
259
|
+
method,
|
|
260
|
+
path,
|
|
261
|
+
params,
|
|
262
|
+
headers,
|
|
263
|
+
body,
|
|
264
|
+
timeout,
|
|
265
|
+
)
|