Skip to content

Commit 2a3a508

Browse files
committed
fix: corrected envflag behavior
* envflag: removed default parameter. * envflag: behavior now matches click.BoolParamType. * envflag: raises an error if none of the conditions are met. * tests(envflag): updated tests and applied monkeypatch.
1 parent f597f83 commit 2a3a508

File tree

4 files changed

+98
-50
lines changed

4 files changed

+98
-50
lines changed

litestar/cli/commands/core.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from contextlib import AbstractContextManager, ExitStack, contextmanager
99
from typing import TYPE_CHECKING, Any
1010

11+
from litestar.utils.helpers import envflag
12+
1113
try:
1214
import rich_click as click
1315
except ImportError:
@@ -119,7 +121,14 @@ class CommaSplittedPath(click.Path):
119121

120122

121123
@click.command(name="version")
122-
@click.option("-s", "--short", help="Exclude release level and serial information", is_flag=True, default=False)
124+
@click.option(
125+
"-s",
126+
"--short",
127+
help="Exclude release level and serial information",
128+
type=click.BOOL,
129+
default=False,
130+
is_flag=True,
131+
)
123132
def version_command(short: bool) -> None:
124133
"""Show the currently installed Litestar version."""
125134
from litestar import __version__
@@ -135,7 +144,15 @@ def info_command(app: Litestar) -> None:
135144

136145

