summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.bumpversion.cfg2
-rw-r--r--.github/workflows/test.yaml2
-rw-r--r--CHANGELOG.md22
-rw-r--r--README.md25
-rw-r--r--iredis/__init__.py2
-rw-r--r--iredis/client.py52
-rw-r--r--iredis/config.py3
-rw-r--r--iredis/data/command_syntax.csv5
-rw-r--r--iredis/data/iredisrc15
-rw-r--r--iredis/entry.py69
-rw-r--r--iredis/redis_grammar.py6
-rw-r--r--poetry.lock20
-rw-r--r--pyproject.toml2
-rw-r--r--tests/cli_tests/test_cli_start.py8
-rw-r--r--tests/cli_tests/test_config.py34
-rw-r--r--tests/unittests/command_parse/test_hash_parse.py21
-rw-r--r--tests/unittests/test_client.py76
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<timeout_const>{c('timeout_const')})"
ABORT_CONST = rf"(?P<abort_const>{c('abort_const')})"
PXAT_CONST = rf"(?P<pxat_const>{c('pxat_const')})"
EXAT_CONST = rf"(?P<exat_const>{c('exat_const')})"
+WITHVALUES_CONST = rf"(?P<withvalues_const>{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<shellcommand>\|.*)?"
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 <laixintao1995@163.com>"]
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))