Skip to content

fix(label): raise CSVError instead of sys.exit on duplicate identifier#65

Open
gkorland wants to merge 3 commits into
masterfrom
fix/label-no-sysexit
Open

fix(label): raise CSVError instead of sys.exit on duplicate identifier#65
gkorland wants to merge 3 commits into
masterfrom
fix/label-no-sysexit

Conversation

@gkorland

@gkorland gkorland commented Apr 26, 2026

Copy link
Copy Markdown

Summary

Label.update_node_dictionary called sys.exit(1) when it encountered a duplicate node identifier and --skip-invalid-nodes was not set. sys.exit from a library function raises SystemExit (a BaseException), which is awkward for callers to catch and makes unit testing more difficult.

Changes

  • falkordb_bulk_loader/label.py: raise CSVError with a descriptive "Duplicate node identifier '' at :" message instead of sys.exit(1).
  • Add CSVError to the existing from .exceptions import … line.

Testing

Lint clean. CLI behaviour is preserved: an unhandled CSVError still terminates the process with a non-zero exit code, and the same descriptive line is also written to stderr (existing message unchanged).

Memory / Performance Impact

N/A.

Related Issues

From the comprehensive code-review report (BUG-11). Note: this PR addresses BUG-11 only; the related BUG-1 (the duplicate-node row is also still inserted, corrupting the ID mapping when skip_invalid_nodes=True) is handled separately.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced error handling for CSV validation and parsing failures with improved error messaging
    • Improved detection and reporting of duplicate node identifier errors

Calling `sys.exit(1)` from within a library method raises `SystemExit`,
which is awkward to catch (callers must explicitly handle a base-class
exception) and makes unit testing more difficult. Raise a domain
`CSVError` with a descriptive message instead. The CLI entry point
already lets exceptions propagate and exits non-zero on uncaught
errors, so user-visible behaviour for the default
(`skip_invalid_nodes=False`) case is preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

CSV validation error handling is refactored to use exceptions instead of direct process termination. When duplicate node identifiers or CSV parsing failures occur, CSVError exceptions are raised and caught in bulk processing operations, enabling structured error propagation instead of immediate termination.

Changes

Cohort / File(s) Summary
CSV Error Handling Refactoring
falkordb_bulk_loader/bulk_insert.py, falkordb_bulk_loader/label.py
Replaces direct sys.exit(1) calls with exception-based error handling. label.py now raises CSVError when duplicate node identifiers are encountered and skip validation is disabled. bulk_insert.py wraps entity processing in a try-catch block to catch and handle CSVError gracefully during CSV parsing and validation.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 Where exits once would abruptly cease,
Now exceptions flow with graceful ease,
CSV errors caught, no sudden quit,
The loader handles each validation hit,
Error paths cleaner, control flows dance!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary change: replacing sys.exit with raising CSVError in label.py for duplicate identifier handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/label-no-sysexit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@gkorland
gkorland marked this pull request as ready for review April 26, 2026 17:58
@gkorland

Copy link
Copy Markdown
Author

Copilot Code Review

No significant issues found.

When label.py raises CSVError on a duplicate node identifier, the
exception now propagates to the CLI entry point where it is caught,
printed to stderr, and converted to sys.exit(1). This restores the
clean non-zero exit behaviour that existed when sys.exit(1) was called
directly from label.py, and fixes the failing test_reused_identifier
test that uses catch_exceptions=False.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gkorland
gkorland force-pushed the fix/label-no-sysexit branch from 034bb36 to 709ae57 Compare April 29, 2026 06:40
@codecov

codecov Bot commented Apr 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.57%. Comparing base (983a393) to head (709ae57).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #65      +/-   ##
==========================================
+ Coverage   89.52%   89.57%   +0.05%     
==========================================
  Files           9        9              
  Lines         611      614       +3     
==========================================
+ Hits          547      550       +3     
  Misses         64       64              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gkorland

Copy link
Copy Markdown
Author

Agent Review Summary

PR Assessment ✅

