Skip to content

Commit a20c9b9

Browse files
authored
Refactor remaining pieces related to reduce_node_name. (#184)
1 parent 99ba717 commit a20c9b9

18 files changed

+105
-124
lines changed

src/_pytask/collect.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from _pytask.config import IS_FILE_SYSTEM_CASE_SENSITIVE
1818
from _pytask.console import console
1919
from _pytask.console import create_summary_panel
20+
from _pytask.console import format_task_id
2021
from _pytask.exceptions import CollectionError
2122
from _pytask.mark_utils import has_marker
2223
from _pytask.nodes import create_task_name
@@ -111,7 +112,7 @@ def pytask_collect_file(
111112
spec = importlib_util.spec_from_file_location(path.stem, str(path))
112113

113114
if spec is None:
114-
raise ImportError(f"Can't find module '{path.stem}' at location {path}.")
115+
raise ImportError(f"Can't find module {path.stem!r} at location {path}.")
115116

116117
mod = importlib_util.module_from_spec(spec)
117118
spec.loader.exec_module(mod)
@@ -340,7 +341,12 @@ def pytask_collect_log(
340341
if report.node is None:
341342
header = "Error"
342343
else:
343-
short_name = reduce_node_name(report.node, session.config["paths"])
344+
if isinstance(report.node, MetaTask):
345+
short_name = format_task_id(
346+
report.node, editor_url_scheme="no_link", short_name=True
347+
)
348+
else:
349+
short_name = reduce_node_name(report.node, session.config["paths"])
344350
header = f"Could not collect {short_name}"
345351

346352
console.rule(

src/_pytask/compat.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ def import_optional_dependency(
7777
if extra and not extra.endswith(" "):
7878
extra += " "
7979
msg = (
80-
f"{caller} requires the optional dependency '{install_name}'. {extra}"
81-
f"Use pip or conda to install '{install_name}'."
80+
f"{caller} requires the optional dependency {install_name!r}. {extra}"
81+
f"Use pip or conda to install {install_name!r}."
8282
)
8383
try:
8484
module = importlib.import_module(name)
@@ -102,8 +102,8 @@ def import_optional_dependency(
102102
version = _get_version(module_to_get)
103103
if parse_version(version) < parse_version(minimum_version):
104104
msg = (
105-
f"{caller} requires version '{minimum_version}' or newer of "
106-
f"'{parent}' (version '{version}' currently installed)."
105+
f"{caller} requires version {minimum_version!r} or newer of "
106+
f"{parent!r} (version {version!r} currently installed)."
107107
)
108108
if errors == "warn":
109109
warnings.warn(msg, UserWarning)
@@ -123,10 +123,10 @@ def check_for_optional_program(
123123
"""Check whether an optional program exists."""
124124
if errors not in ("warn", "raise", "ignore"):
125125
raise ValueError(
126-
f"'errors' must be one of 'warn', 'raise' or 'ignore' and not '{errors}'."
126+
f"'errors' must be one of 'warn', 'raise' or 'ignore' and not {errors!r}."
127127
)
128128

129-
msg = f"{caller} requires the optional program '{name}'. {extra}"
129+
msg = f"{caller} requires the optional program {name!r}. {extra}"
130130

131131
program_exists = shutil.which(name) is not None
132132

src/_pytask/console.py

+21-6
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,12 @@
8888
console = Console(theme=theme, color_system=_COLOR_SYSTEM)
8989

9090

91-
def render_to_string(text: Union[str, Text], console: Optional[Console] = None) -> str:
91+
def render_to_string(
92+
text: Union[str, Text],
93+
*,
94+
console: Optional[Console] = None,
95+
strip_styles: bool = False,
96+
) -> str:
9297
"""Render text with rich to string including ANSI codes, etc..
9398
9499
This function allows to render text with is not automatically printed with rich. For
@@ -104,6 +109,9 @@ def render_to_string(text: Union[str, Text], console: Optional[Console] = None)
104109
if console.no_color and console._color_system:
105110
segments = Segment.remove_color(segments)
106111

112+
if strip_styles:
113+
segments = Segment.strip_styles(segments)
114+
107115
for segment in segments:
108116
if segment.style:
109117
output.append(
@@ -134,7 +142,12 @@ def format_task_id(
134142
task_name = task.base_name
135143
else:
136144
path, task_name = task.name.split("::")
137-
url_style = create_url_style_for_task(task, editor_url_scheme)
145+
146+
if task.function is None:
147+
url_style = Style()
148+
else:
149+
url_style = create_url_style_for_task(task.function, editor_url_scheme)
150+
138151
task_id = Text.assemble(
139152
Text(path + "::", style="dim"), Text(task_name, style=url_style)
140153
)
@@ -146,17 +159,19 @@ def format_strings_as_flat_tree(strings: Iterable[str], title: str, icon: str) -
146159
tree = Tree(title)
147160
for name in strings:
148161
tree.add(icon + name)
149-
text = render_to_string(tree, console)
162+
text = render_to_string(tree, console=console)
150163
return text
151164

152165

153-
def create_url_style_for_task(task: "MetaTask", edtior_url_scheme: str) -> Style:
166+
def create_url_style_for_task(
167+
task_function: Callable[..., Any], edtior_url_scheme: str
168+
) -> Style:
154169
"""Create the style to add a link to a task id."""
155170
url_scheme = _EDITOR_URL_SCHEMES.get(edtior_url_scheme, edtior_url_scheme)
156171

157172
info = {
158-
"path": _get_file(task.function),
159-
"line_number": _get_source_lines(task.function),
173+
"path": _get_file(task_function),
174+
"line_number": _get_source_lines(task_function),
160175
}
161176

162177
return Style() if not url_scheme else Style(link=url_scheme.format(**info))

src/_pytask/execute.py

+9-17
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from _pytask.console import console
1010
from _pytask.console import create_summary_panel
1111
from _pytask.console import create_url_style_for_task
12+
from _pytask.console import format_task_id
1213
from _pytask.console import unify_styles
1314
from _pytask.dag import descending_tasks
1415
from _pytask.dag import TopologicalSorter
@@ -24,7 +25,6 @@
2425
from _pytask.report import ExecutionReport
2526
from _pytask.session import Session
2627
from _pytask.shared import get_first_non_none_value
27-
from _pytask.shared import reduce_node_name
2828
from _pytask.traceback import format_exception_without_traceback
2929
from _pytask.traceback import remove_traceback_from_exc_info
3030
from _pytask.traceback import render_exc_info
@@ -186,7 +186,7 @@ def pytask_execute_task_process_report(
186186
Mark(
187187
"skip_ancestor_failed",
188188
(),
189-
{"reason": f"Previous task '{task.name}' failed."},
189+
{"reason": f"Previous task {task.name!r} failed."},
190190
)
191191
)
192192

@@ -204,7 +204,7 @@ def pytask_execute_task_process_report(
204204
def pytask_execute_task_log_end(session: Session, report: ExecutionReport) -> None:
205205
"""Log task outcome."""
206206
url_style = create_url_style_for_task(
207-
report.task, session.config["editor_url_scheme"]
207+
report.task.function, session.config["editor_url_scheme"]
208208
)
209209
console.print(
210210
report.outcome.symbol,
@@ -260,21 +260,13 @@ def pytask_execute_log_end(session: Session, reports: List[ExecutionReport]) ->
260260

261261
def _print_errored_task_report(session: Session, report: ExecutionReport) -> None:
262262
"""Print the traceback and the exception of an errored report."""
263-
task_name = reduce_node_name(report.task, session.config["paths"])
264-
if len(task_name) > console.width - 15:
265-
task_name = report.task.base_name
266-
267-
url_style = create_url_style_for_task(
268-
report.task, session.config["editor_url_scheme"]
269-
)
270-
271-
console.rule(
272-
Text(
273-
f"Task {task_name} failed",
274-
style=unify_styles(report.outcome.style, url_style),
275-
),
276-
style=report.outcome.style,
263+
task_name = format_task_id(
264+
task=report.task,
265+
editor_url_scheme=session.config["editor_url_scheme"],
266+
short_name=True,
277267
)
268+
text = Text.assemble("Task ", task_name, " failed", style="failed")
269+
console.rule(text, style=report.outcome.style)
278270

279271
console.print()
280272

src/_pytask/graph.py

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from _pytask.shared import get_first_non_none_value
2323
from _pytask.shared import reduce_names_of_multiple_nodes
2424
from _pytask.traceback import remove_internal_traceback_frames_from_exc_info
25+
from rich.text import Text
2526
from rich.traceback import Traceback
2627

2728

@@ -232,6 +233,7 @@ def _shorten_node_labels(dag: nx.DiGraph, paths: List[Path]) -> nx.DiGraph:
232233
"""Shorten the node labels in the graph for a better experience."""
233234
node_names = dag.nodes
234235
short_names = reduce_names_of_multiple_nodes(node_names, dag, paths)
236+
short_names = [i.plain if isinstance(i, Text) else i for i in short_names]
235237
old_to_new = dict(zip(node_names, short_names))
236238
dag = nx.relabel_nodes(dag, old_to_new)
237239
return dag

src/_pytask/logging.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def _humanize_time(
207207
index = i
208208
break
209209
else:
210-
raise ValueError(f"The time unit '{unit}' is not known.")
210+
raise ValueError(f"The time unit {unit!r} is not known.")
211211

212212
seconds = amount * _TIME_UNITS[index]["in_seconds"]
213213

src/_pytask/mark/structures.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def __getattr__(self, name: str) -> MarkDecorator:
224224
raise ValueError(f"Unknown pytask.mark.{name}.")
225225
# Raise a specific error for common misspellings of "parametrize".
226226
if name in ["parameterize", "parametrise", "parameterise"]:
227-
warnings.warn(f"Unknown '{name}' mark, did you mean 'parametrize'?")
227+
warnings.warn(f"Unknown {name!r} mark, did you mean 'parametrize'?")
228228

229229
warnings.warn(
230230
f"Unknown pytask.mark.{name} - is this a typo? You can register "

src/_pytask/nodes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,8 @@ def _collect_nodes(
260260
)
261261
if collected_node is None:
262262
raise NodeNotCollectedError(
263-
f"'{node}' cannot be parsed as a dependency or product for task "
264-
f"'{name}' in '{path}'."
263+
f"{node!r} cannot be parsed as a dependency or product for task "
264+
f"{name!r} in {path!r}."
265265
)
266266
else:
267267
collected_nodes[node_name] = collected_node

src/_pytask/parametrize.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def pytask_parametrize_task(
139139
)
140140
raise ValueError(
141141
"The following ids are duplicated while parametrizing task "
142-
f"'{name}'.\n\n{text}\n\nIt might be caused by "
142+
f"{name!r}.\n\n{text}\n\nIt might be caused by "
143143
"parametrizing the task with the same combination of arguments "
144144
"multiple times. Change the arguments or change the ids generated by "
145145
"the parametrization."
@@ -296,7 +296,7 @@ def _check_if_n_arg_names_matches_n_arg_values(
296296
idx_example = [i == n_names for i in n_values].index(False)
297297
formatted_example = pprint.pformat(arg_values[idx_example])
298298
raise ValueError(
299-
f"Task '{name}' is parametrized with {n_names} 'arg_names', {arg_names}, "
299+
f"Task {name!r} is parametrized with {n_names} 'arg_names', {arg_names}, "
300300
f"but the number of provided 'arg_values' is {pretty_arg_values}. For "
301301
f"example, here are the values of parametrization no. {idx_example}:"
302302
f"\n\n{formatted_example}"

src/_pytask/profile.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def pytask_profile_export_profile(
272272
elif extension is None:
273273
pass
274274
else:
275-
raise ValueError(f"The export option '{extension}' cannot be handled.")
275+
raise ValueError(f"The export option {extension!r} cannot be handled.")
276276

277277

278278
def _export_to_csv(profile: Dict[str, Dict[str, Any]]) -> None:

src/_pytask/resolve_dependencies.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ def _format_dictionary_to_tree(dict_: Dict[str, List[str]], title: str) -> str:
265265
for task in tasks:
266266
branch.add(Text.assemble(TASK_ICON, task))
267267

268-
text = render_to_string(tree)
268+
text = render_to_string(tree, console=console, strip_styles=True)
269269
return text
270270

271271

src/_pytask/shared.py

+23-12
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from typing import Union
1111

1212
import networkx as nx
13-
from _pytask.nodes import create_task_name
13+
from _pytask.console import format_task_id
1414
from _pytask.nodes import MetaNode
1515
from _pytask.nodes import MetaTask
1616
from _pytask.path import find_closest_ancestor
@@ -120,7 +120,7 @@ def parse_value_or_multiline_option(
120120
elif isinstance(value, str):
121121
return value.strip()
122122
else:
123-
raise ValueError(f"Input '{value}' is neither a 'str' nor 'None'.")
123+
raise ValueError(f"Input {value!r} is neither a 'str' nor 'None'.")
124124

125125

126126
def convert_truthy_or_falsy_to_bool(x: Union[bool, str, None]) -> bool:
@@ -133,7 +133,7 @@ def convert_truthy_or_falsy_to_bool(x: Union[bool, str, None]) -> bool:
133133
out = None
134134
else:
135135
raise ValueError(
136-
f"Input '{x}' is neither truthy (True, true, 1) or falsy (False, false, 0)."
136+
f"Input {x!r} is neither truthy (True, true, 1) or falsy (False, false, 0)."
137137
)
138138
return out
139139

@@ -155,13 +155,10 @@ def reduce_node_name(node: "MetaNode", paths: Sequence[Union[str, Path]]) -> str
155155
except ValueError:
156156
ancestor = node.path.parents[-1]
157157

158-
if isinstance(node, MetaTask):
159-
shortened_path = relative_to(node.path, ancestor)
160-
name = create_task_name(shortened_path, node.base_name)
161-
elif isinstance(node, MetaNode):
158+
if isinstance(node, MetaNode):
162159
name = relative_to(node.path, ancestor).as_posix()
163160
else:
164-
raise TypeError(f"Unknown node {node} with type '{type(node)}'.")
161+
raise TypeError(f"Unknown node {node} with type {type(node)!r}.")
165162

166163
return name
167164

@@ -170,7 +167,21 @@ def reduce_names_of_multiple_nodes(
170167
names: List[str], dag: nx.DiGraph, paths: Sequence[Union[str, Path]]
171168
) -> List[str]:
172169
"""Reduce the names of multiple nodes in the DAG."""
173-
return [
174-
reduce_node_name(dag.nodes[n].get("node") or dag.nodes[n].get("task"), paths)
175-
for n in names
176-
]
170+
short_names = []
171+
for name in names:
172+
node = dag.nodes[name].get("node") or dag.nodes[name].get("task")
173+
174+
if isinstance(node, MetaTask):
175+
short_name = format_task_id(
176+
node, editor_url_scheme="no_link", short_name=True
177+
)
178+
elif isinstance(node, MetaNode):
179+
short_name = reduce_node_name(node, paths)
180+
else:
181+
raise TypeError(
182+
f"Requires 'MetaTask' or 'MetaNode' and not {type(node)!r}."
183+
)
184+
185+
short_names.append(short_name)
186+
187+
return short_names

src/_pytask/skipping.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def pytask_execute_task_process_report(
9797
Mark(
9898
"skip",
9999
(),
100-
{"reason": f"Previous task '{task.name}' was skipped."},
100+
{"reason": f"Previous task {task.name!r} was skipped."},
101101
)
102102
)
103103

0 commit comments

Comments
 (0)