Skip to content

Commit 1d2edfe

Browse files
committed
Better compile errors for missing sources or segments.
1 parent ae46a5c commit 1d2edfe

File tree

5 files changed

+65
-8
lines changed

5 files changed

+65
-8
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Changelog
22

3-
## 0.10.0 (in progress)
3+
## in progress
44
### Improvements
55
- Changes several logging statements to make the consistent with the rest of the code
66
- Refactored **lambda** to use AbstractFieldSegment for consistency
@@ -15,6 +15,8 @@
1515
- Updated the ollama embedding and chat connector so that it uses the OLLAMA_SERVER_URL configuration
1616
variable as the host where ollama is installed. So OLLAMA_SERVER_URL can be set in the TOML configuration
1717
file or TALKPIPE_OLLAMA_SERVER_URL can be set as an environment variable.
18+
- Implemented better compile errors for when sources or segments are not found. It had been a key error.
19+
It will now be a compile error.
1820

1921
## 0.9.3
2022
### Improvements

src/talkpipe/chatterlang/compiler.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
logger = logging.getLogger(__name__)
1818

19+
class CompileError(Exception):
20+
""" Exception raised when a compilation error occurs in chatterlang """
21+
pass
22+
1923
@singledispatch
2024
def compile(script: ParsedScript, runtime: RuntimeComponent = None) -> Callable:
2125
""" Compile a parsed script into a callable function
@@ -58,7 +62,10 @@ def _(pipeline: ParsedPipeline, runtime: RuntimeComponent) -> Pipeline:
5862
ans = VariableSource(pipeline.input_node.source.name)
5963
logger.debug(f"Created variable source with name {pipeline.input_node.source.name}")
6064
else:
61-
ans = registry.input_registry.get(pipeline.input_node.source.name)(**_resolve_params(pipeline.input_node.params, runtime=runtime))
65+
try:
66+
ans = registry.input_registry.get(pipeline.input_node.source.name)(**_resolve_params(pipeline.input_node.params, runtime=runtime))
67+
except KeyError:
68+
raise CompileError(f"Source '{pipeline.input_node.source.name}' not found")
6269
logger.debug(f"Created registered input {pipeline.input_node.source.name}")
6370
ans.runtime = runtime
6471

@@ -68,7 +75,10 @@ def _(pipeline: ParsedPipeline, runtime: RuntimeComponent) -> Pipeline:
6875
next_transform = VariableSetSegment(transform.name)
6976
logger.debug(f"Created variable set segment for {transform.name}")
7077
elif isinstance(transform, SegmentNode):
71-
next_transform = registry.segment_registry.get(transform.operation.name)(**_resolve_params(transform.params, runtime=runtime))
78+
try:
79+
next_transform = registry.segment_registry.get(transform.operation.name)(**_resolve_params(transform.params, runtime=runtime))
80+
except KeyError:
81+
raise CompileError(f"Segment '{transform.operation.name}' not found")
7282
logger.debug(f"Created segment {transform.operation.name}")
7383
elif isinstance(transform, ForkNode):
7484
next_transform = compile(transform, runtime)

src/talkpipe/util/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ def load_script(script_input: str) -> str:
327327
try:
328328
is_file = script_path.is_file()
329329
except OSError as e:
330-
logger.warning(f"Script path could not be checked as a file {script_input[0:25]}...: {e}")
330+
logger.debug(f"Script path could not be checked as a file {script_input[0:25]}...: {e}")
331331
is_file = False
332332
if is_file:
333333
try:

tests/talkpipe/app/test_chatterlang_script.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,51 @@ def test_main_with_extra_module(capsys, tmp_path):
5252

5353
def test_main_with_constant_injection(capsys):
5454
test_script = """INPUT FROM echo[data=$TEST] | print"""
55-
55+
5656
with patch('argparse.ArgumentParser.parse_known_args') as mock_args:
5757
# Mock the return value of parse_known_args to simulate --script "INPUT FROM echo[data=TEST] | print" --TEST "hello"
5858
mock_namespace = type('MockNamespace', (), {})()
5959
mock_namespace.script = test_script
6060
mock_namespace.load_module = []
6161
mock_namespace.logger_levels = None
6262
mock_namespace.logger_files = None
63-
63+
6464
# Return the parsed args and unknown args (the constant)
6565
mock_args.return_value = (mock_namespace, ['--TEST', 'hello'])
66-
66+
6767
main()
68-
68+
6969
captured = capsys.readouterr()
7070
assert captured.out == "hello\n"
7171

7272

73+
def test_warning_messages_should_not_include_full_script_content(caplog):
74+
"""
75+
Test that warning messages use elegant formatting and do not include
76+
the full script content, which could clutter logs and expose sensitive information.
77+
"""
78+
test_script = 'INPUT FROM echo[data="test1,test2,test3"] ' + ''.join(['| print ' for _ in range(100)]) # Long script
79+
80+
with caplog.at_level(logging.WARNING):
81+
with patch('argparse.ArgumentParser.parse_known_args') as mock_args:
82+
mock_namespace = type('MockNamespace', (), {})()
83+
mock_namespace.script = test_script
84+
mock_namespace.load_module = []
85+
mock_namespace.logger_levels = None
86+
mock_namespace.logger_files = None
87+
88+
# Return the parsed args and no unknown args
89+
mock_args.return_value = (mock_namespace, [])
90+
91+
main()
92+
93+
# Verify that NO warning messages contain the full script content
94+
warning_messages = [record.message for record in caplog.records if record.levelname == 'WARNING']
95+
96+
for warning_msg in warning_messages:
97+
assert test_script not in warning_msg, (
98+
f"Warning message should not include full script content. "
99+
f"Found script in warning: {warning_msg}"
100+
)
101+
73102

tests/talkpipe/chatterlang/test_compiler.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,19 @@ def test_environment_variable_support():
293293
f = f.as_function(single_out=False)
294294
ans = list(f())
295295
assert ans == ['a', 'b', 'c', 'd']
296+
297+
def test_compile_error_missing_segment():
298+
try:
299+
compiler.compile("""INPUT FROM "test" | unknownSegment""")
300+
except compiler.CompileError as e:
301+
assert "Segment 'unknownSegment' not found" in str(e)
302+
else:
303+
assert False, "Expected CompileError was not raised"
304+
305+
def test_compile_error_in_source():
306+
try:
307+
compiler.compile("""INPUT FROM unknownSource""")
308+
except compiler.CompileError as e:
309+
assert "Source 'unknownSource' not found" in str(e)
310+
else:
311+
assert False, "Expected CompileError was not raised"

0 commit comments

Comments
 (0)