PR title: fix(label): raise CSVErrorinstead ofsys.exit on duplicate identifier

This PR correctly replaces sys.exit(1) in label.py with a proper raise CSVError(...), making the library more testable and caller-friendly. The change itself was sound.


Issue Found & Fixed 🔧

CI was failing (Test with Python 3.10 & 3.11) because CSVError raised in label.py propagated uncaught through bulk_insert.py. The existing test test_reused_identifier used catch_exceptions=False, which causes Click's CliRunner to re-raise non-SystemExit exceptions — breaking the test.

Root cause: bulk_insert.py had no handler for CSVError from process_entities().

Fix applied (commit 709ae57):

In falkordb_bulk_loader/bulk_insert.py:

try:
    process_entities(labels)
    process_entities(reltypes)
except CSVError as e:
    sys.exit(str(e))

Using sys.exit(str(e)) (rather than sys.exit(1)) preserves the error message in SystemExit.code, which satisfies test_script_failures's assertion assert expected_msg in str(res.exception).


CI Results ✅

All 9 checks now pass:

  • ✅ Test with Python 3.10
  • ✅ Test with Python 3.11
  • ✅ Code linting
  • ✅ CodeQL (actions + python)
  • ✅ Codecov patch + project

@gkorland

Copy link
Copy Markdown
Author

@copilot resolve the merge conflicts in this pull request

@gkorland

Copy link
Copy Markdown
Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…dling with debug logging

Co-authored-by: gkorland <753206+gkorland@users.noreply.github.com>

Copilot AI commented Apr 29, 2026

Copy link
Copy Markdown

@copilot resolve the merge conflicts in this pull request

Merge conflicts resolved in eb19db3. The only conflict was in bulk_insert.py where master had added debug logging around process_entities calls while this branch wrapped them in a try/except for CSVError. The resolution keeps both: the debug log statements are now inside the try/except block.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to improve library-usage and testability by removing a sys.exit(1) call from Label.update_node_dictionary() and replacing it with a typed exception (CSVError) when a duplicate node identifier is encountered.

Changes:

  • Raise CSVError (instead of calling sys.exit) on duplicate node identifiers in Label.update_node_dictionary.
  • Import CSVError in label.py.
  • Catch CSVError in bulk_insert and terminate the CLI run.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
falkordb_bulk_loader/label.py Replaces sys.exit(1) with raising CSVError for duplicate node IDs.
falkordb_bulk_loader/bulk_insert.py Adds CSVError handling around entity processing to exit on CSV-level errors.
Comments suppressed due to low confidence (2)

falkordb_bulk_loader/bulk_insert.py:231

  • sys.exit(str(e)) inside a Click command is likely to not display the error message to the user (Click catches SystemExit and typically only uses it for the exit code). If the goal is to surface the CSVError message on stderr while exiting non-zero, prefer converting it to a Click-friendly exception (e.g., raise a click.ClickException) or explicitly echo the message to stderr and then exit with a numeric status code.
        module_list = [m["name"] for m in client.connection.module_list()]
        if "graph" not in module_list:
            logger.error("falkordb module not loaded on connected server.")
            sys.exit(1)
        logger.debug("FalkorDB module is loaded on the server.")

