Skip to content

Commit c3d8b07

Browse files
committed
fix(profiles): enhanced error handling if invalid profile is given
1 parent 9cd2ffb commit c3d8b07

File tree

11 files changed

+92
-43
lines changed

11 files changed

+92
-43
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
//"config", "info", "list",
4747
// "analyze",
4848
"-p", "ci",
49-
"profiles", "show"
49+
"profiles", "list", "-h"
5050
// "discover", "tests", "--tags"
5151
// "."
5252
]

packages/analyze/src/robotcode/analyze/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def analyze(app: Application, paths: Tuple[str]) -> Union[str, int, None]:
3232
try:
3333
robot_config = (
3434
load_robot_config_from_path(*config_files)
35-
.combine_profiles(*(app.config.profiles or []))
35+
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error)
3636
.evaluated_with_env()
3737
)
3838

packages/language_server/src/robotcode/language_server/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ def language_server(
7979
try:
8080
profile = (
8181
load_robot_config_from_path(*config_files)
82-
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose)
83-
.evaluated_with_env()
82+
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error)
83+
.evaluated_with_env(verbose_callback=app.verbose, error_callback=app.error)
8484
)
8585
except (TypeError, ValueError) as e:
8686
app.echo(str(e), err=True)

packages/plugin/src/robotcode/plugin/__init__.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,18 +117,33 @@ def warning(
117117
self,
118118
message: Union[str, Callable[[], Any], None],
119119
file: Optional[IO[AnyStr]] = None,
120-
nl: bool = True,
121-
err: bool = False,
120+
nl: Optional[bool] = True,
121+
err: Optional[bool] = True,
122122
) -> None:
123123
click.secho(
124-
f"WARNING: {message() if callable(message) else message}",
124+
f"[ {click.style('WARN', fg='yellow')} ] {message() if callable(message) else message}",
125125
file=file,
126-
nl=nl,
127-
err=err,
126+
nl=nl if nl is not None else True,
127+
err=err if err is not None else True,
128128
color=self.colored,
129129
fg="bright_yellow",
130130
)
131131

