Adding upstream version 1.12.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
3c10eca007
commit
77ff80fefe
17 changed files with 293 additions and 71 deletions
|
@ -1,5 +1,5 @@
|
||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 1.12.0
|
current_version = 1.12.1
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
|
|
||||||
|
|
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
python: ['3.6', '3.7', '3.8', '3.9', '3.10']
|
python: ['3.6', '3.7', '3.8', '3.9', '3.10']
|
||||||
redis: [5, 6]
|
redis: [5, 6, 7]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
22
CHANGELOG.md
22
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
|
## 1.12
|
||||||
|
|
||||||
- Feature: `CLIENT KILL` now support `LADDR` argument.
|
- Feature: `CLIENT KILL` now support `LADDR` argument.
|
||||||
|
@ -15,14 +23,17 @@
|
||||||
- Feature: support new command: `GETEX`.
|
- Feature: support new command: `GETEX`.
|
||||||
- Feature: `FLUSHDB` and `FLUSHALL` supports `SYNC` option.
|
- Feature: `FLUSHDB` and `FLUSHALL` supports `SYNC` option.
|
||||||
- Feature: `GEOADD` supports `CH XX NX` options.
|
- Feature: `GEOADD` supports `CH XX NX` options.
|
||||||
- Feature: Timestamp Completers are now support completion for timestamp fields and milliseconds timestamp fields.
|
- Feature: Timestamp Completers are now support completion for timestamp fields
|
||||||
- Deprecate: `GEORADIUS` is deprecated, no auto-complete for this command anymore.
|
and milliseconds timestamp fields.
|
||||||
- Deprecate: `GEORADIUSBYMEMBER` is deprecated, no auto-complete for this command anymore.
|
- 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
|
### 1.11.1
|
||||||
|
|
||||||
- Bugfix: Switch `distutils.version` to `packaging.version` to fix the version parse
|
- Bugfix: Switch `distutils.version` to `packaging.version` to fix the version
|
||||||
for windows. (new dependency: pypi's python-packaging.
|
parse for windows. (new dependency: pypi's python-packaging.
|
||||||
|
|
||||||
## 1.11
|
## 1.11
|
||||||
|
|
||||||
|
@ -282,3 +293,4 @@
|
||||||
[hanaasagi]: https://github.com/Hanaasagi
|
[hanaasagi]: https://github.com/Hanaasagi
|
||||||
[sid-maddy]: https://github.com/sid-maddy
|
[sid-maddy]: https://github.com/sid-maddy
|
||||||
[tssujt]: https://github.com/tssujt
|
[tssujt]: https://github.com/tssujt
|
||||||
|
[aymericbeaumet]: https://github.com/aymericbeaumet
|
||||||
|
|
25
README.md
25
README.md
|
@ -60,6 +60,8 @@ like `KEYS *` (see
|
||||||
- Written in pure Python, but IRedis was packaged into a single binary with
|
- Written in pure Python, but IRedis was packaged into a single binary with
|
||||||
[PyOxidizer](https://github.com/indygreg/PyOxidizer), you can use cURL to
|
[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.
|
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.
|
- Hide password for `AUTH` command.
|
||||||
- Says "Goodbye!" to you when you exit!
|
- Says "Goodbye!" to you when you exit!
|
||||||
- For full features, please see: [iredis.io](https://www.iredis.io)
|
- 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
|
Put this in your `iredisrc` then connect via `iredis -d staging` or
|
||||||
`iredis -d dev`.
|
`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
|
### Configuration
|
||||||
|
|
||||||
IRedis supports config files. Command-line options will always take precedence
|
IRedis supports config files. Command-line options will always take precedence
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = "1.12.0"
|
__version__ = "1.12.1"
|
||||||
|
|
|
@ -55,25 +55,35 @@ class Client:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
host=None,
|
host="127.0.0.1",
|
||||||
port=None,
|
port=6379,
|
||||||
db=0,
|
db=0,
|
||||||
password=None,
|
password=None,
|
||||||
path=None,
|
path=None,
|
||||||
scheme="redis",
|
scheme="redis",
|
||||||
username=None,
|
username=None,
|
||||||
client_name=None,
|
client_name=None,
|
||||||
|
prompt=None,
|
||||||
):
|
):
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.db = db
|
self.db = db
|
||||||
self.path = path
|
self.path = path
|
||||||
# FIXME username is not using...
|
|
||||||
self.username = username
|
self.username = username
|
||||||
self.client_name = client_name
|
self.client_name = client_name
|
||||||
self.scheme = scheme
|
self.scheme = scheme
|
||||||
self.password = password
|
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()
|
self.build_connection()
|
||||||
|
|
||||||
# all command upper case
|
# all command upper case
|
||||||
|
@ -93,6 +103,13 @@ class Client:
|
||||||
else:
|
else:
|
||||||
config.no_version_reason = "--no-info flag activated"
|
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):
|
if config.version and re.match(r"([\d\.]+)", config.version):
|
||||||
self.auth_compat(config.version)
|
self.auth_compat(config.version)
|
||||||
|
|
||||||
|
@ -131,6 +148,11 @@ class Client:
|
||||||
"socket_keepalive": config.socket_keepalive,
|
"socket_keepalive": config.socket_keepalive,
|
||||||
"client_name": client_name,
|
"client_name": client_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# if username is set without setting paswword, password will be ignored
|
||||||
|
if password:
|
||||||
|
connection_kwargs["username"] = username
|
||||||
|
|
||||||
if scheme == "rediss":
|
if scheme == "rediss":
|
||||||
connection_class = SSLConnection
|
connection_class = SSLConnection
|
||||||
else:
|
else:
|
||||||
|
@ -141,6 +163,7 @@ class Client:
|
||||||
"password": password,
|
"password": password,
|
||||||
"path": path,
|
"path": path,
|
||||||
"client_name": client_name,
|
"client_name": client_name,
|
||||||
|
"username": username,
|
||||||
}
|
}
|
||||||
connection_class = UnixDomainSocketConnection
|
connection_class = UnixDomainSocketConnection
|
||||||
|
|
||||||
|
@ -150,7 +173,8 @@ class Client:
|
||||||
connection_kwargs["encoding_errors"] = "replace"
|
connection_kwargs["encoding_errors"] = "replace"
|
||||||
|
|
||||||
logger.debug(
|
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)
|
return connection_class(**connection_kwargs)
|
||||||
|
@ -191,6 +215,18 @@ class Client:
|
||||||
config.version = version
|
config.version = version
|
||||||
|
|
||||||
def __str__(self):
|
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":
|
if self.scheme == "unix":
|
||||||
prompt = f"redis {self.path}"
|
prompt = f"redis {self.path}"
|
||||||
else:
|
else:
|
||||||
|
@ -198,7 +234,8 @@ class Client:
|
||||||
|
|
||||||
if self.db:
|
if self.db:
|
||||||
prompt = f"{prompt}[{self.db}]"
|
prompt = f"{prompt}[{self.db}]"
|
||||||
return prompt
|
|
||||||
|
return f"{prompt}> "
|
||||||
|
|
||||||
def client_execute_command(self, command_name, *args):
|
def client_execute_command(self, command_name, *args):
|
||||||
command = command_name.upper()
|
command = command_name.upper()
|
||||||
|
@ -222,7 +259,8 @@ class Client:
|
||||||
Here we retry once for ConnectionError.
|
Here we retry once for ConnectionError.
|
||||||
"""
|
"""
|
||||||
logger.info(
|
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
|
retry_times = config.retry_times # FIXME configurable
|
||||||
last_error = None
|
last_error = None
|
||||||
|
@ -248,7 +286,7 @@ class Client:
|
||||||
last_error = e
|
last_error = e
|
||||||
retry_times -= 1
|
retry_times -= 1
|
||||||
need_refresh_connection = True
|
need_refresh_connection = True
|
||||||
except (ResponseError) as e:
|
except ResponseError as e:
|
||||||
response_message = str(e)
|
response_message = str(e)
|
||||||
if response_message.startswith("MOVED"):
|
if response_message.startswith("MOVED"):
|
||||||
return self.reissue_with_redirect(
|
return self.reissue_with_redirect(
|
||||||
|
|
|
@ -63,6 +63,8 @@ class Config:
|
||||||
self.withscores = False
|
self.withscores = False
|
||||||
self.version = "Unknown"
|
self.version = "Unknown"
|
||||||
|
|
||||||
|
self.prompt = None
|
||||||
|
|
||||||
def __setter__(self, name, value):
|
def __setter__(self, name, value):
|
||||||
# for every time start a transaction
|
# for every time start a transaction
|
||||||
# clear the queued commands first
|
# clear the queued commands first
|
||||||
|
@ -126,5 +128,6 @@ def load_config_files(iredisrc):
|
||||||
config.shell = config_obj["main"].as_bool("shell")
|
config.shell = config_obj["main"].as_bool("shell")
|
||||||
config.pager = config_obj["main"].get("pager")
|
config.pager = config_obj["main"].get("pager")
|
||||||
config.enable_pager = config_obj["main"].as_bool("enable_pager")
|
config.enable_pager = config_obj["main"].as_bool("enable_pager")
|
||||||
|
config.prompt = config_obj["main"].get("prompt")
|
||||||
|
|
||||||
return config_obj
|
return config_obj
|
||||||
|
|
|
@ -87,6 +87,7 @@ hash,HKEYS,command_key,command_hkeys
|
||||||
hash,HLEN,command_key,render_int
|
hash,HLEN,command_key,render_int
|
||||||
hash,HMGET,command_key_fields,render_list
|
hash,HMGET,command_key_fields,render_list
|
||||||
hash,HMSET,command_key_fieldvalues,render_bulk_string
|
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,HSCAN,command_key_cursor_match_pattern_count,command_hscan
|
||||||
hash,HSET,command_key_field_value,render_int
|
hash,HSET,command_key_field_value,render_int
|
||||||
hash,HSETNX,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,LINSERT,command_key_positionchoice_pivot_value,render_int
|
||||||
list,LLEN,command_key,render_int
|
list,LLEN,command_key,render_int
|
||||||
list,LPOS,command_lpos,render_list_or_string
|
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,LPUSH,command_key_values,render_int
|
||||||
list,LPUSHX,command_key_values,render_int
|
list,LPUSHX,command_key_values,render_int
|
||||||
list,LRANGE,command_key_start_end,render_list
|
list,LRANGE,command_key_start_end,render_list
|
||||||
list,LREM,command_key_position_value,render_int
|
list,LREM,command_key_position_value,render_int
|
||||||
list,LSET,command_key_position_value,render_simple_string
|
list,LSET,command_key_position_value,render_simple_string
|
||||||
list,LTRIM,command_key_start_end,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,RPOPLPUSH,command_key_newkey,render_bulk_string
|
||||||
list,RPUSH,command_key_values,render_int
|
list,RPUSH,command_key_values,render_int
|
||||||
list,RPUSHX,command_key_value,render_int
|
list,RPUSHX,command_key_value,render_int
|
||||||
|
|
|
|
@ -58,6 +58,21 @@ warning = True
|
||||||
# eg. ~/.iredis.log
|
# eg. ~/.iredis.log
|
||||||
log_location =
|
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 file location
|
||||||
history_location = ~/.iredis_history
|
history_location = ~/.iredis_history
|
||||||
|
|
||||||
|
|
|
@ -123,22 +123,22 @@ def write_result(text, max_height=None):
|
||||||
|
|
||||||
class Rainbow:
|
class Rainbow:
|
||||||
color = [
|
color = [
|
||||||
("#cc2244"),
|
"#cc2244",
|
||||||
("#bb4444"),
|
"#bb4444",
|
||||||
("#996644"),
|
"#996644",
|
||||||
("#cc8844"),
|
"#cc8844",
|
||||||
("#ccaa44"),
|
"#ccaa44",
|
||||||
("#bbaa44"),
|
"#bbaa44",
|
||||||
("#99aa44"),
|
"#99aa44",
|
||||||
("#778844"),
|
"#778844",
|
||||||
("#55aa44"),
|
"#55aa44",
|
||||||
("#33aa44"),
|
"#33aa44",
|
||||||
("#11aa44"),
|
"#11aa44",
|
||||||
("#11aa66"),
|
"#11aa66",
|
||||||
("#11aa88"),
|
"#11aa88",
|
||||||
("#11aaaa"),
|
"#11aaaa",
|
||||||
("#11aacc"),
|
"#11aacc",
|
||||||
("#11aaee"),
|
"#11aaee",
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -160,8 +160,7 @@ class Rainbow:
|
||||||
|
|
||||||
|
|
||||||
def prompt_message(client):
|
def prompt_message(client):
|
||||||
# TODO custom prompt
|
text = str(client)
|
||||||
text = "{hostname}> ".format(hostname=str(client))
|
|
||||||
if config.rainbow:
|
if config.rainbow:
|
||||||
return list(zip(Rainbow(), text))
|
return list(zip(Rainbow(), text))
|
||||||
return text
|
return text
|
||||||
|
@ -248,8 +247,11 @@ PAGER_HELP = """Using pager when output is too tall for your window, default to
|
||||||
@click.option(
|
@click.option(
|
||||||
"-s", "--socket", default=None, help="Server socket (overrides hostname and port)."
|
"-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(
|
@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("-a", "--password", help="Password to use when connecting to the server.")
|
||||||
@click.option("--url", default=None, envvar="IREDIS_URL", help=URL_HELP)
|
@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("--rainbow/--no-rainbow", default=None, is_flag=True, help=RAINBOW)
|
||||||
@click.option("--shell/--no-shell", default=None, is_flag=True, help=SHELL)
|
@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("--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.version_option()
|
||||||
@click.argument("cmd", nargs=-1)
|
@click.argument("cmd", nargs=-1)
|
||||||
def gather_args(
|
def gather_args(
|
||||||
|
@ -278,6 +288,7 @@ def gather_args(
|
||||||
h,
|
h,
|
||||||
p,
|
p,
|
||||||
n,
|
n,
|
||||||
|
username,
|
||||||
password,
|
password,
|
||||||
client_name,
|
client_name,
|
||||||
newbie,
|
newbie,
|
||||||
|
@ -291,6 +302,7 @@ def gather_args(
|
||||||
socket,
|
socket,
|
||||||
shell,
|
shell,
|
||||||
pager,
|
pager,
|
||||||
|
prompt,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
IRedis: Interactive Redis
|
IRedis: Interactive Redis
|
||||||
|
@ -311,9 +323,9 @@ def gather_args(
|
||||||
load_config_files(iredisrc)
|
load_config_files(iredisrc)
|
||||||
setup_log()
|
setup_log()
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[commandline args] host={h}, port={p}, db={n}, newbie={newbie}, "
|
f"[commandline args] host={h}, port={p}, db={n}, user={username},"
|
||||||
f"iredisrc={iredisrc}, decode={decode}, raw={raw}, "
|
f" newbie={newbie}, iredisrc={iredisrc}, decode={decode}, raw={raw}, cmd={cmd},"
|
||||||
f"cmd={cmd}, rainbow={rainbow}."
|
f" rainbow={rainbow}."
|
||||||
)
|
)
|
||||||
# raw config
|
# raw config
|
||||||
if raw is not None:
|
if raw is not None:
|
||||||
|
@ -368,8 +380,10 @@ def create_client(params):
|
||||||
host = params["h"]
|
host = params["h"]
|
||||||
port = params["p"]
|
port = params["p"]
|
||||||
db = params["n"]
|
db = params["n"]
|
||||||
|
username = params["username"]
|
||||||
password = params["password"]
|
password = params["password"]
|
||||||
client_name = params["client_name"]
|
client_name = params["client_name"]
|
||||||
|
prompt = params["prompt"]
|
||||||
|
|
||||||
dsn_from_url = None
|
dsn_from_url = None
|
||||||
dsn = params["dsn"]
|
dsn = params["dsn"]
|
||||||
|
@ -390,17 +404,26 @@ def create_client(params):
|
||||||
scheme=dsn_from_url.scheme,
|
scheme=dsn_from_url.scheme,
|
||||||
username=dsn_from_url.username,
|
username=dsn_from_url.username,
|
||||||
client_name=client_name,
|
client_name=client_name,
|
||||||
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
if params["socket"]:
|
if params["socket"]:
|
||||||
return Client(
|
return Client(
|
||||||
scheme="unix",
|
scheme="unix",
|
||||||
path=params["socket"],
|
path=params["socket"],
|
||||||
db=db,
|
db=db,
|
||||||
|
username=username,
|
||||||
password=password,
|
password=password,
|
||||||
client_name=client_name,
|
client_name=client_name,
|
||||||
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
return Client(
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ logger = logging.getLogger(__name__)
|
||||||
CONST = {
|
CONST = {
|
||||||
"failoverchoice": "TAKEOVER FORCE",
|
"failoverchoice": "TAKEOVER FORCE",
|
||||||
"withscores": "WITHSCORES",
|
"withscores": "WITHSCORES",
|
||||||
|
"withvalues_const": "WITHVALUES",
|
||||||
"limit": "LIMIT",
|
"limit": "LIMIT",
|
||||||
"expiration": "EX PX",
|
"expiration": "EX PX",
|
||||||
"exat_const": "EXAT",
|
"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')})"
|
ABORT_CONST = rf"(?P<abort_const>{c('abort_const')})"
|
||||||
PXAT_CONST = rf"(?P<pxat_const>{c('pxat_const')})"
|
PXAT_CONST = rf"(?P<pxat_const>{c('pxat_const')})"
|
||||||
EXAT_CONST = rf"(?P<exat_const>{c('exat_const')})"
|
EXAT_CONST = rf"(?P<exat_const>{c('exat_const')})"
|
||||||
|
WITHVALUES_CONST = rf"(?P<withvalues_const>{c('withvalues_const')})"
|
||||||
|
|
||||||
command_grammar = compile(COMMAND)
|
command_grammar = compile(COMMAND)
|
||||||
|
|
||||||
|
@ -660,6 +662,10 @@ GRAMMAR = {
|
||||||
(\s+ {EXAT_CONST} \s+ {TIMESTAMP})
|
(\s+ {EXAT_CONST} \s+ {TIMESTAMP})
|
||||||
)?
|
)?
|
||||||
\s*""",
|
\s*""",
|
||||||
|
"command_key_count_withvalues": rf"""
|
||||||
|
\s+ {KEY}
|
||||||
|
(\s+ {COUNT} (\s+ {WITHVALUES_CONST})?)?
|
||||||
|
\s*""",
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeline = r"(?P<shellcommand>\|.*)?"
|
pipeline = r"(?P<shellcommand>\|.*)?"
|
||||||
|
|
20
poetry.lock
generated
20
poetry.lock
generated
|
@ -30,7 +30,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.4"
|
version = "0.4.5"
|
||||||
description = "Cross-platform colored terminal text."
|
description = "Cross-platform colored terminal text."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -89,7 +89,7 @@ python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mistune"
|
name = "mistune"
|
||||||
version = "2.0.2"
|
version = "2.0.3"
|
||||||
description = "A sane Markdown parser with useful plugins and renderers"
|
description = "A sane Markdown parser with useful plugins and renderers"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
|
@ -173,11 +173,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.11.2"
|
version = "2.12.0"
|
||||||
description = "Pygments is a syntax highlighting package written in Python."
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyparsing"
|
name = "pyparsing"
|
||||||
|
@ -305,8 +305,8 @@ click = [
|
||||||
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
|
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
|
||||||
]
|
]
|
||||||
colorama = [
|
colorama = [
|
||||||
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
|
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||||
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
|
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||||
]
|
]
|
||||||
configobj = [
|
configobj = [
|
||||||
{file = "configobj-5.0.6.tar.gz", hash = "sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"},
|
{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"},
|
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||||
]
|
]
|
||||||
mistune = [
|
mistune = [
|
||||||
{file = "mistune-2.0.2-py2.py3-none-any.whl", hash = "sha256:6bab6c6abd711c4604206c7d8cad5cd48b28f072b4bb75797d74146ba393a049"},
|
{file = "mistune-2.0.3-py2.py3-none-any.whl", hash = "sha256:e3964140c0775535fba50bd616fe180920044a64bc21850253267b07bff89924"},
|
||||||
{file = "mistune-2.0.2.tar.gz", hash = "sha256:6fc88c3cb49dba8b16687b41725e661cf85784c12e8974a29b9d336dd596c3a1"},
|
{file = "mistune-2.0.3.tar.gz", hash = "sha256:d7605b46b6156b53b7d52a465202b29a6f00f4ea4130ad5d25e9d5547d6b7e50"},
|
||||||
]
|
]
|
||||||
packaging = [
|
packaging = [
|
||||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
{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"},
|
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
||||||
]
|
]
|
||||||
pygments = [
|
pygments = [
|
||||||
{file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"},
|
{file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"},
|
||||||
{file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
|
{file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"},
|
||||||
]
|
]
|
||||||
pyparsing = [
|
pyparsing = [
|
||||||
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
|
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "iredis"
|
name = "iredis"
|
||||||
version = "1.12.0"
|
version = "1.12.1"
|
||||||
description = "Terminal client for Redis with auto-completion and syntax highlighting."
|
description = "Terminal client for Redis with auto-completion and syntax highlighting."
|
||||||
authors = ["laixintao <laixintao1995@163.com>"]
|
authors = ["laixintao <laixintao1995@163.com>"]
|
||||||
readme = 'README.md'
|
readme = 'README.md'
|
||||||
|
|
|
@ -81,3 +81,11 @@ def test_connect_via_socket(fake_redis_socket):
|
||||||
c.expect("redis /tmp/test.sock")
|
c.expect("redis /tmp/test.sock")
|
||||||
|
|
||||||
c.close()
|
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()
|
||||||
|
|
|
@ -23,3 +23,37 @@ def test_log_location_config():
|
||||||
content = logfile.read()
|
content = logfile.read()
|
||||||
|
|
||||||
assert len(content) > 100
|
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()
|
||||||
|
|
|
@ -43,3 +43,24 @@ def test_hset(judge_command):
|
||||||
"HSET foo bar hello",
|
"HSET foo bar hello",
|
||||||
{"command": "HSET", "key": "foo", "field": "bar", "value": "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)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import pytest
|
import pytest
|
||||||
import redis
|
import redis
|
||||||
|
@ -20,6 +21,11 @@ def completer():
|
||||||
return IRedisCompleter()
|
return IRedisCompleter()
|
||||||
|
|
||||||
|
|
||||||
|
zset_type = "ziplist"
|
||||||
|
if os.environ["REDIS_VERSION"] == "7":
|
||||||
|
zset_type = "listpack"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"_input, command_name, expect_args",
|
"_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"])
|
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):
|
def test_auto_select_db_and_auth_for_reconnect_only_6(iredis_client, config):
|
||||||
config.retry_times = 2
|
config.retry_times = 2
|
||||||
config.raw = True
|
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."]
|
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):
|
def test_peek_string(iredis_client, clean_redis):
|
||||||
clean_redis.set("foo", "bar")
|
clean_redis.set("foo", "bar")
|
||||||
peek_result = list(iredis_client.do_peek("foo"))
|
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)))
|
"myzset", dict(zip([f"hello-{index}" for index in range(3)], range(3)))
|
||||||
)
|
)
|
||||||
peek_result = list(iredis_client.do_peek("myzset"))
|
peek_result = list(iredis_client.do_peek("myzset"))
|
||||||
|
|
||||||
formatted_text_rematch(
|
formatted_text_rematch(
|
||||||
peek_result[0][0:9],
|
peek_result[0][0:9],
|
||||||
FormattedText(
|
FormattedText(
|
||||||
[
|
[
|
||||||
("class:dockey", "key: "),
|
("class:dockey", "key: "),
|
||||||
("", r"zset \(ziplist\) mem: \d+ bytes, ttl: -1"),
|
("", rf"zset \({zset_type}\) mem: \d+ bytes, ttl: -1"),
|
||||||
("", "\n"),
|
("", "\n"),
|
||||||
("class:dockey", "zcount: "),
|
("class:dockey", "zcount: "),
|
||||||
("", "3"),
|
("", "3"),
|
||||||
|
@ -365,7 +388,7 @@ def test_peek_zset_fetch_part(iredis_client, clean_redis):
|
||||||
FormattedText(
|
FormattedText(
|
||||||
[
|
[
|
||||||
("class:dockey", "key: "),
|
("class:dockey", "key: "),
|
||||||
("", r"zset \(ziplist\) mem: \d+ bytes, ttl: -1"),
|
("", rf"zset \({zset_type}\) mem: \d+ bytes, ttl: -1"),
|
||||||
("", "\n"),
|
("", "\n"),
|
||||||
("class:dockey", "zcount: "),
|
("class:dockey", "zcount: "),
|
||||||
("", "40"),
|
("", "40"),
|
||||||
|
@ -527,29 +550,23 @@ def test_version_parse_for_auth(iredis_client):
|
||||||
"info, version",
|
"info, version",
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
(
|
"# Server\r\nredis_version:df--128-NOTFOUND\r\n"
|
||||||
"# Server\r\nredis_version:df--128-NOTFOUND\r\n"
|
"redis_mode:standalone\r\narch_bits:64",
|
||||||
"redis_mode:standalone\r\narch_bits:64"
|
|
||||||
),
|
|
||||||
"df--128-NOTFOUND",
|
"df--128-NOTFOUND",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(
|
"# Server\r\nredis_version:6.2.5\r\n"
|
||||||
"# Server\r\nredis_version:6.2.5\r\n"
|
"redis_git_sha1:00000000\r\n"
|
||||||
"redis_git_sha1:00000000\r\n"
|
"redis_git_dirty:0\r\n"
|
||||||
"redis_git_dirty:0\r\n"
|
"redis_build_id:915e5480613bc9b6\r\n"
|
||||||
"redis_build_id:915e5480613bc9b6\r\n"
|
"redis_mode:standalone ",
|
||||||
"redis_mode:standalone "
|
|
||||||
),
|
|
||||||
"6.2.5",
|
"6.2.5",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
(
|
"# Server\r\nredis_version:5.0.14.1\r\n"
|
||||||
"# Server\r\nredis_version:5.0.14.1\r\n"
|
"redis_git_sha1:00000000\r\nredis_git_dirty:0\r\n"
|
||||||
"redis_git_sha1:00000000\r\nredis_git_dirty:0\r\n"
|
"redis_build_id:915e5480613bc9b6\r\n"
|
||||||
"redis_build_id:915e5480613bc9b6\r\n"
|
"redis_mode:standalone ",
|
||||||
"redis_mode:standalone "
|
|
||||||
),
|
|
||||||
"5.0.14.1",
|
"5.0.14.1",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -564,3 +581,22 @@ def test_version_path(info, version):
|
||||||
client = Client("127.0.0.1", "6379", None)
|
client = Client("127.0.0.1", "6379", None)
|
||||||
client.get_server_info()
|
client.get_server_info()
|
||||||
assert mock_config.version == version
|
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))
|
||||||
|
|
Loading…
Add table
Reference in a new issue