falkordb_bulk_loader/bulk_insert.py:231

  • The PR description says an "unhandled CSVError" will still terminate the process, but this change explicitly catches CSVError and converts it to SystemExit. Either update the PR description to reflect this behavior change, or adjust the implementation to match the stated intent (e.g., let CSVError propagate and be handled consistently elsewhere).
        module_list = [m["name"] for m in client.connection.module_list()]
        if "graph" not in module_list:
            logger.error("falkordb module not loaded on connected server.")
            sys.exit(1)
        logger.debug("FalkorDB module is loaded on the server.")

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 59 to +63
% (identifier, self.infile.name, self.reader.line_num)
)
sys.stderr.flush()
if self.config.skip_invalid_nodes is False:
sys.exit(1)
raise CSVError(

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@falkordb_bulk_loader/bulk_insert.py`:
- Around line 260-266: The CSVError except block is too narrow—expand the
try/except to include the parse_schemas(...) call so any CSVError raised during
schema parsing or during process_entities(labels)/process_entities(reltypes) is
caught; specifically wrap the call to parse_schemas(...) along with the
logger.debug("Processing nodes...")/process_entities(labels) and
logger.debug("Processing relations...")/process_entities(reltypes) in the same
try and keep the existing except CSVError as e: sys.exit(str(e)) behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 43bde68f-c1bc-4cef-b5d8-cbd704daf61c

📥 Commits

Reviewing files that changed from the base of the PR and between ababbb2 and eb19db3.

📒 Files selected for processing (2)
  • falkordb_bulk_loader/bulk_insert.py
  • falkordb_bulk_loader/label.py

Comment on lines +260 to +266
try:
logger.debug("Processing nodes...")
process_entities(labels)
logger.debug("Processing relations...")
process_entities(reltypes)
except CSVError as e:
sys.exit(str(e))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

CSVError handling scope is too narrow for consistent CLI behavior.

Line 260 only wraps process_entities(...). parse_schemas(...) (Lines 252-257) can also raise CSVError (e.g., relation/header validation), which would still escape uncaught in CliRunner(catch_exceptions=False) scenarios.

Suggested fix
-    # Read the header rows of each input CSV and save its schema.
-    logger.debug("Parsing node CSV schemas...")
-    labels = parse_schemas(Label, query_buf, nodes, nodes_with_label, config)
-    logger.debug(f"Parsed {len(labels)} node CSV file(s).")
-    logger.debug("Parsing relation CSV schemas...")
-    reltypes = parse_schemas(
-        RelationType, query_buf, relations, relations_with_type, config
-    )
-    logger.debug(f"Parsed {len(reltypes)} relation CSV file(s).")
-
-    try:
+    try:
+        # Read the header rows of each input CSV and save its schema.
+        logger.debug("Parsing node CSV schemas...")
+        labels = parse_schemas(Label, query_buf, nodes, nodes_with_label, config)
+        logger.debug(f"Parsed {len(labels)} node CSV file(s).")
+        logger.debug("Parsing relation CSV schemas...")
+        reltypes = parse_schemas(
+            RelationType, query_buf, relations, relations_with_type, config
+        )
+        logger.debug(f"Parsed {len(reltypes)} relation CSV file(s).")
+
         logger.debug("Processing nodes...")
         process_entities(labels)
         logger.debug("Processing relations...")
         process_entities(reltypes)
     except CSVError as e:
         sys.exit(str(e))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
logger.debug("Processing nodes...")
process_entities(labels)
logger.debug("Processing relations...")
process_entities(reltypes)
except CSVError as e:
sys.exit(str(e))
try:
# Read the header rows of each input CSV and save its schema.
logger.debug("Parsing node CSV schemas...")
labels = parse_schemas(Label, query_buf, nodes, nodes_with_label, config)
logger.debug(f"Parsed {len(labels)} node CSV file(s).")
logger.debug("Parsing relation CSV schemas...")
reltypes = parse_schemas(
RelationType, query_buf, relations, relations_with_type, config
)
logger.debug(f"Parsed {len(reltypes)} relation CSV file(s).")
logger.debug("Processing nodes...")
process_entities(labels)
logger.debug("Processing relations...")
process_entities(reltypes)
except CSVError as e:
sys.exit(str(e))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@falkordb_bulk_loader/bulk_insert.py` around lines 260 - 266, The CSVError
except block is too narrow—expand the try/except to include the
parse_schemas(...) call so any CSVError raised during schema parsing or during
process_entities(labels)/process_entities(reltypes) is caught; specifically wrap
the call to parse_schemas(...) along with the logger.debug("Processing
nodes...")/process_entities(labels) and logger.debug("Processing
relations...")/process_entities(reltypes) in the same try and keep the existing
except CSVError as e: sys.exit(str(e)) behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants