summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/release-drafter-config.yml2
-rw-r--r--.readthedocs.yml13
-rw-r--r--docs/conf.py16
-rw-r--r--docs/index.rst16
-rw-r--r--docs/make.bat190
-rw-r--r--docs/requirements.txt2
-rwxr-xr-xredis/client.py41
-rw-r--r--redis/commands/core.py10
-rw-r--r--redis/commands/redismodules.py38
-rw-r--r--redis/commands/search/__init__.py3
-rw-r--r--redis/commands/search/commands.py4
-rw-r--r--redis/commands/timeseries/__init__.py3
-rw-r--r--redis/lock.py8
-rw-r--r--tests/conftest.py18
-rw-r--r--tests/test_commands.py91
-rw-r--r--tests/test_connection.py26
-rw-r--r--tests/test_connection_pool.py12
-rw-r--r--tests/test_monitor.py15
-rw-r--r--tests/test_pubsub.py7
-rw-r--r--tests/test_scripting.py11
-rw-r--r--tests/test_search.py12
-rw-r--r--tox.ini3
22 files changed, 249 insertions, 292 deletions
diff --git a/.github/release-drafter-config.yml b/.github/release-drafter-config.yml
index f17a299..a3a5d83 100644
--- a/.github/release-drafter-config.yml
+++ b/.github/release-drafter-config.yml
@@ -15,7 +15,7 @@ autolabeler:
branch:
- '/feature-.+'
categories:
- - title: 'Breaking Changes'
+ - title: '🔥 Breaking Changes'
labels:
- 'breakingchange'
- title: '🚀 New Features'
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..80b9738
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,13 @@
+version: 2
+
+python:
+ install:
+ - requirements: ./docs/requirements.txt
+
+build:
+ os: ubuntu-20.04
+ tools:
+ python: "3.9"
+
+sphinx:
+ configuration: docs/conf.py
diff --git a/docs/conf.py b/docs/conf.py
index dfdaf9e..ff37119 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -46,16 +46,16 @@ master_doc = "index"
# General information about the project.
project = "redis-py"
-copyright = "2016, Andy McCurdy"
+copyright = "2021, Redis Inc."
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = "2.10.5"
+version = "4.0.9"
# The full version, including alpha/beta/rc tags.
-release = "2.10.5"
+release = "4.0.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -191,7 +191,7 @@ latex_documents = [
("index",
"redis-py.tex",
"redis-py Documentation",
- "Andy McCurdy",
+ "Redis Inc",
"manual"),
]
@@ -240,7 +240,7 @@ texinfo_documents = [
"index",
"redis-py",
"redis-py Documentation",
- "Andy McCurdy",
+ "Redis Inc",
"redis-py",
"One line description of project.",
"Miscellaneous",
@@ -257,6 +257,6 @@ texinfo_documents = [
# texinfo_show_urls = 'footnote'
epub_title = "redis-py"
-epub_author = "Andy McCurdy"
-epub_publisher = "Andy McCurdy"
-epub_copyright = "2011, Andy McCurdy"
+epub_author = "Redis Inc"
+epub_publisher = "Redis Inc"
+epub_copyright = "2021, Redis Inc"
diff --git a/docs/index.rst b/docs/index.rst
index bc1a4fa..8af5385 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -22,5 +22,21 @@ Contents:
.. automodule:: redis
:members:
+.. automodule:: redis.backoff
+ :members:
+
+.. automodule:: redis.connection
+ :members:
+
+.. automodule:: redis.commands
+ :members:
+
+.. automodule:: redis.exceptions
+ :members:
+
+.. automodule:: redis.lock
+ :members:
+
.. automodule:: redis.sentinel
:members:
+
diff --git a/docs/make.bat b/docs/make.bat
deleted file mode 100644
index bb2ae4d..0000000
--- a/docs/make.bat
+++ /dev/null
@@ -1,190 +0,0 @@
-@ECHO OFF
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set BUILDDIR=_build
-set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
-set I18NSPHINXOPTS=%SPHINXOPTS% .
-if NOT "%PAPER%" == "" (
- set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
- set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
-)
-
-if "%1" == "" goto help
-
-if "%1" == "help" (
- :help
- echo.Please use `make ^<target^>` where ^<target^> is one of
- echo. html to make standalone HTML files
- echo. dirhtml to make HTML files named index.html in directories
- echo. singlehtml to make a single large HTML file
- echo. pickle to make pickle files
- echo. json to make JSON files
- echo. htmlhelp to make HTML files and a HTML help project
- echo. qthelp to make HTML files and a qthelp project
- echo. devhelp to make HTML files and a Devhelp project
- echo. epub to make an epub
- echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
- echo. text to make text files
- echo. man to make manual pages
- echo. texinfo to make Texinfo files
- echo. gettext to make PO message catalogs
- echo. changes to make an overview over all changed/added/deprecated items
- echo. linkcheck to check all external links for integrity
- echo. doctest to run all doctests embedded in the documentation if enabled
- goto end
-)
-
-if "%1" == "clean" (
- for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
- del /q /s %BUILDDIR%\*
- goto end
-)
-
-if "%1" == "html" (
- %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/html.
- goto end
-)
-
-if "%1" == "dirhtml" (
- %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
- goto end
-)
-
-if "%1" == "singlehtml" (
- %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
- goto end
-)
-
-if "%1" == "pickle" (
- %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the pickle files.
- goto end
-)
-
-if "%1" == "json" (
- %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the JSON files.
- goto end
-)
-
-if "%1" == "htmlhelp" (
- %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run HTML Help Workshop with the ^
-.hhp project file in %BUILDDIR%/htmlhelp.
- goto end
-)
-
-if "%1" == "qthelp" (
- %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run "qcollectiongenerator" with the ^
-.qhcp project file in %BUILDDIR%/qthelp, like this:
- echo.^> qcollectiongenerator %BUILDDIR%\qthelp\redis-py.qhcp
- echo.To view the help file:
- echo.^> assistant -collectionFile %BUILDDIR%\qthelp\redis-py.ghc
- goto end
-)
-
-if "%1" == "devhelp" (
- %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished.
- goto end
-)
-
-if "%1" == "epub" (
- %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The epub file is in %BUILDDIR%/epub.
- goto end
-)
-
-if "%1" == "latex" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "text" (
- %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The text files are in %BUILDDIR%/text.
- goto end
-)
-
-if "%1" == "man" (
- %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The manual pages are in %BUILDDIR%/man.
- goto end
-)
-
-if "%1" == "texinfo" (
- %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
- goto end
-)
-
-if "%1" == "gettext" (
- %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
- goto end
-)
-
-if "%1" == "changes" (
- %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
- if errorlevel 1 exit /b 1
- echo.
- echo.The overview file is in %BUILDDIR%/changes.
- goto end
-)
-
-if "%1" == "linkcheck" (
- %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
- if errorlevel 1 exit /b 1
- echo.
- echo.Link check complete; look for any errors in the above output ^
-or in %BUILDDIR%/linkcheck/output.txt.
- goto end
-)
-
-if "%1" == "doctest" (
- %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
- if errorlevel 1 exit /b 1
- echo.
- echo.Testing of doctests in the sources finished, look at the ^
-results in %BUILDDIR%/doctest/output.txt.
- goto end
-)
-
-:end
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..2e1c4fb
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,2 @@
+sphinx<2
+docutils<0.18
diff --git a/redis/client.py b/redis/client.py
index f2f1eed..753770e 100755
--- a/redis/client.py
+++ b/redis/client.py
@@ -703,7 +703,6 @@ class Redis(RedisModuleCommands, CoreCommands, object):
'CLUSTER SET-CONFIG-EPOCH': bool_ok,
'CLUSTER SETSLOT': bool_ok,
'CLUSTER SLAVES': parse_cluster_nodes,
- 'COMMAND': int,
'COMMAND COUNT': int,
'CONFIG GET': parse_config_get,
'CONFIG RESETSTAT': bool_ok,
@@ -891,6 +890,12 @@ class Redis(RedisModuleCommands, CoreCommands, object):
self.response_callbacks = CaseInsensitiveDict(
self.__class__.RESPONSE_CALLBACKS)
+ # preload our class with the available redis commands
+ try:
+ self.__redis_commands__()
+ except RedisError:
+ pass
+
def __repr__(self):
return "%s<%s>" % (type(self).__name__, repr(self.connection_pool))
@@ -898,12 +903,12 @@ class Redis(RedisModuleCommands, CoreCommands, object):
"Set a custom Response Callback"
self.response_callbacks[command] = callback
- def load_external_module(self, modname, funcname, func):
+ def load_external_module(self, funcname, func,
+ ):
"""
This function can be used to add externally defined redis modules,
and their namespaces to the redis client.
- modname - A string containing the name of the redis module to look for
- in the redis info block.
+
funcname - A string containing the name of the function to create
func - The function, being added to this class.
@@ -914,31 +919,25 @@ class Redis(RedisModuleCommands, CoreCommands, object):
from redis import Redis
from foomodule import F
r = Redis()
- r.load_external_module("foomod", "foo", F)
+ r.load_external_module("foo", F)
r.foo().dothing('your', 'arguments')
For a concrete example see the reimport of the redisjson module in
tests/test_connection.py::test_loading_external_modules
"""
- mods = self.loaded_modules
- if modname.lower() not in mods:
- raise ModuleError("{} is not loaded in redis.".format(modname))
setattr(self, funcname, func)
- @property
- def loaded_modules(self):
- key = '__redis_modules__'
- mods = getattr(self, key, None)
- if mods is not None:
- return mods
-
+ def __redis_commands__(self):
+ """Store the list of available commands, for our redis instance."""
+ cmds = getattr(self, '__commands__', None)
+ if cmds is not None:
+ return cmds
try:
- mods = {f.get('name').lower(): f.get('ver')
- for f in self.info().get('modules')}
- except TypeError:
- mods = []
- setattr(self, key, mods)
- return mods
+ cmds = [c[0].upper().decode() for c in self.command()]
+ except AttributeError: # if encoded
+ cmds = [c[0].upper() for c in self.command()]
+ self.__commands__ = cmds
+ return cmds
def pipeline(self, transaction=True, shard_hint=None):
"""
diff --git a/redis/commands/core.py b/redis/commands/core.py
index 0344e3a..516e7d9 100644
--- a/redis/commands/core.py
+++ b/redis/commands/core.py
@@ -2520,10 +2520,13 @@ class CoreCommands:
``offset`` and ``num`` are specified, then return a slice of the range.
Can't be provided when using ``bylex``.
"""
- # Supports old implementation: need to support ``desc`` also for version < 6.2.0
- if not byscore and not bylex and (offset is None and num is None) and desc:
+ # Need to support ``desc`` also when using old redis version
+ # because it was supported in 3.5.3 (of redis-py)
+ if not byscore and not bylex and (offset is None and num is None) \
+ and desc:
return self.zrevrange(name, start, end, withscores,
score_cast_func)
+
return self._zrange('ZRANGE', None, name, start, end, desc, byscore,
bylex, withscores, score_cast_func, offset, num)
@@ -3312,6 +3315,9 @@ class CoreCommands:
def command_count(self):
return self.execute_command('COMMAND COUNT')
+ def command(self):
+ return self.execute_command('COMMAND')
+
class Script:
"An executable Lua script object returned by ``register_script``"
diff --git a/redis/commands/redismodules.py b/redis/commands/redismodules.py
index 457a69e..b3cbee1 100644
--- a/redis/commands/redismodules.py
+++ b/redis/commands/redismodules.py
@@ -8,41 +8,41 @@ class RedisModuleCommands:
"""
def json(self, encoder=JSONEncoder(), decoder=JSONDecoder()):
- """Access the json namespace, providing support for redis json."""
- try:
- modversion = self.loaded_modules['rejson']
- except IndexError:
- raise ModuleError("rejson is not a loaded in the redis instance.")
+ """Access the json namespace, providing support for redis json.
+ """
+ if 'JSON.SET' not in self.__commands__:
+ raise ModuleError("redisjson is not loaded in redis. "
+ "For more information visit "
+ "https://redisjson.io/")
from .json import JSON
jj = JSON(
client=self,
- version=modversion,
encoder=encoder,
decoder=decoder)
return jj
def ft(self, index_name="idx"):
- """Access the search namespace, providing support for redis search."""
- try:
- modversion = self.loaded_modules['search']
- except IndexError:
- raise ModuleError("search is not a loaded in the redis instance.")
+ """Access the search namespace, providing support for redis search.
+ """
+ if 'FT.INFO' not in self.__commands__:
+ raise ModuleError("redisearch is not loaded in redis. "
+ "For more information visit "
+ "https://redisearch.io/")
from .search import Search
- s = Search(client=self, version=modversion, index_name=index_name)
+ s = Search(client=self, index_name=index_name)
return s
- def ts(self, index_name="idx"):
+ def ts(self):
"""Access the timeseries namespace, providing support for
redis timeseries data.
"""
- try:
- modversion = self.loaded_modules['timeseries']
- except IndexError:
- raise ModuleError("timeseries is not a loaded in "
- "the redis instance.")
+ if 'TS.INFO' not in self.__commands__:
+ raise ModuleError("reditimeseries is not loaded in redis. "
+ "For more information visit "
+ "https://redistimeseries.io/")
from .timeseries import TimeSeries
- s = TimeSeries(client=self, version=modversion, index_name=index_name)
+ s = TimeSeries(client=self)
return s
diff --git a/redis/commands/search/__init__.py b/redis/commands/search/__init__.py
index 425578e..8320ad4 100644
--- a/redis/commands/search/__init__.py
+++ b/redis/commands/search/__init__.py
@@ -83,7 +83,7 @@ class Search(SearchCommands):
self.pipeline.execute()
self.current_chunk = 0
- def __init__(self, client, version=None, index_name="idx"):
+ def __init__(self, client, index_name="idx"):
"""
Create a new Client for the given index_name.
The default name is `idx`
@@ -91,7 +91,6 @@ class Search(SearchCommands):
If conn is not None, we employ an already existing redis connection
"""
self.client = client
- self.MODULE_VERSION = version
self.index_name = index_name
self.execute_command = client.execute_command
self.pipeline = client.pipeline
diff --git a/redis/commands/search/commands.py b/redis/commands/search/commands.py
index 296fb25..0cee2ad 100644
--- a/redis/commands/search/commands.py
+++ b/redis/commands/search/commands.py
@@ -17,6 +17,7 @@ ADD_CMD = "FT.ADD"
ADDHASH_CMD = "FT.ADDHASH"
DROP_CMD = "FT.DROP"
EXPLAIN_CMD = "FT.EXPLAIN"
+EXPLAINCLI_CMD = "FT.EXPLAINCLI"
DEL_CMD = "FT.DEL"
AGGREGATE_CMD = "FT.AGGREGATE"
CURSOR_CMD = "FT.CURSOR"
@@ -376,6 +377,9 @@ class SearchCommands:
args, query_text = self._mk_query_args(query)
return self.execute_command(EXPLAIN_CMD, *args)
+ def explain_cli(self, query): # noqa
+ raise NotImplementedError("EXPLAINCLI will not be implemented.")
+
def aggregate(self, query):
"""
Issue an aggregation query
diff --git a/redis/commands/timeseries/__init__.py b/redis/commands/timeseries/__init__.py
index 83fa170..5ce538f 100644
--- a/redis/commands/timeseries/__init__.py
+++ b/redis/commands/timeseries/__init__.py
@@ -34,7 +34,7 @@ class TimeSeries(TimeSeriesCommands):
functionality.
"""
- def __init__(self, client=None, version=None, **kwargs):
+ def __init__(self, client=None, **kwargs):
"""Create a new RedisTimeSeries client."""
# Set the module commands' callbacks
self.MODULE_CALLBACKS = {
@@ -55,7 +55,6 @@ class TimeSeries(TimeSeriesCommands):
self.client = client
self.execute_command = client.execute_command
- self.MODULE_VERSION = version
for key, value in self.MODULE_CALLBACKS.items():
self.client.set_response_callback(key, value)
diff --git a/redis/lock.py b/redis/lock.py
index 326dbaf..d229752 100644
--- a/redis/lock.py
+++ b/redis/lock.py
@@ -76,14 +76,14 @@ class Lock:
Create a new Lock instance named ``name`` using the Redis client
supplied by ``redis``.
- ``timeout`` indicates a maximum life for the lock.
+ ``timeout`` indicates a maximum life for the lock in seconds.
By default, it will remain locked until release() is called.
``timeout`` can be specified as a float or integer, both representing
the number of seconds to wait.
- ``sleep`` indicates the amount of time to sleep per loop iteration
- when the lock is in blocking mode and another client is currently
- holding the lock.
+ ``sleep`` indicates the amount of time to sleep in seconds per loop
+ iteration when the lock is in blocking mode and another client is
+ currently holding the lock.
``blocking`` indicates whether calling ``acquire`` should block until
the lock has been acquired or to fail immediately, causing ``acquire``
diff --git a/tests/conftest.py b/tests/conftest.py
index 0adec91..31d3fbd 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -31,6 +31,10 @@ def pytest_addoption(parser):
def _get_info(redis_url):
client = redis.Redis.from_url(redis_url)
info = client.info()
+ if 'dping' in client.__commands__:
+ info["enterprise"] = True
+ else:
+ info["enterprise"] = False
client.connection_pool.disconnect()
return info
@@ -42,6 +46,7 @@ def pytest_sessionstart(session):
arch_bits = info["arch_bits"]
REDIS_INFO["version"] = version
REDIS_INFO["arch_bits"] = arch_bits
+ REDIS_INFO["enterprise"] = info["enterprise"]
# module info, if the second redis is running
try:
@@ -50,6 +55,8 @@ def pytest_sessionstart(session):
REDIS_INFO["modules"] = info["modules"]
except redis.exceptions.ConnectionError:
pass
+ except KeyError:
+ pass
def skip_if_server_version_lt(min_version):
@@ -92,6 +99,17 @@ def skip_ifmodversion_lt(min_version: str, module_name: str):
raise AttributeError("No redis module named {}".format(module_name))
+def skip_if_redis_enterprise(func):
+ check = REDIS_INFO["enterprise"] is True
+ return pytest.mark.skipif(check, reason="Redis enterprise"
+ )
+
+
+def skip_ifnot_redis_enterprise(func):
+ check = REDIS_INFO["enterprise"] is False
+ return pytest.mark.skipif(check, reason="Redis enterprise")
+
+
def _get_client(cls, request, single_connection_client=True, flushdb=True,
from_url=None,
**kwargs):
diff --git a/tests/test_commands.py b/tests/test_commands.py
index c361a4b..dbd0442 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -13,6 +13,7 @@ from .conftest import (
_get_client,
skip_if_server_version_gte,
skip_if_server_version_lt,
+ skip_if_redis_enterprise,
skip_unless_arch_bits,
)
@@ -80,6 +81,7 @@ class TestRedisCommands:
assert 'get' in commands
@skip_if_server_version_lt("6.0.0")
+ @skip_if_redis_enterprise
def test_acl_deluser(self, r, request):
username = 'redis-py-user'
@@ -104,6 +106,7 @@ class TestRedisCommands:
assert r.acl_getuser(users[4]) is None
@skip_if_server_version_lt("6.0.0")
+ @skip_if_redis_enterprise
def test_acl_genpass(self, r):
password = r.acl_genpass()
assert isinstance(password, str)
@@ -117,6 +120,7 @@ class TestRedisCommands:
assert isinstance(password, str)
@skip_if_server_version_lt("6.0.0")
+ @skip_if_redis_enterprise
def test_acl_getuser_setuser(self, r, request):
username = 'redis-py-user'
@@ -210,6 +214,7 @@ class TestRedisCommands:
assert len(res) != 0
@skip_if_server_version_lt("6.0.0")
+ @skip_if_redis_enterprise
def test_acl_list(self, r, request):
username = 'redis-py-user'
@@ -222,6 +227,7 @@ class TestRedisCommands:
assert len(users) == 2
@skip_if_server_version_lt("6.0.0")
+ @skip_if_redis_enterprise
def test_acl_log(self, r, request):
username = 'redis-py-user'
@@ -257,6 +263,7 @@ class TestRedisCommands:
assert r.acl_log_reset()
@skip_if_server_version_lt("6.0.0")
+ @skip_if_redis_enterprise
def test_acl_setuser_categories_without_prefix_fails(self, r, request):
username = 'redis-py-user'
@@ -268,6 +275,7 @@ class TestRedisCommands:
r.acl_setuser(username, categories=['list'])
@skip_if_server_version_lt("6.0.0")
+ @skip_if_redis_enterprise
def test_acl_setuser_commands_without_prefix_fails(self, r, request):
username = 'redis-py-user'
@@ -279,6 +287,7 @@ class TestRedisCommands:
r.acl_setuser(username, commands=['get'])
@skip_if_server_version_lt("6.0.0")
+ @skip_if_redis_enterprise
def test_acl_setuser_add_passwords_and_nopass_fails(self, r, request):
username = 'redis-py-user'
@@ -312,13 +321,18 @@ class TestRedisCommands:
assert 'addr' in info
@skip_if_server_version_lt('5.0.0')
- def test_client_list_type(self, r):
+ def test_client_list_types_not_replica(self, r):
with pytest.raises(exceptions.RedisError):
r.client_list(_type='not a client type')
- for client_type in ['normal', 'master', 'replica', 'pubsub']:
+ for client_type in ['normal', 'master', 'pubsub']:
clients = r.client_list(_type=client_type)
assert isinstance(clients, list)
+ @skip_if_redis_enterprise
+ def test_client_list_replica(self, r):
+ clients = r.client_list(_type='replica')
+ assert isinstance(clients, list)
+
@skip_if_server_version_lt('6.2.0')
def test_client_list_client_id(self, r, request):
clients = r.client_list()
@@ -453,7 +467,8 @@ class TestRedisCommands:
client_2_addr = clients_by_name['redis-py-c2'].get('laddr')
assert r.client_kill_filter(laddr=client_2_addr)
- @skip_if_server_version_lt('2.8.12')
+ @skip_if_server_version_lt('6.0.0')
+ @skip_if_redis_enterprise
def test_client_kill_filter_by_user(self, r, request):
killuser = 'user_to_kill'
r.acl_setuser(killuser, enabled=True, reset=True,
@@ -467,6 +482,7 @@ class TestRedisCommands:
r.acl_deluser(killuser)
@skip_if_server_version_lt('2.9.50')
+ @skip_if_redis_enterprise
def test_client_pause(self, r):
assert r.client_pause(1)
assert r.client_pause(timeout=1)
@@ -474,6 +490,7 @@ class TestRedisCommands:
r.client_pause(timeout='not an integer')
@skip_if_server_version_lt('6.2.0')
+ @skip_if_redis_enterprise
def test_client_unpause(self, r):
assert r.client_unpause() == b'OK'
@@ -491,15 +508,18 @@ class TestRedisCommands:
assert r.get('foo') == b'bar'
@skip_if_server_version_lt('6.0.0')
+ @skip_if_redis_enterprise
def test_client_getredir(self, r):
assert isinstance(r.client_getredir(), int)
assert r.client_getredir() == -1
def test_config_get(self, r):
data = r.config_get()
- assert 'maxmemory' in data
- assert data['maxmemory'].isdigit()
+ assert len(data.keys()) > 10
+ # # assert 'maxmemory' in data
+ # assert data['maxmemory'].isdigit()
+ @skip_if_redis_enterprise
def test_config_resetstat(self, r):
r.ping()
prior_commands_processed = int(r.info()['total_commands_processed'])
@@ -508,14 +528,12 @@ class TestRedisCommands:
reset_commands_processed = int(r.info()['total_commands_processed'])
assert reset_commands_processed < prior_commands_processed
+ @skip_if_redis_enterprise
def test_config_set(self, r):
- data = r.config_get()
- rdbname = data['dbfilename']
- try:
- assert r.config_set('dbfilename', 'redis_py_test.rdb')
- assert r.config_get()['dbfilename'] == 'redis_py_test.rdb'
- finally:
- assert r.config_set('dbfilename', rdbname)
+ r.config_set('timeout', 70)
+ assert r.config_get()['timeout'] == '70'
+ assert r.config_set('timeout', 0)
+ assert r.config_get()['timeout'] == '0'
def test_dbsize(self, r):
r['a'] = 'foo'
@@ -530,8 +548,10 @@ class TestRedisCommands:
r['b'] = 'bar'
info = r.info()
assert isinstance(info, dict)
- assert info['db9']['keys'] == 2
+ assert 'arch_bits' in info.keys()
+ assert 'redis_version' in info.keys()
+ @skip_if_redis_enterprise
def test_lastsave(self, r):
assert isinstance(r.lastsave(), datetime.datetime)
@@ -625,6 +645,7 @@ class TestRedisCommands:
assert isinstance(t[0], int)
assert isinstance(t[1], int)
+ @skip_if_redis_enterprise
def test_bgsave(self, r):
assert r.bgsave()
time.sleep(0.3)
@@ -1187,6 +1208,12 @@ class TestRedisCommands:
value1 = 'ohmytext'
value2 = 'mynewtext'
res = 'mytext'
+
+ if skip_if_redis_enterprise(None).args[0] is True:
+ with pytest.raises(redis.exceptions.ResponseError):
+ assert r.stralgo('LCS', value1, value2) == res
+ return
+
# test LCS of strings
assert r.stralgo('LCS', value1, value2) == res
# test using keys
@@ -1229,6 +1256,12 @@ class TestRedisCommands:
def test_substr(self, r):
r['a'] = '0123456789'
+
+ if skip_if_redis_enterprise(None).args[0] is True:
+ with pytest.raises(redis.exceptions.ResponseError):
+ assert r.substr('a', 0) == b'0123456789'
+ return
+
assert r.substr('a', 0) == b'0123456789'
assert r.substr('a', 2) == b'23456789'
assert r.substr('a', 3, 5) == b'345'
@@ -2433,6 +2466,7 @@ class TestRedisCommands:
'slaves', 'nodeid'), dict)
@skip_if_server_version_lt('3.0.0')
+ @skip_if_redis_enterprise
def test_readwrite(self, r):
assert r.readwrite()
@@ -3595,6 +3629,11 @@ class TestRedisCommands:
@skip_if_server_version_lt('4.0.0')
def test_memory_malloc_stats(self, r):
+ if skip_if_redis_enterprise(None).args[0] is True:
+ with pytest.raises(redis.exceptions.ResponseError):
+ assert r.memory_malloc_stats()
+ return
+
assert r.memory_malloc_stats()
@skip_if_server_version_lt('4.0.0')
@@ -3602,6 +3641,12 @@ class TestRedisCommands:
# put a key into the current db to make sure that "db.<current-db>"
# has data
r.set('foo', 'bar')
+
+ if skip_if_redis_enterprise(None).args[0] is True:
+ with pytest.raises(redis.exceptions.ResponseError):
+ stats = r.memory_stats()
+ return
+
stats = r.memory_stats()
assert isinstance(stats, dict)
for key, value in stats.items():
@@ -3614,6 +3659,7 @@ class TestRedisCommands:
assert isinstance(r.memory_usage('foo'), int)
@skip_if_server_version_lt('4.0.0')
+ @skip_if_redis_enterprise
def test_module_list(self, r):
assert isinstance(r.module_list(), list)
for x in r.module_list():
@@ -3625,7 +3671,16 @@ class TestRedisCommands:
assert isinstance(res, int)
assert res >= 100
+ @skip_if_server_version_lt('2.8.13')
+ def test_command(self, r):
+ res = r.command()
+ assert len(res) >= 100
+ cmds = [c[0].decode() for c in res]
+ assert 'set' in cmds
+ assert 'get' in cmds
+
@skip_if_server_version_lt('4.0.0')
+ @skip_if_redis_enterprise
def test_module(self, r):
with pytest.raises(redis.exceptions.ModuleError) as excinfo:
r.module_load('/some/fake/path')
@@ -3661,7 +3716,8 @@ class TestRedisCommands:
assert r.restore(key2, 0, dumpdata)
assert r.ttl(key2) == -1
- # idletime
+ @skip_if_server_version_lt('5.0.0')
+ def test_restore_idletime(self, r):
key = 'yayakey'
r.set(key, 'blee!')
dumpdata = r.dump(key)
@@ -3669,7 +3725,8 @@ class TestRedisCommands:
assert r.restore(key, 0, dumpdata, idletime=5)
assert r.get(key) == b'blee!'
- # frequency
+ @skip_if_server_version_lt('5.0.0')
+ def test_restore_frequency(self, r):
key = 'yayakey'
r.set(key, 'blee!')
dumpdata = r.dump(key)
@@ -3678,11 +3735,10 @@ class TestRedisCommands:
assert r.get(key) == b'blee!'
@skip_if_server_version_lt('5.0.0')
+ @skip_if_redis_enterprise
def test_replicaof(self, r):
-
with pytest.raises(redis.ResponseError):
assert r.replicaof("NO ONE")
-
assert r.replicaof("NO", "ONE")
@@ -3756,6 +3812,7 @@ class TestBinarySave:
assert '6' in parsed['allocation_stats']
assert '>=256' in parsed['allocation_stats']
+ @skip_if_redis_enterprise
def test_large_responses(self, r):
"The PythonParser has some special cases for return values > 1MB"
# load up 5MB of data into a key
diff --git a/tests/test_connection.py b/tests/test_connection.py
index f2fc834..7c44768 100644
--- a/tests/test_connection.py
+++ b/tests/test_connection.py
@@ -2,7 +2,7 @@ from unittest import mock
import types
import pytest
-from redis.exceptions import InvalidResponse, ModuleError
+from redis.exceptions import InvalidResponse
from redis.utils import HIREDIS_AVAILABLE
from .conftest import skip_if_server_version_lt
@@ -19,30 +19,20 @@ def test_invalid_response(r):
@skip_if_server_version_lt('4.0.0')
@pytest.mark.redismod
-def test_loaded_modules(r, modclient):
- assert r.loaded_modules == []
- assert 'rejson' in modclient.loaded_modules.keys()
-
-
-@skip_if_server_version_lt('4.0.0')
-@pytest.mark.redismod
-def test_loading_external_modules(r, modclient):
+def test_loading_external_modules(modclient):
def inner():
pass
- with pytest.raises(ModuleError):
- r.load_external_module('rejson', 'myfuncname', inner)
-
- modclient.load_external_module('rejson', 'myfuncname', inner)
+ modclient.load_external_module('myfuncname', inner)
assert getattr(modclient, 'myfuncname') == inner
assert isinstance(getattr(modclient, 'myfuncname'), types.FunctionType)
# and call it
from redis.commands import RedisModuleCommands
j = RedisModuleCommands.json
- modclient.load_external_module('rejson', 'sometestfuncname', j)
+ modclient.load_external_module('sometestfuncname', j)
- d = {'hello': 'world!'}
- mod = j(modclient)
- mod.set("fookey", ".", d)
- assert mod.get('fookey') == d
+ # d = {'hello': 'world!'}
+ # mod = j(modclient)
+ # mod.set("fookey", ".", d)
+ # assert mod.get('fookey') == d
diff --git a/tests/test_connection_pool.py b/tests/test_connection_pool.py
index 6fedec6..521f520 100644
--- a/tests/test_connection_pool.py
+++ b/tests/test_connection_pool.py
@@ -7,7 +7,11 @@ from unittest import mock
from threading import Thread
from redis.connection import ssl_available, to_bool
-from .conftest import skip_if_server_version_lt, _get_client
+from .conftest import (
+ skip_if_server_version_lt,
+ skip_if_redis_enterprise,
+ _get_client
+)
from .test_pubsub import wait_for_message
@@ -481,6 +485,7 @@ class TestConnection:
assert not pool._available_connections[0]._sock
@skip_if_server_version_lt('2.8.8')
+ @skip_if_redis_enterprise
def test_busy_loading_disconnects_socket(self, r):
"""
If Redis raises a LOADING error, the connection should be
@@ -491,6 +496,7 @@ class TestConnection:
assert not r.connection._sock
@skip_if_server_version_lt('2.8.8')
+ @skip_if_redis_enterprise
def test_busy_loading_from_pipeline_immediate_command(self, r):
"""
BusyLoadingErrors should raise from Pipelines that execute a
@@ -506,6 +512,7 @@ class TestConnection:
assert not pool._available_connections[0]._sock
@skip_if_server_version_lt('2.8.8')
+ @skip_if_redis_enterprise
def test_busy_loading_from_pipeline(self, r):
"""
BusyLoadingErrors should be raised from a pipeline execution
@@ -521,6 +528,7 @@ class TestConnection:
assert not pool._available_connections[0]._sock
@skip_if_server_version_lt('2.8.8')
+ @skip_if_redis_enterprise
def test_read_only_error(self, r):
"READONLY errors get turned in ReadOnlyError exceptions"
with pytest.raises(redis.ReadOnlyError):
@@ -546,6 +554,7 @@ class TestConnection:
'path=/path/to/socket,db=0',
)
+ @skip_if_redis_enterprise
def test_connect_no_auth_supplied_when_required(self, r):
"""
AuthenticationError should be raised when the server requires a
@@ -555,6 +564,7 @@ class TestConnection:
r.execute_command('DEBUG', 'ERROR',
'ERR Client sent AUTH, but no password is set')
+ @skip_if_redis_enterprise
def test_connect_invalid_password_supplied(self, r):
"AuthenticationError should be raised when sending the wrong password"
with pytest.raises(redis.AuthenticationError):
diff --git a/tests/test_monitor.py b/tests/test_monitor.py
index bbb7fb7..a8a535b 100644
--- a/tests/test_monitor.py
+++ b/tests/test_monitor.py
@@ -1,4 +1,8 @@
-from .conftest import wait_for_command
+from .conftest import (
+ skip_if_redis_enterprise,
+ skip_ifnot_redis_enterprise,
+ wait_for_command
+)
class TestMonitor:
@@ -40,6 +44,7 @@ class TestMonitor:
response = wait_for_command(r, m, 'GET foo\\\\x92')
assert response['command'] == 'GET foo\\\\x92'
+ @skip_if_redis_enterprise
def test_lua_script(self, r):
with r.monitor() as m:
script = 'return redis.call("GET", "foo")'
@@ -49,3 +54,11 @@ class TestMonitor:
assert response['client_type'] == 'lua'
assert response['client_address'] == 'lua'
assert response['client_port'] == ''
+
+ @skip_ifnot_redis_enterprise
+ def test_lua_script_in_enterprise(self, r):
+ with r.monitor() as m:
+ script = 'return redis.call("GET", "foo")'
+ assert r.eval(script, 0) is None
+ response = wait_for_command(r, m, 'GET foo')
+ assert response is None
diff --git a/tests/test_pubsub.py b/tests/test_pubsub.py
index cfc6e5e..e242459 100644
--- a/tests/test_pubsub.py
+++ b/tests/test_pubsub.py
@@ -7,7 +7,11 @@ import pytest
import redis
from redis.exceptions import ConnectionError
-from .conftest import _get_client, skip_if_server_version_lt
+from .conftest import (
+ _get_client,
+ skip_if_redis_enterprise,
+ skip_if_server_version_lt
+)
def wait_for_message(pubsub, timeout=0.1, ignore_subscribe_messages=False):
@@ -528,6 +532,7 @@ class TestPubSubPings:
class TestPubSubConnectionKilled:
@skip_if_server_version_lt('3.0.0')
+ @skip_if_redis_enterprise
def test_connection_error_raised_when_connection_dies(self, r):
p = r.pubsub()
p.subscribe('foo')
diff --git a/tests/test_scripting.py b/tests/test_scripting.py
index c3c2094..352f3ba 100644
--- a/tests/test_scripting.py
+++ b/tests/test_scripting.py
@@ -2,6 +2,8 @@ import pytest
from redis import exceptions
+from tests.conftest import skip_if_server_version_lt
+
multiply_script = """
local value = redis.call('GET', KEYS[1])
@@ -30,7 +32,8 @@ class TestScripting:
# 2 * 3 == 6
assert r.eval(multiply_script, 1, 'a', 3) == 6
- def test_script_flush(self, r):
+ @skip_if_server_version_lt('6.2.0')
+ def test_script_flush_620(self, r):
r.set('a', 2)
r.script_load(multiply_script)
r.script_flush('ASYNC')
@@ -43,6 +46,12 @@ class TestScripting:
r.script_load(multiply_script)
r.script_flush()
+ with pytest.raises(exceptions.DataError):
+ r.set('a', 2)
+ r.script_load(multiply_script)
+ r.script_flush("NOTREAL")
+
+ def test_script_flush(self, r):
r.set('a', 2)
r.script_load(multiply_script)
r.script_flush(None)
diff --git a/tests/test_search.py b/tests/test_search.py
index e07a61c..d1fc75f 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -612,6 +612,12 @@ def test_explain(client):
@pytest.mark.redismod
+def test_explaincli(client):
+ with pytest.raises(NotImplementedError):
+ client.ft().explain_cli("foo")
+
+
+@pytest.mark.redismod
def test_summarize(client):
createIndex(client.ft())
waitForIndex(client, "idx")
@@ -643,9 +649,6 @@ def test_alias():
index1 = getClient()
index2 = getClient()
- index1.hset("index1:lonestar", mapping={"name": "lonestar"})
- index2.hset("index2:yogurt", mapping={"name": "yogurt"})
-
def1 = IndexDefinition(prefix=["index1:"])
def2 = IndexDefinition(prefix=["index2:"])
@@ -654,6 +657,9 @@ def test_alias():
ftindex1.create_index((TextField("name"),), definition=def1)
ftindex2.create_index((TextField("name"),), definition=def2)
+ index1.hset("index1:lonestar", mapping={"name": "lonestar"})
+ index2.hset("index2:yogurt", mapping={"name": "yogurt"})
+
res = ftindex1.search("*").docs[0]
assert "index1:lonestar" == res.id
diff --git a/tox.ini b/tox.ini
index 1d4da08..6d4c658 100644
--- a/tox.ini
+++ b/tox.ini
@@ -127,7 +127,8 @@ exclude =
.tox,
.venv*,
build,
+ docs/*,
dist,
docker,
venv*,
- whitelist.py \ No newline at end of file
+ whitelist.py