Skip to content

Add configurable remote Python path (fixes #314)#315

Merged
nitrobass24 merged 3 commits intodevelopfrom
fix/remote-python-compat
Mar 25, 2026
Merged

Add configurable remote Python path (fixes #314)#315
nitrobass24 merged 3 commits intodevelopfrom
fix/remote-python-compat

Conversation

@nitrobass24
Copy link
Owner

@nitrobass24 nitrobass24 commented Mar 25, 2026

Summary

  • Python 3.5 compatibility: scan_fs.py now works on remote servers with Python 3.5+ by replacing modern type syntax (X | None, list[X]) with typing.List/Optional and removing from __future__ import annotations
  • Configurable remote Python path: New Remote Python Path setting in the Server section lets users specify a custom Python binary on their seedbox (e.g. ~/python3/bin/python3). Defaults to python3 when empty.
  • Documentation: Updated README, configuration docs, and FAQ with the new setting and custom Python path guidance

Changes

File Change
scan_fs.py Python 3.5 compat: use typing imports, quoted forward refs, lower version check
config.py Add remote_python_path property (empty string default)
remote_scanner.py Accept remote_python_path param, use it instead of hardcoded python3
controller.py Pass remote_python_path from config to RemoteScanner
options-list.ts Add "Remote Python Path" text field to Server settings
pyproject.toml Add ruff per-file ignores for scan_fs.py (UP006/UP035/UP045)
test_config.py Add remote_python_path to golden config string
test_remote_scanner.py Add directory check to test data, add custom Python path tests
README.md, configuration.md, faq.md Document the new setting

Test plan

  • All 425 Python unit tests pass
  • All 291 Angular unit tests pass
  • Ruff lint passes (including scan_fs.py with per-file ignores)
  • Verify Docker build succeeds in CI
  • Manual test: set Remote Python Path to a custom path and verify scanner uses it

Closes #314

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a "Remote Python Path" setting so the remote scanner can use a custom Python 3 executable (falls back to python3 when empty).
  • Documentation

    • Updated configuration docs and FAQ with examples and guidance for the new setting and clarified that the remote scanner requires Python 3.5+.

- Add `remote_python_path` config field so users with custom Python
  installs on their seedbox can override the default `python3`
- Make scan_fs.py compatible with Python 3.5+ (use typing.List/Optional
  instead of modern syntax, remove `from __future__ import annotations`)
- Add Remote Python Path option to Settings UI (Server section)
- Wire config through controller to RemoteScanner
- Update README, docs, and FAQ with the new setting
- Add ruff per-file ignores for scan_fs.py (Python 3.5 compat)
- Fix test data for directory check guard in _install_scanfs

Closes #314

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Adds a configurable "Remote Python Path" setting, threads it through config and controller into RemoteScanner so remote scans invoke the specified Python binary (falling back to python3), lowers scan script requirement to Python 3.5, and updates docs and tests.

Changes

Cohort / File(s) Summary
Configuration & UI
src/python/common/config.py, src/angular/src/app/pages/settings/options-list.ts
Added lftp.remote_python_path config property and corresponding Settings UI option (Remote Python Path).
Controller Wiring
src/python/controller/controller.py
Passes remote_python_path from config into RemoteScanner when creating pair context.
Remote Scanning
src/python/controller/scan/remote_scanner.py, src/python/scan_fs.py
RemoteScanner accepts remote_python_path, computes the remote python command (default python3), and uses it when composing the remote shell invocation; scan_fs.py compatibility lowered to Python 3.5 and typing adjusted for older parsers.
Tests
src/python/tests/unittests/test_common/test_config.py, src/python/tests/unittests/test_controller/test_scan/test_remote_scanner.py
Updated config serialization tests and expanded RemoteScanner tests to assert custom-path and fallback behavior; adjusted mocked SSH call sequences to include an added directory-check invocation.
Linting & Docs
src/python/pyproject.toml, README.md, website/docs/configuration.md, website/docs/faq.md
Added per-file Ruff ignores for scan_fs.py; documented the new Remote Python Path setting and updated FAQ/troubleshooting to mention Python 3.5+ and custom path guidance.

Sequence Diagram(s)

sequenceDiagram
  participant UI as "Web UI"
  participant Config as "Config Layer"
  participant Controller as "Controller"
  participant Scanner as "RemoteScanner"
  participant SSH as "SSH Client"
  participant Remote as "Remote Host"

  UI->>Config: user sets Remote Python Path
  Controller->>Config: read lftp.remote_python_path
  Controller->>Scanner: init(..., remote_python_path)
  Scanner->>SSH: ssh.shell(run "<remote_python_cmd> /tmp/scan_fs.py ...")
  SSH->>Remote: execute command
  Remote-->>SSH: command output
  SSH-->>Scanner: result
  Scanner-->>Controller: scan result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • #161 — Updates Controller._create_pair_context to pass a remote_python_path into RemoteScanner (closely related wiring change).
  • #148 — Changed RemoteScanner to explicitly invoke python3; this PR makes that executable configurable.
  • #114 — Modified RemoteScanner remote command construction; relates to the adjustments in _run_scanfs_with_retry.

Suggested labels

refactor

Poem

🐇 I nibbled paths across the wire,

tucked Python where it must aspire,
No more assumptions, one small tweak,
Old boxes hum, new things speak,
A rabbit's hop — config complete!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.88% 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 'Add configurable remote Python path (fixes #314)' clearly and concisely summarizes the main change: adding a configurable option for the remote Python path to resolve issue #314.
Linked Issues check ✅ Passed The PR fully addresses issue #314's objectives: scan_fs.py is made Python 3.5+ compatible, remote_python_path is configurable via settings, and the scanner uses the configured path.
Out of Scope Changes check ✅ Passed All changes are directly related to issue #314: Python compatibility updates, configuration property additions, scanner wiring, UI field, tests, documentation, and lint rules are all in scope.

✏️ 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/remote-python-compat

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

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/python/tests/unittests/test_common/test_config.py (1)

546-593: 🧹 Nitpick | 🔵 Trivial

Add one non-empty remote_python_path round-trip assertion.

This change covers the empty case well; please also add a non-empty path check (e.g., /opt/python/bin/python3) to guard serialization/deserialization regressions for configured values.

✅ Minimal test extension
-        config.lftp.remote_python_path = ""
+        config.lftp.remote_python_path = "/opt/python/bin/python3"
...
-        remote_python_path =
+        remote_python_path = /opt/python/bin/python3
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/python/tests/unittests/test_common/test_config.py` around lines 546 -
593, Set a non-empty remote_python_path before serializing and assert it
round-trips: change the assignment config.lftp.remote_python_path = "" to
config.lftp.remote_python_path = "/opt/python/bin/python3", update the expected
golden block to include the line remote_python_path = /opt/python/bin/python3,
and ensure the test still calls config.to_file(config_file_path) and reads
actual_str to assert the serialized file contains that non-empty path (use the
existing config_file_path, config.to_file and actual_str variables).
src/python/common/config.py (1)

257-291: ⚠️ Potential issue | 🟡 Minor

Add .strip() to normalize remote_python_path before use.

The config definition is correct, but downstream in src/python/controller/scan/remote_scanner.py (line 42), the parameter is stored without trimming whitespace. The check if remote_python_path else "python3" only guards against empty strings—whitespace-only input (e.g., spaces) would pass through and create malformed commands. Add .strip() when assigning: self.__remote_python_cmd = (remote_python_path.strip() if remote_python_path else "python3") to handle this edge case.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/python/common/config.py` around lines 257 - 291, The remote Python
command can be a whitespace-only string because remote_python_path isn't trimmed
before use; update the RemoteScanner (or the code that sets __remote_python_cmd)
to use remote_python_path.strip() when building the command — e.g., compute
__remote_python_cmd as (remote_python_path.strip() if remote_python_path else
"python3") so whitespace-only values fall back to "python3".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/python/controller/scan/remote_scanner.py`:
- Around line 86-98: The constructed shell commands insert
self.__remote_python_cmd unescaped which will break for paths with
spaces/special chars; update both branches that call self.__ssh.shell to wrap
__remote_python_cmd with the same escaping used for script/scan paths (use
_escape_remote_path_double in the first branch and _escape_remote_path_single in
the else branch) so the full command string uses escaped Python path along with
the escaped script and scan paths.

In `@src/python/scan_fs.py`:
- Around line 189-190: Update the Python version guard that currently checks
sys.hexversion in scan_fs.py: change the numeric literal from 0x03050000 to
0x030C0000 and update the sys.exit message string to "Python 3.12 or newer is
required to run this program." so the check in the top-level version validation
uses Python 3.12 as the minimum.

---

Outside diff comments:
In `@src/python/common/config.py`:
- Around line 257-291: The remote Python command can be a whitespace-only string
because remote_python_path isn't trimmed before use; update the RemoteScanner
(or the code that sets __remote_python_cmd) to use remote_python_path.strip()
when building the command — e.g., compute __remote_python_cmd as
(remote_python_path.strip() if remote_python_path else "python3") so
whitespace-only values fall back to "python3".

In `@src/python/tests/unittests/test_common/test_config.py`:
- Around line 546-593: Set a non-empty remote_python_path before serializing and
assert it round-trips: change the assignment config.lftp.remote_python_path = ""
to config.lftp.remote_python_path = "/opt/python/bin/python3", update the
expected golden block to include the line remote_python_path =
/opt/python/bin/python3, and ensure the test still calls
config.to_file(config_file_path) and reads actual_str to assert the serialized
file contains that non-empty path (use the existing config_file_path,
config.to_file and actual_str variables).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dfe317df-84db-4f72-af9c-602355acfe7f

📥 Commits

Reviewing files that changed from the base of the PR and between 15e036c and 71353b5.

📒 Files selected for processing (11)
  • README.md
  • src/angular/src/app/pages/settings/options-list.ts
  • src/python/common/config.py
  • src/python/controller/controller.py
  • src/python/controller/scan/remote_scanner.py
  • src/python/pyproject.toml
  • src/python/scan_fs.py
  • src/python/tests/unittests/test_common/test_config.py
  • src/python/tests/unittests/test_controller/test_scan/test_remote_scanner.py
  • website/docs/configuration.md
  • website/docs/faq.md

Comment on lines +189 to +190
if sys.hexversion < 0x03050000:
sys.exit("Python 3.5 or newer is required to run this program.")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify no Python 3.6+ specific syntax remains in scan_fs.py
# Check for f-strings (3.6+), walrus operator (3.8+), or union types (3.10+)

echo "Checking for f-strings (Python 3.6+):"
rg -n 'f"[^"]*\{' src/python/scan_fs.py || echo "No f-strings found"
rg -n "f'[^']*\{" src/python/scan_fs.py || echo "No f-strings found"

echo ""
echo "Checking for walrus operator := (Python 3.8+):"
rg -n ':=' src/python/scan_fs.py || echo "No walrus operators found"

echo ""
echo "Checking for union type syntax | (Python 3.10+):"
rg -n '\w+\s*\|\s*\w+' src/python/scan_fs.py | grep -v '#' || echo "No union types found"

Repository: nitrobass24/seedsync

Length of output: 284


🏁 Script executed:

cd src/python && head -200 scan_fs.py | tail -20

Repository: nitrobass24/seedsync

Length of output: 844


Version check must be updated to Python 3.12 to comply with coding guidelines.

The file src/python/scan_fs.py matches the pattern src/python/**/*.py, which requires Python 3.12 per project guidelines. The current check if sys.hexversion < 0x03050000: must be changed to 0x030C0000 (Python 3.12). Update the error message accordingly to "Python 3.12 or newer is required to run this program."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/python/scan_fs.py` around lines 189 - 190, Update the Python version
guard that currently checks sys.hexversion in scan_fs.py: change the numeric
literal from 0x03050000 to 0x030C0000 and update the sys.exit message string to
"Python 3.12 or newer is required to run this program." so the check in the
top-level version validation uses Python 3.12 as the minimum.

nitrobass24 and others added 2 commits March 25, 2026 09:13
- Escape __remote_python_cmd with the same quoting used for script/scan
  paths so paths with spaces or special chars work correctly
- Strip whitespace from remote_python_path so whitespace-only values
  fall back to "python3"
- Use non-empty remote_python_path in config round-trip test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

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 `@src/python/tests/unittests/test_controller/test_scan/test_remote_scanner.py`:
- Around line 478-501: The command-building logic in RemoteScanner
(remote_scanner.py) currently chooses quoting based solely on
remote_path_to_scan and thus single-quotes the entire command when the scan path
is absolute, preventing tilde expansion in remote_python_path (e.g.
"~/python3/bin/python3"); modify the code that constructs the shell command (the
Scan flow / scan() or the helper that formats arguments) to decide quoting per
argument: if an argument starts with '~' do not wrap it in single quotes (or
wrap that arg in double quotes) so the shell can expand the tilde, otherwise
keep existing quoting behavior; ensure remote_python_path and
remote_path_to_scan are handled independently when building the final command
string passed to self.mock_ssh.shell.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cf13c14e-4c65-4efc-849a-70005bbbb7c4

📥 Commits

Reviewing files that changed from the base of the PR and between 71353b5 and 56c18e7.

📒 Files selected for processing (3)
  • src/python/controller/scan/remote_scanner.py
  • src/python/tests/unittests/test_common/test_config.py
  • src/python/tests/unittests/test_controller/test_scan/test_remote_scanner.py

Comment on lines +478 to +501
def test_uses_custom_python_path(self):
"""When remote_python_path is set, uses it instead of 'python3'"""
scanner = RemoteScanner(
remote_address="my remote address",
remote_username="my remote user",
remote_password="my password",
remote_port=1234,
remote_path_to_scan="/remote/path/to/scan",
local_path_to_scan_script=TestRemoteScanner.temp_scan_script,
remote_path_to_scan_script="/remote/path/to/scan/script",
remote_python_path="~/python3/bin/python3",
)

self.mock_ssh.shell.side_effect = self._make_shell_side_effect(
[
b"d41d8cd98f00b204e9800998ecf8427e", # md5sum - matches, skip install
json.dumps([]).encode(), # scanfs
]
)

scanner.scan()
self.mock_ssh.shell.assert_called_with(
"'~/python3/bin/python3' '/remote/path/to/scan/script' '/remote/path/to/scan'"
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify how tilde is handled in the remote_python_path configuration documentation
rg -n -C3 'remote_python_path' --type=md

Repository: nitrobass24/seedsync

Length of output: 1782


🏁 Script executed:

#!/bin/bash
# First, list files to understand structure
git ls-files "src/python/tests/unittests/test_controller/test_scan/" | head -20

Repository: nitrobass24/seedsync

Length of output: 1885


🏁 Script executed:

#!/bin/bash
# Find the RemoteScanner implementation
find . -type f -name "*.py" | xargs grep -l "class RemoteScanner" | head -5

Repository: nitrobass24/seedsync

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Try to read the test file directly
cat -n "src/python/tests/unittests/test_controller/test_scan/test_remote_scanner.py" | sed -n '478,520p'

Repository: nitrobass24/seedsync

Length of output: 2089


🏁 Script executed:

#!/bin/bash
# Read RemoteScanner implementation - focusing on scan method
cat -n "src/python/controller/scan/remote_scanner.py" | head -150

Repository: nitrobass24/seedsync

Length of output: 7667


🏁 Script executed:

#!/bin/bash
# Search for the scan method and command construction in RemoteScanner
rg -n "def scan" "src/python/controller/scan/remote_scanner.py" -A 50

Repository: nitrobass24/seedsync

Length of output: 2532


🏁 Script executed:

#!/bin/bash
# Check for other tests related to python path with different scan paths
rg -n "remote_python_path" "src/python/tests/unittests/test_controller/test_scan/test_remote_scanner.py" -B 3 -A 3

Repository: nitrobass24/seedsync

Length of output: 1023


🏁 Script executed:

#!/bin/bash
# Check documentation or comments about tilde expansion
rg -n "tilde\|~" "src/python/controller/scan/remote_scanner.py" -B 1 -A 1

Repository: nitrobass24/seedsync

Length of output: 46


Fix: Tilde in remote_python_path won't expand when scan path is absolute.

The implementation determines quoting strategy based only on whether remote_path_to_scan starts with ~ (lines 84-101 in remote_scanner.py). When the scan path is absolute but remote_python_path contains tilde (e.g., ~/bin/python3), single quotes are applied to all arguments. This prevents tilde expansion in the python path, causing execution to fail with "command ~ not found."

The test correctly reflects this behavior, but the behavior is a bug: users configuring remote_python_path="~/python3/bin/python3" with an absolute remote_path will encounter failures. The quoting logic should evaluate tilde expansion independently for each path argument, or consistently use double quotes when any argument requires expansion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/python/tests/unittests/test_controller/test_scan/test_remote_scanner.py`
around lines 478 - 501, The command-building logic in RemoteScanner
(remote_scanner.py) currently chooses quoting based solely on
remote_path_to_scan and thus single-quotes the entire command when the scan path
is absolute, preventing tilde expansion in remote_python_path (e.g.
"~/python3/bin/python3"); modify the code that constructs the shell command (the
Scan flow / scan() or the helper that formats arguments) to decide quoting per
argument: if an argument starts with '~' do not wrap it in single quotes (or
wrap that arg in double quotes) so the shell can expand the tilde, otherwise
keep existing quoting behavior; ensure remote_python_path and
remote_path_to_scan are handled independently when building the final command
string passed to self.mock_ssh.shell.

@nitrobass24 nitrobass24 merged commit 45bffa5 into develop Mar 25, 2026
12 checks passed
@nitrobass24 nitrobass24 deleted the fix/remote-python-compat branch March 25, 2026 15:18
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.

1 participant