From 69829819561dd586ad38aeb10ed71ca0771b9d7a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Aug 2022 14:04:47 +0200 Subject: Merging upstream version 1.12.1. Signed-off-by: Daniel Baumann --- .bumpversion.cfg | 2 +- .github/workflows/test.yaml | 2 +- CHANGELOG.md | 22 +++++-- README.md | 25 ++++++++ iredis/__init__.py | 2 +- iredis/client.py | 52 +++++++++++++--- iredis/config.py | 3 + iredis/data/command_syntax.csv | 5 +- iredis/data/iredisrc | 15 +++++ iredis/entry.py | 69 ++++++++++++++------- iredis/redis_grammar.py | 6 ++ poetry.lock | 20 +++---- pyproject.toml | 2 +- tests/cli_tests/test_cli_start.py | 8 +++ tests/cli_tests/test_config.py | 34 +++++++++++ tests/unittests/command_parse/test_hash_parse.py | 21 +++++++ tests/unittests/test_client.py | 76 +++++++++++++++++------- 17 files changed, 293 insertions(+), 71 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 1207eca..98c577e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.12.0 +current_version = 1.12.1 commit = True tag = True diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 722c808..114c3b7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,7 @@ jobs: matrix: os: [ubuntu-latest] python: ['3.6', '3.7', '3.8', '3.9', '3.10'] - redis: [5, 6] + redis: [5, 6, 7] runs-on: ${{ matrix.os }} services: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1420608..f190414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## UPCOMING + +- Feature: support new command: `HRANDFIELD`. +- Bugfix: all tests pass on redis:7 now. +- Feature: IRedis now accept `username` for auth, redis server version under 6 + will ignore `username`. +- Feature: IRedis support prompt now, you can customize prompt string. (thanks to [aymericbeaumet]) + ## 1.12 - Feature: `CLIENT KILL` now support `LADDR` argument. @@ -15,14 +23,17 @@ - Feature: support new command: `GETEX`. - Feature: `FLUSHDB` and `FLUSHALL` supports `SYNC` option. - Feature: `GEOADD` supports `CH XX NX` options. -- Feature: Timestamp Completers are now support completion for timestamp fields and milliseconds timestamp fields. -- Deprecate: `GEORADIUS` is deprecated, no auto-complete for this command anymore. -- Deprecate: `GEORADIUSBYMEMBER` is deprecated, no auto-complete for this command anymore. +- Feature: Timestamp Completers are now support completion for timestamp fields + and milliseconds timestamp fields. +- Deprecate: `GEORADIUS` is deprecated, no auto-complete for this command + anymore. +- Deprecate: `GEORADIUSBYMEMBER` is deprecated, no auto-complete for this + command anymore. ### 1.11.1 -- Bugfix: Switch `distutils.version` to `packaging.version` to fix the version parse - for windows. (new dependency: pypi's python-packaging. +- Bugfix: Switch `distutils.version` to `packaging.version` to fix the version + parse for windows. (new dependency: pypi's python-packaging. ## 1.11 @@ -282,3 +293,4 @@ [hanaasagi]: https://github.com/Hanaasagi [sid-maddy]: https://github.com/sid-maddy [tssujt]: https://github.com/tssujt +[aymericbeaumet]: https://github.com/aymericbeaumet diff --git a/README.md b/README.md index e7daf42..ae2f3a1 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ like `KEYS *` (see - Written in pure Python, but IRedis was packaged into a single binary with [PyOxidizer](https://github.com/indygreg/PyOxidizer), you can use cURL to download and run, it just works, even you don't have a Python interpreter. +- You can change the cli prompt using `--prompt` option or set via `~/.iredisrc` + config file. - Hide password for `AUTH` command. - Says "Goodbye!" to you when you exit! - For full features, please see: [iredis.io](https://www.iredis.io) @@ -190,6 +192,29 @@ staging=redis://username:password@staging-redis.example.com:6379/1 Put this in your `iredisrc` then connect via `iredis -d staging` or `iredis -d dev`. +### Change The Default Prompt + +You can change the prompt str, the default prompt is: + +```shell +127.0.0.1:6379> +``` + +Which is rendered by `{host}:{port}[{db}]> `, you can change this via `--prompt` +option or change +[iredisrc](https://github.com/laixintao/iredis/blob/master/iredis/data/iredisrc) +config file. The prompwt string uses python string format engine, supported +interpolations: + +- `{client_name}` +- `{db}` +- `{host}` +- `{path}` +- `{port}` +- `{username}` +- `{client_addr}` +- `{client_id}` + ### Configuration IRedis supports config files. Command-line options will always take precedence diff --git a/iredis/__init__.py b/iredis/__init__.py index b518f6e..438a38d 100644 --- a/iredis/__init__.py +++ b/iredis/__init__.py @@ -1 +1 @@ -__version__ = "1.12.0" +__version__ = "1.12.1" diff --git a/iredis/client.py b/iredis/client.py index 1b1bf38..6555ff1 100644 --- a/iredis/client.py +++ b/iredis/client.py @@ -55,25 +55,35 @@ class Client: def __init__( self, - host=None, - port=None, + host="127.0.0.1", + port=6379, db=0, password=None, path=None, scheme="redis", username=None, client_name=None, + prompt=None, ): self.host = host self.port = port self.db = db self.path = path - # FIXME username is not using... self.username = username self.client_name = client_name self.scheme = scheme self.password = password + # cli args --prompt will overwrite the prompt in iredisrc config file + self.prompt = "" + if config.prompt: + self.prompt = config.prompt + if prompt: + self.prompt = prompt + + self.client_id = None + self.client_addr = None + self.build_connection() # all command upper case @@ -93,6 +103,13 @@ class Client: else: config.no_version_reason = "--no-info flag activated" + if self.prompt and "client_addr" in self.prompt: + self.client_addr = ":".join( + str(x) for x in self.connection._sock.getsockname() + ) + if self.prompt and "client_id" in self.prompt: + self.client_id = str(self.execute("CLIENT ID")) + if config.version and re.match(r"([\d\.]+)", config.version): self.auth_compat(config.version) @@ -131,6 +148,11 @@ class Client: "socket_keepalive": config.socket_keepalive, "client_name": client_name, } + + # if username is set without setting paswword, password will be ignored + if password: + connection_kwargs["username"] = username + if scheme == "rediss": connection_class = SSLConnection else: @@ -141,6 +163,7 @@ class Client: "password": password, "path": path, "client_name": client_name, + "username": username, } connection_class = UnixDomainSocketConnection @@ -150,7 +173,8 @@ class Client: connection_kwargs["encoding_errors"] = "replace" logger.debug( - f"connection_class={connection_class}, connection_kwargs={connection_kwargs}" + f"connection_class={connection_class}," + f" connection_kwargs={connection_kwargs}" ) return connection_class(**connection_kwargs) @@ -191,6 +215,18 @@ class Client: config.version = version def __str__(self): + if self.prompt: # not None and not empty + return self.prompt.format( + client_name=self.client_name, + db=self.db, + host=self.host, + path=self.path, + port=self.port, + username=self.username, + client_addr=self.client_addr, + client_id=self.client_id, + ) + if self.scheme == "unix": prompt = f"redis {self.path}" else: @@ -198,7 +234,8 @@ class Client: if self.db: prompt = f"{prompt}[{self.db}]" - return prompt + + return f"{prompt}> " def client_execute_command(self, command_name, *args): command = command_name.upper() @@ -222,7 +259,8 @@ class Client: Here we retry once for ConnectionError. """ logger.info( - f"execute by connection: connection={connection}, name={command_name}, {args}, {options}" + f"execute by connection: connection={connection}, name={command_name}," + f" {args}, {options}" ) retry_times = config.retry_times # FIXME configurable last_error = None @@ -248,7 +286,7 @@ class Client: last_error = e retry_times -= 1 need_refresh_connection = True - except (ResponseError) as e: + except ResponseError as e: response_message = str(e) if response_message.startswith("MOVED"): return self.reissue_with_redirect( diff --git a/iredis/config.py b/iredis/config.py index fefccdb..a62101c 100644 --- a/iredis/config.py +++ b/iredis/config.py @@ -63,6 +63,8 @@ class Config: self.withscores = False self.version = "Unknown" + self.prompt = None + def __setter__(self, name, value): # for every time start a transaction # clear the queued commands first @@ -126,5 +128,6 @@ def load_config_files(iredisrc): config.shell = config_obj["main"].as_bool("shell") config.pager = config_obj["main"].get("pager") config.enable_pager = config_obj["main"].as_bool("enable_pager") + config.prompt = config_obj["main"].get("prompt") return config_obj diff --git a/iredis/data/command_syntax.csv b/iredis/data/command_syntax.csv index 477c15c..fbaf47b 100644 --- a/iredis/data/command_syntax.csv +++ b/iredis/data/command_syntax.csv @@ -87,6 +87,7 @@ hash,HKEYS,command_key,command_hkeys hash,HLEN,command_key,render_int hash,HMGET,command_key_fields,render_list hash,HMSET,command_key_fieldvalues,render_bulk_string +hash,HRANDFIELD,command_key_count_withvalues,render_list_or_string hash,HSCAN,command_key_cursor_match_pattern_count,command_hscan hash,HSET,command_key_field_value,render_int hash,HSETNX,command_key_field_value,render_int @@ -103,14 +104,14 @@ list,LINDEX,command_key_position,render_bulk_string list,LINSERT,command_key_positionchoice_pivot_value,render_int list,LLEN,command_key,render_int list,LPOS,command_lpos,render_list_or_string -list,LPOP,command_key,render_bulk_string +list,LPOP,command_key,render_list_or_string list,LPUSH,command_key_values,render_int list,LPUSHX,command_key_values,render_int list,LRANGE,command_key_start_end,render_list list,LREM,command_key_position_value,render_int list,LSET,command_key_position_value,render_simple_string list,LTRIM,command_key_start_end,render_simple_string -list,RPOP,command_key,render_bulk_string +list,RPOP,command_key,render_list_or_string list,RPOPLPUSH,command_key_newkey,render_bulk_string list,RPUSH,command_key_values,render_int list,RPUSHX,command_key_value,render_int diff --git a/iredis/data/iredisrc b/iredis/data/iredisrc index bf79e90..6db02a7 100644 --- a/iredis/data/iredisrc +++ b/iredis/data/iredisrc @@ -58,6 +58,21 @@ warning = True # eg. ~/.iredis.log log_location = +# You can change the prompt str, if left blank, the default prompt would be: +# 127.0.0.1:6379> +# which is rendered by "{host}:{port}[{db}]> " +# supported interpolations: +# {client_name} +# {db} +# {host} +# {path} +# {port} +# {username} +# {client_addr} +# {client_id} +# The prompt string uses python string format engine +prompt = + # History file location history_location = ~/.iredis_history diff --git a/iredis/entry.py b/iredis/entry.py index 2a9dc05..9a07956 100644 --- a/iredis/entry.py +++ b/iredis/entry.py @@ -123,22 +123,22 @@ def write_result(text, max_height=None): class Rainbow: color = [ - ("#cc2244"), - ("#bb4444"), - ("#996644"), - ("#cc8844"), - ("#ccaa44"), - ("#bbaa44"), - ("#99aa44"), - ("#778844"), - ("#55aa44"), - ("#33aa44"), - ("#11aa44"), - ("#11aa66"), - ("#11aa88"), - ("#11aaaa"), - ("#11aacc"), - ("#11aaee"), + "#cc2244", + "#bb4444", + "#996644", + "#cc8844", + "#ccaa44", + "#bbaa44", + "#99aa44", + "#778844", + "#55aa44", + "#33aa44", + "#11aa44", + "#11aa66", + "#11aa88", + "#11aaaa", + "#11aacc", + "#11aaee", ] def __init__(self): @@ -160,8 +160,7 @@ class Rainbow: def prompt_message(client): - # TODO custom prompt - text = "{hostname}> ".format(hostname=str(client)) + text = str(client) if config.rainbow: return list(zip(Rainbow(), text)) return text @@ -248,8 +247,11 @@ PAGER_HELP = """Using pager when output is too tall for your window, default to @click.option( "-s", "--socket", default=None, help="Server socket (overrides hostname and port)." ) +@click.option("-n", help="Database number.(overwrites dsn/url's db number)", default=0) @click.option( - "-n", help="Database number.(overwrites dsn/url's db number)", default=None + "-u", + "--username", + help="User name used to auth, will be ignore for redis version < 6.", ) @click.option("-a", "--password", help="Password to use when connecting to the server.") @click.option("--url", default=None, envvar="IREDIS_URL", help=URL_HELP) @@ -271,6 +273,14 @@ PAGER_HELP = """Using pager when output is too tall for your window, default to @click.option("--rainbow/--no-rainbow", default=None, is_flag=True, help=RAINBOW) @click.option("--shell/--no-shell", default=None, is_flag=True, help=SHELL) @click.option("--pager/--no-pager", default=None, is_flag=True, help=PAGER_HELP) +@click.option( + "--prompt", + default=None, + help=( + "Prompt format (supported interpolations: {client_name}, {db}, {host}, {path}," + " {port}, {username}, {client_addr}, {client_id})." + ), +) @click.version_option() @click.argument("cmd", nargs=-1) def gather_args( @@ -278,6 +288,7 @@ def gather_args( h, p, n, + username, password, client_name, newbie, @@ -291,6 +302,7 @@ def gather_args( socket, shell, pager, + prompt, ): """ IRedis: Interactive Redis @@ -311,9 +323,9 @@ def gather_args( load_config_files(iredisrc) setup_log() logger.info( - f"[commandline args] host={h}, port={p}, db={n}, newbie={newbie}, " - f"iredisrc={iredisrc}, decode={decode}, raw={raw}, " - f"cmd={cmd}, rainbow={rainbow}." + f"[commandline args] host={h}, port={p}, db={n}, user={username}," + f" newbie={newbie}, iredisrc={iredisrc}, decode={decode}, raw={raw}, cmd={cmd}," + f" rainbow={rainbow}." ) # raw config if raw is not None: @@ -368,8 +380,10 @@ def create_client(params): host = params["h"] port = params["p"] db = params["n"] + username = params["username"] password = params["password"] client_name = params["client_name"] + prompt = params["prompt"] dsn_from_url = None dsn = params["dsn"] @@ -390,17 +404,26 @@ def create_client(params): scheme=dsn_from_url.scheme, username=dsn_from_url.username, client_name=client_name, + prompt=prompt, ) if params["socket"]: return Client( scheme="unix", path=params["socket"], db=db, + username=username, password=password, client_name=client_name, + prompt=prompt, ) return Client( - host=host, port=port, db=db, password=password, client_name=client_name + host=host, + port=port, + db=db, + username=username, + password=password, + client_name=client_name, + prompt=prompt, ) diff --git a/iredis/redis_grammar.py b/iredis/redis_grammar.py index 4e38540..b9e2ea8 100644 --- a/iredis/redis_grammar.py +++ b/iredis/redis_grammar.py @@ -16,6 +16,7 @@ logger = logging.getLogger(__name__) CONST = { "failoverchoice": "TAKEOVER FORCE", "withscores": "WITHSCORES", + "withvalues_const": "WITHVALUES", "limit": "LIMIT", "expiration": "EX PX", "exat_const": "EXAT", @@ -362,6 +363,7 @@ TIMEOUT_CONST = rf"(?P{c('timeout_const')})" ABORT_CONST = rf"(?P{c('abort_const')})" PXAT_CONST = rf"(?P{c('pxat_const')})" EXAT_CONST = rf"(?P{c('exat_const')})" +WITHVALUES_CONST = rf"(?P{c('withvalues_const')})" command_grammar = compile(COMMAND) @@ -660,6 +662,10 @@ GRAMMAR = { (\s+ {EXAT_CONST} \s+ {TIMESTAMP}) )? \s*""", + "command_key_count_withvalues": rf""" + \s+ {KEY} + (\s+ {COUNT} (\s+ {WITHVALUES_CONST})?)? + \s*""", } pipeline = r"(?P\|.*)?" diff --git a/poetry.lock b/poetry.lock index c523f21..f06eaf5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -30,7 +30,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "colorama" -version = "0.4.4" +version = "0.4.5" description = "Cross-platform colored terminal text." category = "dev" optional = false @@ -89,7 +89,7 @@ python-versions = "*" [[package]] name = "mistune" -version = "2.0.2" +version = "2.0.3" description = "A sane Markdown parser with useful plugins and renderers" category = "main" optional = false @@ -173,11 +173,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pygments" -version = "2.11.2" +version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "pyparsing" @@ -305,8 +305,8 @@ click = [ {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, ] colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] configobj = [ {file = "configobj-5.0.6.tar.gz", hash = "sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"}, @@ -324,8 +324,8 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] mistune = [ - {file = "mistune-2.0.2-py2.py3-none-any.whl", hash = "sha256:6bab6c6abd711c4604206c7d8cad5cd48b28f072b4bb75797d74146ba393a049"}, - {file = "mistune-2.0.2.tar.gz", hash = "sha256:6fc88c3cb49dba8b16687b41725e661cf85784c12e8974a29b9d336dd596c3a1"}, + {file = "mistune-2.0.3-py2.py3-none-any.whl", hash = "sha256:e3964140c0775535fba50bd616fe180920044a64bc21850253267b07bff89924"}, + {file = "mistune-2.0.3.tar.gz", hash = "sha256:d7605b46b6156b53b7d52a465202b29a6f00f4ea4130ad5d25e9d5547d6b7e50"}, ] packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, @@ -375,8 +375,8 @@ py = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pygments = [ - {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, - {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, diff --git a/pyproject.toml b/pyproject.toml index 10b3a1f..87bb464 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "iredis" -version = "1.12.0" +version = "1.12.1" description = "Terminal client for Redis with auto-completion and syntax highlighting." authors = ["laixintao "] readme = 'README.md' diff --git a/tests/cli_tests/test_cli_start.py b/tests/cli_tests/test_cli_start.py index 00fcbf7..8045947 100644 --- a/tests/cli_tests/test_cli_start.py +++ b/tests/cli_tests/test_cli_start.py @@ -81,3 +81,11 @@ def test_connect_via_socket(fake_redis_socket): c.expect("redis /tmp/test.sock") c.close() + + +def test_iredis_start_with_prompt(): + cli = pexpect.spawn("iredis --prompt '{host}abc{port}def{client_name}'", timeout=2) + cli.logfile_read = open("cli_test.log", "ab") + cli.expect("iredis") + cli.expect("127.0.0.1abc6379defNone") + cli.close() diff --git a/tests/cli_tests/test_config.py b/tests/cli_tests/test_config.py index 7d6839d..1795c62 100644 --- a/tests/cli_tests/test_config.py +++ b/tests/cli_tests/test_config.py @@ -23,3 +23,37 @@ def test_log_location_config(): content = logfile.read() assert len(content) > 100 + + +def test_load_prompt_from_config(iredis_client, clean_redis): + config_content = dedent( + """ + [main] + prompt = {host}abc{port}xx{db} + """ + ) + with open("/tmp/iredisrc", "w+") as etc_config: + etc_config.write(config_content) + + cli = pexpect.spawn("iredis -n 15 --iredisrc /tmp/iredisrc", timeout=1) + cli.expect("iredis") + cli.expect("127.0.0.1abc6379xx15") + cli.close() + + +def test_prompt_cli_overwrite_config(iredis_client, clean_redis): + config_content = dedent( + """ + [main] + prompt = {host}abc{port}xx{db} + """ + ) + with open("/tmp/iredisrc", "w+") as etc_config: + etc_config.write(config_content) + + cli = pexpect.spawn( + "iredis -n 15 --iredisrc /tmp/iredisrc --prompt='{db}-12345'", timeout=1 + ) + cli.expect("iredis") + cli.expect("15-12345") + cli.close() diff --git a/tests/unittests/command_parse/test_hash_parse.py b/tests/unittests/command_parse/test_hash_parse.py index 0c3aa04..2bde9e0 100644 --- a/tests/unittests/command_parse/test_hash_parse.py +++ b/tests/unittests/command_parse/test_hash_parse.py @@ -43,3 +43,24 @@ def test_hset(judge_command): "HSET foo bar hello", {"command": "HSET", "key": "foo", "field": "bar", "value": "hello"}, ) + + +def test_hrandfield(judge_command): + judge_command( + "HRANDFIELD coin", + {"command": "HRANDFIELD", "key": "coin"}, + ) + judge_command( + "HRANDFIELD coin -5 WITHVALUES", + { + "command": "HRANDFIELD", + "key": "coin", + "count": "-5", + "withvalues_const": "WITHVALUES", + }, + ) + judge_command( + "HRANDFIELD coin -5", + {"command": "HRANDFIELD", "key": "coin", "count": "-5"}, + ) + judge_command("HRANDFIELD coin WITHVALUES", None) diff --git a/tests/unittests/test_client.py b/tests/unittests/test_client.py index 5c6628f..45f0054 100644 --- a/tests/unittests/test_client.py +++ b/tests/unittests/test_client.py @@ -1,3 +1,4 @@ +import os import re import pytest import redis @@ -20,6 +21,11 @@ def completer(): return IRedisCompleter() +zset_type = "ziplist" +if os.environ["REDIS_VERSION"] == "7": + zset_type = "listpack" + + @pytest.mark.parametrize( "_input, command_name, expect_args", [ @@ -169,7 +175,16 @@ def test_not_retry_on_authentication_error(iredis_client, config): iredis_client.execute("None", "GET", ["foo"]) -@pytest.mark.skipif("int(os.environ['REDIS_VERSION']) < 6") +@pytest.mark.skipif( + "int(os.environ['REDIS_VERSION']) != 6", + reason=""" +in redis7, it will not work if you: +1. connect redis without password +2. set a password +3. auth + +the auth will fail""", +) def test_auto_select_db_and_auth_for_reconnect_only_6(iredis_client, config): config.retry_times = 2 config.raw = True @@ -256,6 +271,13 @@ def test_peek_key_not_exist(iredis_client, clean_redis, config): assert peek_result == ["non-exist-key doesn't exist."] +def test_iredis_with_username(): + with patch("redis.connection.Connection.connect"): + c = Client("127.0.0.1", "6379", username="abc", password="abc1") + assert c.connection.username == "abc" + assert c.connection.password == "abc1" + + def test_peek_string(iredis_client, clean_redis): clean_redis.set("foo", "bar") peek_result = list(iredis_client.do_peek("foo")) @@ -337,12 +359,13 @@ def test_peek_zset_fetch_all(iredis_client, clean_redis): "myzset", dict(zip([f"hello-{index}" for index in range(3)], range(3))) ) peek_result = list(iredis_client.do_peek("myzset")) + formatted_text_rematch( peek_result[0][0:9], FormattedText( [ ("class:dockey", "key: "), - ("", r"zset \(ziplist\) mem: \d+ bytes, ttl: -1"), + ("", rf"zset \({zset_type}\) mem: \d+ bytes, ttl: -1"), ("", "\n"), ("class:dockey", "zcount: "), ("", "3"), @@ -365,7 +388,7 @@ def test_peek_zset_fetch_part(iredis_client, clean_redis): FormattedText( [ ("class:dockey", "key: "), - ("", r"zset \(ziplist\) mem: \d+ bytes, ttl: -1"), + ("", rf"zset \({zset_type}\) mem: \d+ bytes, ttl: -1"), ("", "\n"), ("class:dockey", "zcount: "), ("", "40"), @@ -527,29 +550,23 @@ def test_version_parse_for_auth(iredis_client): "info, version", [ ( - ( - "# Server\r\nredis_version:df--128-NOTFOUND\r\n" - "redis_mode:standalone\r\narch_bits:64" - ), + "# Server\r\nredis_version:df--128-NOTFOUND\r\n" + "redis_mode:standalone\r\narch_bits:64", "df--128-NOTFOUND", ), ( - ( - "# Server\r\nredis_version:6.2.5\r\n" - "redis_git_sha1:00000000\r\n" - "redis_git_dirty:0\r\n" - "redis_build_id:915e5480613bc9b6\r\n" - "redis_mode:standalone " - ), + "# Server\r\nredis_version:6.2.5\r\n" + "redis_git_sha1:00000000\r\n" + "redis_git_dirty:0\r\n" + "redis_build_id:915e5480613bc9b6\r\n" + "redis_mode:standalone ", "6.2.5", ), ( - ( - "# Server\r\nredis_version:5.0.14.1\r\n" - "redis_git_sha1:00000000\r\nredis_git_dirty:0\r\n" - "redis_build_id:915e5480613bc9b6\r\n" - "redis_mode:standalone " - ), + "# Server\r\nredis_version:5.0.14.1\r\n" + "redis_git_sha1:00000000\r\nredis_git_dirty:0\r\n" + "redis_build_id:915e5480613bc9b6\r\n" + "redis_mode:standalone ", "5.0.14.1", ), ], @@ -564,3 +581,22 @@ def test_version_path(info, version): client = Client("127.0.0.1", "6379", None) client.get_server_info() assert mock_config.version == version + + +def test_prompt(): + c = Client() + assert str(c) == "127.0.0.1:6379> " + + c = Client(prompt="{host} {port} {db}") + assert str(c) == "127.0.0.1 6379 0" + + c = Client(prompt="{host} {port} {db} {username}") + assert str(c) == "127.0.0.1 6379 0 None" + + c = Client(prompt="{host} {port} {db} {username}", username="foo1") + assert str(c) == "127.0.0.1 6379 0 foo1" + + c = Client(prompt="{client_id} aabc") + assert re.match(r"^\d+ aabc$", str(c)) + c = Client(prompt="{client_addr} >") + assert re.match(r"^127.0.0.1:\d+ >$", str(c)) -- cgit v1.2.3