137146
@click.command(name="run")
138-
@click.option("-r", "--reload", help="Reload server on changes", default=False, is_flag=True, envvar="LITESTAR_RELOAD")
147+
@click.option(
148+
"-r",
149+
"--reload",
150+
help="Reload server on changes",
151+
envvar="LITESTAR_RELOAD",
152+
type=click.BOOL,
153+
default=False,
154+
is_flag=True,
155+
)
139156
@click.option(
140157
"-R",
141158
"--reload-dir",
@@ -195,23 +212,39 @@ def info_command(app: Litestar) -> None:
195212
show_default=True,
196213
envvar="LITESTAR_UNIX_DOMAIN_SOCKET",
197214
)
198-
@click.option("-d", "--debug", help="Run app in debug mode", is_flag=True, envvar="LITESTAR_DEBUG")
199-
@click.option("-P", "--pdb", "--use-pdb", help="Drop into PDB on an exception", is_flag=True, envvar="LITESTAR_PDB")
215+
@click.option(
216+
"-d",
217+
"--debug",
218+
help="Run app in debug mode",
219+
envvar="LITESTAR_DEBUG",
220+
type=click.BOOL,
221+
is_flag=True,
222+
)
223+
@click.option(
224+
"-P",
225+
"--pdb",
226+
"--use-pdb",
227+
help="Drop into PDB on an exception",
228+
envvar="LITESTAR_PDB",
229+
type=click.BOOL,
230+
is_flag=True,
231+
)
200232
@click.option("--ssl-certfile", help="Location of the SSL cert file", default=None, envvar="LITESTAR_SSL_CERT_PATH")
201233
@click.option("--ssl-keyfile", help="Location of the SSL key file", default=None, envvar="LITESTAR_SSL_KEY_PATH")
202234
@click.option(
203235
"--create-self-signed-cert",
204236
help="If certificate and key are not found at specified locations, create a self-signed certificate and a key",
205-
is_flag=True,
206237
envvar="LITESTAR_CREATE_SELF_SIGNED_CERT",
238+
type=click.BOOL,
239+
is_flag=True,
207240
)
208241
@click.option(
209242
"-q",
210243
"--quiet-console",
211244
envvar="LITESTAR_QUIET_CONSOLE",
212245
help="Suppress formatted console output (useful for non-TTY environments, logs, and CI/CD",
246+
type=click.BOOL,
213247
is_flag=True,
214-
default=False,
215248
)
216249
def run_command(
217250
reload: bool,
@@ -244,6 +277,8 @@ def run_command(
244277
if pdb:
245278
os.environ["LITESTAR_PDB"] = "1"
246279

280+
quiet_console = bool(envflag("LITESTAR_QUIET_CONSOLE"))
281+
247282
if not UVICORN_INSTALLED:
248283
console.print(
249284
r"uvicorn is not installed. Please install the standard group, litestar\[standard], to use this command."
@@ -316,7 +351,7 @@ def run_command(
316351

317352

318353
@click.command(name="routes")
319-
@click.option("--schema", help="Include schema routes", is_flag=True, default=False)
354+
@click.option("--schema", help="Include schema routes", is_flag=True, default=False, type=click.BOOL)
320355
@click.option("--exclude", help="routes to exclude via regex", type=str, is_flag=False, multiple=True)
321356
def routes_command(app: Litestar, exclude: tuple[str, ...], schema: bool) -> None: # pragma: no cover
322357
"""Display information about the application's routes."""

litestar/utils/helpers.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from __future__ import annotations
22

3+
import os
34
from enum import Enum
45
from functools import partial
5-
from os import getenv
66
from typing import TYPE_CHECKING, TypeVar, cast
77
from urllib.parse import quote
88

9+
from litestar.exceptions import LitestarException
910
from litestar.utils.typing import get_origin_or_inner_type
1011

1112
if TYPE_CHECKING:
@@ -105,23 +106,31 @@ def get_exception_group() -> type[BaseException]:
105106
return cast("type[BaseException]", _ExceptionGroup)
106107

107108

108-
def envflag(varname: str, default: bool = False) -> bool:
109-
"""Get the boolean value of an environment variable.
110-
111-
Determines whether an environment variable is set to a truthy value.
112-
Returns True if the variable exists and its value matches one of:
113-
"1", "true", "t", "yes", "on", "y" (case-insensitive).
114-
Otherwise, including when the variable is not set, returns False.
109+
def envflag(varname: str) -> bool | None:
110+
"""Parse an environment variable as a boolean flag.
115111
116112
Args:
117-
varname (str): The name of the environment variable to check.
118-
default (bool): Value to return if the variable is not set OR empty.
113+
varname: The name of the environment variable to check.
119114
120115
Returns:
121-
bool: True if the environment variable is set to a truthy value,
122-
otherwise False.
116+
True for truthy values (1, true, t, yes, y, on),
117+
False for falsy values (0, false, f, no, n, off),
118+
or empty string, None if not set.
119+
120+
Raises:
121+
LitestarException: If the value is not a recognized boolean.
123122
"""
124-
envvar = getenv(varname)
123+
if varname not in os.environ:
124+
return None
125+
126+
envvar = os.environ.get(varname)
125127
if not envvar:
126-
return default
127-
return envvar.lower() in ("1", "true", "t", "yes", "on", "y")
128+
return False
129+
130+
norm = envvar.strip().lower()
131+
if norm in {"1", "true", "t", "yes", "y", "on"}:
132+
return True
133+
if norm in {"0", "false", "f", "no", "n", "off"}:
134+
return False
135+
136+
raise LitestarException(f"Invalid value for {varname}: '{norm}' is not a valid boolean.")

litestar/utils/warnings.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414

1515
def warn_implicit_sync_to_thread(source: AnyCallable, stacklevel: int = 2) -> None:
16-
if not envflag("LITESTAR_WARN_IMPLICIT_SYNC_TO_THREAD", default=True):
16+
if not envflag("LITESTAR_WARN_IMPLICIT_SYNC_TO_THREAD"):
1717
return
1818

1919
warnings.warn(
@@ -29,7 +29,7 @@ def warn_implicit_sync_to_thread(source: AnyCallable, stacklevel: int = 2) -> No
2929

3030

3131
def warn_sync_to_thread_with_async_callable(source: AnyCallable, stacklevel: int = 2) -> None:
32-
if not envflag("LITESTAR_WARN_SYNC_TO_THREAD_WITH_ASYNC", default=True):
32+
if not envflag("LITESTAR_WARN_SYNC_TO_THREAD_WITH_ASYNC"):
3333
return
3434

3535
warnings.warn(
@@ -42,7 +42,7 @@ def warn_sync_to_thread_with_async_callable(source: AnyCallable, stacklevel: int
4242

4343

4444
def warn_sync_to_thread_with_generator(source: AnyGenerator, stacklevel: int = 2) -> None:
45-
if not envflag("LITESTAR_WARN_SYNC_TO_THREAD_WITH_GENERATOR", default=True):
45+
if not envflag("LITESTAR_WARN_SYNC_TO_THREAD_WITH_GENERATOR"):
4646
return
4747

4848
warnings.warn(
@@ -73,7 +73,7 @@ def warn_middleware_excluded_on_all_routes(
7373

7474

7575
def warn_signature_namespace_override(signature_key: str, stacklevel: int = 2) -> None:
76-
if not envflag("LITESTAR_WARN_SIGNATURE_NAMESPACE_OVERRIDE", default=True):
76+
if not envflag("LITESTAR_WARN_SIGNATURE_NAMESPACE_OVERRIDE"):
7777
return
7878

7979
warnings.warn(

tests/unit/test_utils/test_helpers.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import os
21
from functools import partial
32
from typing import Any, Generic, TypeVar
43

54
import pytest
65

6+
from litestar.exceptions import LitestarException
77
from litestar.utils.helpers import envflag, get_name, unique_name_for_scope, unwrap_partial
88

99
T = TypeVar("T")
@@ -48,38 +48,42 @@ def test_unique_name_for_scope() -> None:
4848
assert unique_name_for_scope("b", ["a", "a_0", "b"]) == "b_0"
4949

5050

51-
def test_envflag_truthy_values() -> None:
52-
for value in ("1", "true", "TRUE", "t", "T", "yes", "YES", "on", "ON", "y", "Y"):
53-
os.environ["TEST_FLAG"] = value
51+
def test_envflag_truthy_values(monkeypatch: pytest.MonkeyPatch) -> None:
52+
for value in ("1", "true", "t", "yes", "y", "on", "YeS", "oN", "TRUE", "T"):
53+
monkeypatch.setenv("TEST_FLAG", value)
5454
assert envflag("TEST_FLAG") is True
55-
del os.environ["TEST_FLAG"]
55+
monkeypatch.delenv("TEST_FLAG")
5656

5757

58-
def test_envflag_falsy_values() -> None:
59-
for value in ("0", "false", "no", "off", ""):
60-
os.environ["TEST_FLAG"] = value
58+
def test_envflag_falsy_values(monkeypatch: pytest.MonkeyPatch) -> None:
59+
for value in ("0", "false", "f", "no", "n", "off", "", "OfF", "fAlSe", "NO"):
60+
monkeypatch.setenv("TEST_FLAG", value)
6161
assert envflag("TEST_FLAG") is False
62-
del os.environ["TEST_FLAG"]
62+
monkeypatch.delenv("TEST_FLAG")
63+
64+
65+
def test_envflag_invalid_value(monkeypatch: pytest.MonkeyPatch) -> None:
66+
for value in ("2", "Tru", "Fals", "maybe", "invalid", "O"):
67+
monkeypatch.setenv("TEST_FLAG", value)
68+
with pytest.raises(LitestarException):
69+
envflag("TEST_FLAG")
6370

6471

6572
def test_envflag_missing() -> None:
66-
assert envflag("NONEXISTENT_VAR") is False
67-
assert envflag("NONEXISTENT_VAR_123", default=True) is True
68-
assert envflag("NONEXISTENT_VAR_456", default=False) is False
73+
assert envflag("NONEXISTENT_VAR") is None
6974

7075

71-
def test_envflag_overrides_default() -> None:
72-
os.environ["TEST_FLAG"] = "true"
73-
assert envflag("TEST_FLAG", default=False) is True
74-
del os.environ["TEST_FLAG"]
76+
def test_envflag_overrides(monkeypatch: pytest.MonkeyPatch) -> None:
77+
monkeypatch.setenv("TEST_FLAG", "true")
78+
assert envflag("TEST_FLAG") is True
79+
monkeypatch.delenv("TEST_FLAG")
7580

76-
os.environ["TEST_FLAG"] = "0"
77-
assert envflag("TEST_FLAG", default=True) is False
78-
del os.environ["TEST_FLAG"]
81+
monkeypatch.setenv("TEST_FLAG", "0")
82+
assert envflag("TEST_FLAG") is False
83+
monkeypatch.delenv("TEST_FLAG")
7984

8085

81-
def test_envflag_empty_string_uses_default() -> None:
82-
os.environ["TEST_FLAG"] = ""
83-
assert envflag("TEST_FLAG", default=True) is True
84-
assert envflag("TEST_FLAG", default=False) is False
85-
del os.environ["TEST_FLAG"]
86+
def test_envflag_empty_string(monkeypatch: pytest.MonkeyPatch) -> None:
87+
monkeypatch.setenv("TEST_FLAG", "")
88+
assert envflag("TEST_FLAG") is False
89+
monkeypatch.delenv("TEST_FLAG")

0 commit comments

Comments
 (0)