132+
def error(
133+
self,
134+
message: Union[str, Callable[[], Any], None],
135+
file: Optional[IO[AnyStr]] = None,
136+
nl: Optional[bool] = True,
137+
err: Optional[bool] = True,
138+
) -> None:
139+
click.secho(
140+
f"[ {click.style('ERROR', fg='red')} ] {message() if callable(message) else message}",
141+
file=file,
142+
nl=nl if nl is not None else True,
143+
err=err if err is not None else True,
144+
color=self.colored,
145+
)
146+
132147
def print_data(
133148
self,
134149
data: Any,

packages/robot/src/robotcode/robot/config/model.py

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,11 @@ def add_options(self, config: "BaseOptions", combine_extends: bool = False) -> N
368368
if new is not None:
369369
setattr(self, f.name, new)
370370

371-
def evaluated(self, verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None) -> Self:
371+
def evaluated(
372+
self,
373+
verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
374+
error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
375+
) -> Self:
372376
if verbose_callback is not None:
373377
verbose_callback("Evaluating options")
374378

@@ -395,7 +399,11 @@ def evaluated(self, verbose_callback: Optional[Callable[[Union[str, Callable[[],
395399
},
396400
)
397401
except EvaluationError as e:
398-
raise ValueError(f"Evaluation of '{f.name}' failed: {e!s}") from e
402+
message = f"Evaluation of '{f.name}' failed: {type(e).__name__}: {e}"
403+
if error_callback is None:
404+
raise ValueError(message) from e
405+
error_callback(message)
406+
399407
return result
400408

401409

@@ -2199,15 +2207,17 @@ def save(self, path: "os.PathLike[str]") -> None:
21992207
f.write(tomli_w.dumps(as_dict(self, remove_defaults=True)))
22002208

22012209
def evaluated_with_env(
2202-
self, verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None
2210+
self,
2211+
verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
2212+
error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
22032213
) -> Self:
22042214
if self.env:
22052215
for k, v in self.env.items():
22062216
os.environ[k] = str(v)
22072217
if verbose_callback:
22082218
verbose_callback(lambda: f"Set environment variable `{k}` to `{v}`")
22092219

2210-
return self.evaluated(verbose_callback)
2220+
return self.evaluated(verbose_callback, error_callback)
22112221

22122222

22132223
@dataclass
@@ -2343,6 +2353,7 @@ def select_profiles(
23432353
self,
23442354
*names: str,
23452355
verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
2356+
error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
23462357
) -> Dict[str, RobotProfile]:
23472358
result: Dict[str, RobotProfile] = {}
23482359

@@ -2373,7 +2384,12 @@ def select(name: str) -> None:
23732384
profile_names = [p for p in profiles.keys() if fnmatch.fnmatchcase(p, name)]
23742385

23752386
if not profile_names:
2376-
raise ValueError(f"Can't find any profiles matching the pattern '{name}'.")
2387+
message = f"Can't find any configuration profiles matching the pattern '{name}'."
2388+
if error_callback is None:
2389+
raise ValueError(message)
2390+
2391+
error_callback(message)
2392+
return
23772393

23782394
for v in profile_names:
23792395
p = profiles[v]
@@ -2391,8 +2407,20 @@ def select(name: str) -> None:
23912407
return result
23922408

23932409
def combine_profiles(
2394-
self, *names: str, verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None
2410+
self,
2411+
*names: str,
2412+
verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
2413+
error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
23952414
) -> RobotBaseProfile:
2415+
return self.combine_profiles_ex(*names, verbose_callback=verbose_callback, error_callback=error_callback)[0]
2416+
2417+
def combine_profiles_ex(
2418+
self,
2419+
*names: str,
2420+
verbose_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
2421+
error_callback: Optional[Callable[[Union[str, Callable[[], Any]]], None]] = None,
2422+
) -> Tuple[RobotBaseProfile, Dict[str, RobotProfile], List[str]]:
2423+
enabled_profiles = []
23962424
type_hints = get_type_hints(RobotBaseProfile)
23972425
base_field_names = [f.name for f in dataclasses.fields(RobotBaseProfile)]
23982426

@@ -2410,7 +2438,9 @@ def combine_profiles(
24102438
}
24112439
)
24122440

2413-
selected_profiles = self.select_profiles(*names, verbose_callback=verbose_callback)
2441+
selected_profiles = self.select_profiles(
2442+
*names, verbose_callback=verbose_callback, error_callback=error_callback
2443+
)
24142444
if verbose_callback:
24152445
if selected_profiles:
24162446
verbose_callback(f"Selected profiles: {', '.join(selected_profiles.keys())}")
@@ -2424,11 +2454,19 @@ def combine_profiles(
24242454
verbose_callback(f'Skipping profile "{profile_name}" because it\'s disabled.')
24252455
continue
24262456
except EvaluationError as e:
2427-
raise ValueError(f'Error evaluating "enabled" condition for profile "{profile_name}": {e}') from e
2457+
message = f'Error evaluating "enabled" condition for profile "{profile_name}": {e}'
2458+
2459+
if error_callback is None:
2460+
raise ValueError(message) from e
2461+
2462+
error_callback(message)
2463+
continue
24282464

24292465
if verbose_callback:
24302466
verbose_callback(f'Using profile "{profile_name}".')
24312467

2468+
enabled_profiles.append(profile_name)
2469+
24322470
if profile.env:
24332471
for k, v in profile.env.items():
24342472
os.environ[k] = str(v)
@@ -2482,4 +2520,4 @@ def combine_profiles(
24822520
if new is not None:
24832521
setattr(result, f.name, new)
24842522

2485-
return result
2523+
return result, selected_profiles, enabled_profiles

packages/runner/src/robotcode/runner/cli/libdoc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ def libdoc(app: Application, robot_options_and_args: Tuple[str, ...]) -> None:
7272
try:
7373
profile = (
7474
load_robot_config_from_path(*config_files)
75-
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose)
76-
.evaluated_with_env()
75+
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error)
76+
.evaluated_with_env(verbose_callback=app.verbose, error_callback=app.error)
7777
)
7878

7979
except (TypeError, ValueError) as e:

packages/runner/src/robotcode/runner/cli/rebot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ def rebot(app: Application, robot_options_and_args: Tuple[str, ...]) -> None:
7373
try:
7474
profile = (
7575
load_robot_config_from_path(*config_files)
76-
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose)
77-
.evaluated_with_env()
76+
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error)
77+
.evaluated_with_env(verbose_callback=app.verbose, error_callback=app.error)
7878
)
7979

8080
except (TypeError, ValueError) as e:

packages/runner/src/robotcode/runner/cli/robot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ def handle_robot_options(
110110
try:
111111
profile = (
112112
load_robot_config_from_path(*config_files)
113-
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose)
114-
.evaluated_with_env()
113+
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error)
114+
.evaluated_with_env(verbose_callback=app.verbose, error_callback=app.error)
115115
)
116116
except (TypeError, ValueError) as e:
117117
raise click.ClickException(str(e)) from e

packages/runner/src/robotcode/runner/cli/testdoc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ def testdoc(app: Application, robot_options_and_args: Tuple[str, ...]) -> None:
7373
try:
7474
profile = (
7575
load_robot_config_from_path(*config_files)
76-
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose)
77-
.evaluated_with_env()
76+
.combine_profiles(*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error)
77+
.evaluated_with_env(verbose_callback=app.verbose, error_callback=app.error)
7878
)
7979

