-
Notifications
You must be signed in to change notification settings - Fork 606
Add --timeout option and a connect_timeout config default #1622
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -216,7 +216,7 @@ def connect( | |
|
|
||
| if new_params["dsn"]: | ||
| # When using DSN, only keep dsn, password, and hostaddr (for SSH tunnels) | ||
| new_params = {k: v for k, v in new_params.items() if k in ("dsn", "password", "hostaddr")} | ||
| new_params = {k: v for k, v in new_params.items() if k in ("dsn", "password", "hostaddr", "connect_timeout")} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment above is now out-of-date. |
||
|
|
||
| if new_params["password"]: | ||
| new_params["dsn"] = make_conninfo(new_params["dsn"], password=new_params.pop("password")) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| COLOR_CODE_REGEX, | ||
| ) | ||
| from pgcli.pgexecute import PGExecute | ||
| from psycopg.conninfo import conninfo_to_dict | ||
| from pgspecial.main import PAGER_OFF, PAGER_LONG_OUTPUT, PAGER_ALWAYS | ||
| from utils import dbtest, run | ||
| from collections import namedtuple | ||
|
|
@@ -500,6 +501,7 @@ def test_pg_service_file(tmpdir): | |
| "", | ||
| notify_callback, | ||
| application_name="pgcli", | ||
| connect_timeout="30", | ||
| ) | ||
| del os.environ["PGPASSWORD"] | ||
| del os.environ["PGSERVICEFILE"] | ||
|
|
@@ -548,7 +550,7 @@ def test_application_name_db_uri(tmpdir): | |
| mock_pgexecute.return_value = None | ||
| cli = PGCli(pgclirc_file=str(tmpdir.join("rcfile"))) | ||
| cli.connect_uri("postgres://bar@baz.com/?application_name=cow") | ||
| mock_pgexecute.assert_called_with("bar", "bar", "", "baz.com", "", "", notify_callback, application_name="cow") | ||
| mock_pgexecute.assert_called_with("bar", "bar", "", "baz.com", "", "", notify_callback, application_name="cow", connect_timeout="30") | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
|
|
@@ -701,3 +703,62 @@ def test_get_editor_precedence(): | |
| # Nothing set -> None, so click uses its platform default. | ||
| with mock.patch.dict(os.environ, {}, clear=True): | ||
| assert get_editor() is None | ||
|
|
||
|
|
||
| def _effective_connect_timeout(tmpdir, cli_timeout=None, dsn_timeout=None, env=None, cfgval=None): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it works... but a |
||
| """The connect_timeout that actually reaches the connection.""" | ||
| rc = str(tmpdir.join("rcfile")) | ||
| with open(rc, "w") as f: | ||
| f.write("[main]\n" + (f"connect_timeout = {cfgval}\n" if cfgval else "")) | ||
| environ = {k: v for k, v in os.environ.items() if k != "PGCONNECT_TIMEOUT"} | ||
| if env: | ||
| environ["PGCONNECT_TIMEOUT"] = env | ||
| with mock.patch.dict(os.environ, environ, clear=True): | ||
| cli_obj = PGCli(pgclirc_file=rc, connect_timeout=cli_timeout) | ||
| dsn = "postgresql://u@h:5432/db" + (f"?connect_timeout={dsn_timeout}" if dsn_timeout else "") | ||
| captured = {} | ||
|
|
||
| def fake(*a, **k): | ||
| captured["dsn"] = k.get("dsn") or (a[5] if len(a) > 5 else None) | ||
| captured["kwargs"] = k | ||
| raise RuntimeError("stop") | ||
|
|
||
| # connect() turns a failed connection into sys.exit(1); let it. | ||
| with mock.patch("pgcli.main.PGExecute", side_effect=fake), pytest.raises(SystemExit): | ||
| cli_obj.connect(dsn=dsn, host="h", port="5432", user="u", database="db") | ||
| from_kwargs = captured.get("kwargs", {}).get("connect_timeout") | ||
| return from_kwargs or conninfo_to_dict(captured.get("dsn") or "").get("connect_timeout") | ||
|
|
||
|
|
||
| def test_connect_timeout_config_default(tmpdir): | ||
| """With nothing else set, the config default is applied: libpq's own default | ||
| of 0 waits until the OS gives up, which takes minutes.""" | ||
| assert _effective_connect_timeout(tmpdir) == "30" | ||
|
|
||
|
|
||
| def test_connect_timeout_config_value_used(tmpdir): | ||
| assert _effective_connect_timeout(tmpdir, cfgval=45) == "45" | ||
|
|
||
|
|
||
| def test_connect_timeout_connection_string_wins_over_config(tmpdir): | ||
| assert _effective_connect_timeout(tmpdir, dsn_timeout=15) == "15" | ||
|
|
||
|
|
||
| def test_connect_timeout_connection_string_wins_over_env(tmpdir): | ||
| """libpq precedence: an explicit connect_timeout beats $PGCONNECT_TIMEOUT.""" | ||
| assert _effective_connect_timeout(tmpdir, dsn_timeout=15, env="7") == "15" | ||
|
|
||
|
|
||
| def test_connect_timeout_env_left_to_libpq(tmpdir): | ||
| """With only $PGCONNECT_TIMEOUT set nothing is injected, so libpq reads the | ||
| environment variable itself and the config default does not override it.""" | ||
| assert _effective_connect_timeout(tmpdir, env="7") is None | ||
|
|
||
|
|
||
| def test_connect_timeout_cli_overrides_everything(tmpdir): | ||
| assert _effective_connect_timeout(tmpdir, cli_timeout=3, dsn_timeout=15, env="7") == "3" | ||
|
|
||
|
|
||
| def test_connect_timeout_cli_zero_waits_forever(tmpdir): | ||
| """--timeout 0 is meaningful and must not be treated as unset.""" | ||
| assert _effective_connect_timeout(tmpdir, cli_timeout=0, dsn_timeout=15) == "0" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather see this conversion when setting
self.default_connect_timeoutin__init__(). That way, all uses of the attribute can be sure that it's an integer (and not garbage). We could use this:That will (1) convert to an integer; (2) automatically default to 30 (from
pgclirc) if the user removed the line from their configuration file; and (3) raise an error if the user configured a non-integer value, which I find useful: I prefer when the software says I have done something stupid, instead of silently ignoring it, and making me search and finally find out that, yes, I did something stupid. ;)