Skip to content

Commit bcb06b4

Browse files
Merge pull request #11 from peterstan077/master
feat: add ValidationError base exception and optional graceful error …
2 parents 1540cd9 + 3c4f5b6 commit bcb06b4

3 files changed

Lines changed: 29 additions & 10 deletions

File tree

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ classifiers = [
1111
"Programming Language :: Python :: 3",
1212
"Operating System :: OS Independent"
1313
]
14+
dependencies = [
15+
"pytest>=8.3.5",
16+
"rich>=14.1.0",
17+
]
1418

1519
[project.optional-dependencies]
1620
dev = [

src/venvalid/core.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import os
2+
from typing import Optional
3+
4+
from rich.console import Console
25

36
from .dotenv import load_env_file
47
from .errors import EnvSafeError
@@ -8,9 +11,11 @@
811
def venvalid(
912
specs: dict[str, object],
1013
*,
11-
source: dict[str, str] | None = None,
14+
source: Optional[dict[str, str]] = None,
1215
dotenv_path: str = ".env",
1316
dotenv_override: bool = False,
17+
pretty: bool = False,
18+
exit_on_error: bool = True,
1419
) -> dict[str, object]:
1520
"""
1621
Validates and loads environment variables based on declarative specifications.
@@ -20,9 +25,14 @@ def venvalid(
2025
source (dict, optional): Alternative source for the variables (default: os.environ).
2126
dotenv_path (str, optional): Path to the .env file to be loaded.
2227
dotenv_override (bool): If True, overwrites existing variables when loading .env.
28+
pretty (bool): If True, uses rich to pretty-print errors.
29+
exit_on_error (bool): If True, calls SystemExit(1) on validation errors (default=True for backwards compat).
2330
2431
Returns:
2532
dict: Validated and converted environment variables.
33+
34+
Raises:
35+
ValidationError: If exit_on_error=False and validation fails.
2636
"""
2737
if source is None:
2838
load_env_file(dotenv_path, override=dotenv_override)
@@ -32,13 +42,19 @@ def venvalid(
3242

3343
for key, spec in specs.items():
3444
raw_value = env_source.get(key)
35-
3645
try:
3746
value = _resolve_variable(key, raw_value, spec)
3847
result[key] = value
3948
except EnvSafeError as e:
40-
print(f"\n{e}\n")
41-
raise SystemExit(1)
49+
if pretty:
50+
try:
51+
console = Console()
52+
console.print(f"[bold red]Error:[/bold red] {e}")
53+
except ImportError:
54+
pass
55+
if exit_on_error:
56+
raise SystemExit(1)
57+
raise
4258

4359
return result
4460

@@ -47,7 +63,6 @@ def _resolve_variable(key: str, raw: str | None, spec: object) -> object:
4763
"""
4864
Resolves and validates an environment variable based on its specification.
4965
"""
50-
# Case enum-style: ["dev", "prod"]
5166
if isinstance(spec, list):
5267
if raw is None:
5368
raise EnvSafeError(f"{key} is required and must be one of {spec}")
@@ -62,18 +77,14 @@ def _resolve_variable(key: str, raw: str | None, spec: object) -> object:
6277

6378
if isinstance(spec, tuple):
6479
t_candidate, options = spec
65-
6680
if not isinstance(t_candidate, type):
6781
raise TypeError(f"{key}: expected a type, got {t_candidate}")
68-
6982
expected_type = t_candidate
7083
default = options.get("default")
7184
allowed = options.get("allowed")
7285
validate = options.get("validate")
73-
7486
elif isinstance(spec, type):
7587
expected_type = spec
76-
7788
else:
7889
raise TypeError(f"{key}: invalid spec type {type(spec)}")
7990

src/venvalid/errors.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
class EnvSafeError(Exception):
1+
class ValidationError(Exception):
2+
pass
3+
4+
5+
class EnvSafeError(ValidationError):
26
def __init__(self, message: str):
37
super().__init__(f"[venvalid] {message}")

0 commit comments

Comments
 (0)