8080
except (TypeError, ValueError) as e:

src/robotcode/cli/commands/profiles.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
DiscoverdBy,
1414
load_robot_config_from_path,
1515
)
16-
from robotcode.robot.config.model import EvaluationError, RobotProfile
1716
from robotcode.robot.config.utils import get_config_files
1817

1918

@@ -44,11 +43,11 @@ def show(app: Application, no_evaluate: bool, paths: List[Path]) -> None:
4443
config_files, _, _ = get_config_files(paths, app.config.config_files, verbose_callback=app.verbose)
4544

4645
config = load_robot_config_from_path(*config_files).combine_profiles(
47-
*(app.config.profiles or []), verbose_callback=app.verbose
46+
*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error
4847
)
4948

5049
if not no_evaluate:
51-
config = config.evaluated_with_env(verbose_callback=app.verbose)
50+
config = config.evaluated_with_env(verbose_callback=app.verbose, error_callback=app.error)
5251

5352
app.print_data(
5453
config,
@@ -77,24 +76,21 @@ def list(app: Application, paths: List[Path], show_hidden: bool = False, sort_by
7776
config_files, _, discovered_by = get_config_files(paths, app.config.config_files, verbose_callback=app.verbose)
7877

7978
config = load_robot_config_from_path(*config_files)
80-
selected_profiles = [
81-
k for k in config.select_profiles(*(app.config.profiles or []), verbose_callback=app.verbose).keys()
82-
]
8379

84-
def check_enabled(name: str, profile: RobotProfile) -> bool:
85-
try:
86-
return profile.enabled is None or bool(profile.enabled)
87-
except EvaluationError as e:
88-
raise ValueError(f"Cannot evaluate profile '{name}'.enabled: {e}") from e
80+
_, selected_profiles, enabled_names = config.combine_profiles_ex(
81+
*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error
82+
)
83+
84+
selected_names = [k for k in selected_profiles.keys()]
8985

9086
result: Dict[str, Any] = {
9187
"profiles": sorted(
9288
[
9389
{
9490
"name": k,
95-
"enabled": check_enabled(k, v),
91+
"enabled": k in enabled_names,
9692
"description": v.description or "",
97-
"selected": True if k in selected_profiles else False,
93+
"selected": True if k in selected_names else False,
9894
"precedence": v.precedence,
9995
}
10096
for k, v in (config.profiles or {}).items()
@@ -140,12 +136,12 @@ def check_enabled(name: str, profile: RobotProfile) -> bool:
140136
f'| Description{(max_description - len("Description")) * " "} |\n'
141137
)
142138
header += f"|:------:|:------:|:--------:|:-------:|:{max_name * '-'}-|:{max_description * '-'}-|\n"
143-
for selected, enabled, name, description, precedence in (
139+
for selected_profiles, enabled, name, description, precedence in (
144140
(v["selected"], v["enabled"], v["name"], v["description"], v["precedence"]) for v in result["profiles"]
145141
):
146142
header += (
147-
f'| {"*" if selected and enabled else " "} '
148-
f'| {"*" if selected else " "} '
143+
f'| {"*" if selected_profiles and enabled else " "} '
144+
f'| {"*" if selected_profiles else " "} '
149145
f'| {"*" if enabled else " "} '
150146
f'| {precedence if precedence else " "} '
151147
f'| {name}{(max_name - len(name)) * " "} '

tests/robotcode/robot/config/test_profile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def test_if_profile_is_not_defined_an_error_is_raised() -> None:
113113

114114
with pytest.raises(
115115
ValueError,
116-
match="Can't find any profiles matching the pattern 'nonexistent'.",
116+
match="Can't find any configuration profiles matching the pattern 'nonexistent'.",
117117
):
118118
config.combine_profiles("nonexistent")
119119

0 commit comments

Comments
 (0)