Skip to content

Conversation

@PythonFZ
Copy link
Member

@PythonFZ PythonFZ commented Sep 9, 2025

Summary by CodeRabbit

  • New Features
    • Detects and optionally displays the Julia Packmol version when running with verbosity.
  • Bug Fixes
    • More robust handling when Packmol output is missing, with clearer, actionable error messages.
  • Refactor
    • Introduced module-level logging and enriched diagnostics for Packmol failures to aid troubleshooting.
  • Tests
    • Added unit test covering Packmol (Julia) version detection utility.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 9, 2025

Warning

Rate limit exceeded

@pre-commit-ci[bot] has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 7 minutes and 38 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 5c040a2 and ea2dc9a.

📒 Files selected for processing (2)
  • rdkit2ase/pack.py (4 hunks)
  • tests/test_utils.py (3 hunks)

Walkthrough

Adds Julia Packmol version detection via a new utility, integrates version-aware logging into pack(), and improves error handling when Packmol output is missing. Introduces a module logger, a FORMAT type alias, and a unit test asserting a specific Julia Packmol version string.

Changes

Cohort / File(s) Summary
Packmol integration and error handling
rdkit2ase/pack.py
Added module logger; introduced FORMAT type alias; integrated get_packmol_julia_version() when using packmol.jl; expanded diagnostics and logging; wrapped output read in try/except to raise descriptive FileExistsError with context.
Utilities for Julia Packmol version
rdkit2ase/utils.py
Added subprocess import; implemented get_packmol_julia_version() to run Julia Pkg.status("Packmol") and parse version; raises RuntimeError on failures; documented behavior.
Unit tests
tests/test_utils.py
Added test_get_packmol_julia_version expecting "v0.1.12"; exposed new utility in public API imports.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant pack as pack()
  participant utils as get_packmol_julia_version()
  participant Julia as julia Pkg.status
  participant Packmol as Packmol (CLI/Julia)
  participant FS as Filesystem

  User->>pack: call pack(output_format)
  alt Using Julia Packmol
    pack->>utils: get_packmol_julia_version()
    utils->>Julia: run `Pkg.status("Packmol")`
    Julia-->>utils: stdout (version info)
    utils-->>pack: "vX.Y.Z" or error
    Note over pack: Logs Packmol Julia version/path
  else Non-Julia Packmol
    Note over pack: Proceed without version query
  end

  pack->>Packmol: execute with input script
  Packmol-->>FS: write mixture.{format}
  pack->>FS: read mixture.{format}
  alt Output missing
    pack-->>User: raise FileExistsError with logged context
  else Output present
    pack-->>User: return Atoms
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Pre-merge checks (2 passed, 2 warnings, 1 inconclusive)

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning While the PR implements version detection via Pkg.status and improves FileNotFoundError handling, it does not provide any mechanism to allow passing a custom Julia interpreter path as required. Please extend the implementation to accept a custom Julia executable path, for example by adding a parameter or environment variable to get_packmol_julia_version and update related calls accordingly.
Out of Scope Changes Check ⚠️ Warning The PR introduces a new FORMAT type alias which falls outside the objectives defined in issue #72 and is unrelated to improving Julia Packmol detection or error handling. Please remove the FORMAT alias from this PR or move it to a separate change request, or provide justification for including it within the scope of the current improvements.
Description Check ❓ Inconclusive The current description “- fix #72” simply references the linked issue but does not summarize any of the actual changes, leaving it too vague to convey the scope or intent of the PR. Please expand the description to briefly outline the key changes introduced, such as version detection via Pkg.status, enhanced error handling, and any new public APIs.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title “improve julia / packmol detection” succinctly conveys the primary focus of the changeset by highlighting enhancements to Julia and Packmol detection logic without extraneous detail.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

I thump my paws at version strings,
Julia whispers what Packmol brings.
If files go missing in the night,
Logs will lantern shed their light.
Formats tidy, errors tamed—
A rabbit hops, precisely named.
Pack on, paths proclaimed!

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch julia-fix

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.

@codecov-commenter
Copy link

codecov-commenter commented Sep 9, 2025

Codecov Report

❌ Patch coverage is 57.14286% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.52%. Comparing base (3724a9d) to head (ea2dc9a).

Files with missing lines Patch % Lines
rdkit2ase/pack.py 36.84% 12 Missing ⚠️
rdkit2ase/utils.py 75.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #74      +/-   ##
==========================================
- Coverage   97.33%   96.52%   -0.81%     
==========================================
  Files          25       25              
  Lines        1723     1756      +33     
==========================================
+ Hits         1677     1695      +18     
- Misses         46       61      +15     

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

Copy link
Contributor

@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 (3)
rdkit2ase/utils.py (1)

226-256: Make Julia detection robust: support custom Julia path, handle not-found/timeout, and parse output safely

  • Hardcodes "julia" and misses a way to pass a custom interpreter path (PR objective).
  • Missing handling for FileNotFoundError/TimeoutExpired; current except only catches CalledProcessError.
  • Parsing with split()[2] is brittle across Pkg.status formats; prefer a regex.
  • Deduplicate/clarify the repeated long error message; include the interpreter path used.

Apply this diff within the function:

-def get_packmol_julia_version() -> str:
+def get_packmol_julia_version(julia: str | None = None, timeout: float = 30.0) -> str:
@@
-    try:
-        result = subprocess.run(
-            ["julia", "-e", 'import Pkg; Pkg.status("Packmol")'],
-            capture_output=True,
-            text=True,
-            check=True,
-        )
-        for line in result.stdout.splitlines():
-            if "Packmol" in line:
-                parts = line.split()
-                if len(parts) >= 3:
-                    return parts[2]
-        raise RuntimeError(
-            "Failed to get Packmol version via Julia. Please verify that you can"
-            ' import packmol via `julia -e "import Pkg; Pkg.status("Packmol")"`'
-        )
-
-    except subprocess.CalledProcessError as e:
-        raise RuntimeError(
-            "Failed to get Packmol version via Julia. Please verify that you can"
-            ' import packmol via `julia -e "import Pkg; Pkg.status("Packmol")"`'
-        ) from e
+    import os, re, shutil
+    cmd = julia or os.environ.get("JULIA", "julia")
+    resolved = shutil.which(cmd) or cmd  # allow absolute/custom paths
+    try:
+        result = subprocess.run(
+            [resolved, "-e", 'import Pkg; Pkg.status("Packmol")'],
+            capture_output=True,
+            text=True,
+            check=True,
+            timeout=timeout,
+        )
+    except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as e:
+        raise RuntimeError(
+            f"Failed to get Packmol version via Julia at '{cmd}'. "
+            "Ensure Julia is installed and Packmol.jl is available. "
+            "Try: julia -e 'import Pkg; Pkg.status(\"Packmol\")'."
+        ) from e
+    for line in result.stdout.splitlines():
+        m = re.search(r"\bPackmol\b[^\n]*\b(v?\d+\.\d+\.\d+(?:[+-].+)?)", line)
+        if m:
+            ver = m.group(1)
+            return ver if ver.startswith("v") else f"v{ver}"
+    raise RuntimeError(
+        "Could not parse Packmol version from Julia Pkg.status output. "
+        "Run locally: julia -e 'import Pkg; Pkg.status(\"Packmol\")'."
+    )

If needed outside this hunk, I can add imports/constants to the module.

rdkit2ase/pack.py (2)

63-88: Remove shell=True, pass stdin explicitly, and support custom Julia path

  • shell=True with user-provided executables is avoidable; pass argv list + stdin for Packmol.
  • Support a custom Julia path by threading it through to get_packmol_julia_version and the run command (PR objective).

Apply this diff:

-def _run_packmol(
-    packmol_executable: str,
-    input_file: pathlib.Path,
-    tmpdir: pathlib.Path,
-    verbose: bool,
-) -> None:
+def _run_packmol(
+    packmol_executable: str,
+    input_file: pathlib.Path,
+    tmpdir: pathlib.Path,
+    verbose: bool,
+    julia: str | None = None,
+) -> None:
@@
-    if packmol_executable == "packmol.jl":
-        version = get_packmol_julia_version()
+    if packmol_executable == "packmol.jl":
+        version = get_packmol_julia_version(julia=julia)
         if verbose:
             print(f"Using Packmol version {version} via Julia")
         with open(tmpdir / "pack.jl", "w") as f:
             f.write("using Packmol \n")
             f.write(f'run_packmol("{input_file.name}") \n')
-        command = f"julia {tmpdir / 'pack.jl'}"
+        command = [(julia or "julia"), str(tmpdir / "pack.jl")]
     else:
-        command = f"{packmol_executable} < {input_file.name}"
+        command = [packmol_executable]
 
-    subprocess.run(
-        command,
-        cwd=tmpdir,
-        shell=True,
-        check=True,
-        capture_output=not verbose,
-    )
+    if packmol_executable == "packmol.jl":
+        subprocess.run(
+            command,
+            cwd=tmpdir,
+            shell=False,
+            check=True,
+            capture_output=not verbose,
+        )
+    else:
+        with open(input_file, "rb") as fin:
+            subprocess.run(
+                command,
+                cwd=tmpdir,
+                shell=False,
+                check=True,
+                capture_output=not verbose,
+                stdin=fin,
+            )

152-162: Expose a julia parameter on pack() and thread it through

Allows callers to pass a custom Julia interpreter path (PR objective).

Apply this diff:

 def pack(
     data: list[list[ase.Atoms]],
     counts: list[int],
     density: float,
     seed: int = 42,
     tolerance: float = 2,
     verbose: bool = False,
     packmol: str = "packmol",
     pbc: bool = True,
-    output_format: FORMAT = "pdb",
+    output_format: FORMAT = "pdb",
+    julia: str | None = None,
 ) -> ase.Atoms:

And thread it to the runner:

-        _run_packmol(packmol, tmpdir / "pack.inp", tmpdir, verbose)
+        _run_packmol(packmol, tmpdir / "pack.inp", tmpdir, verbose, julia=julia)
🧹 Nitpick comments (1)
rdkit2ase/pack.py (1)

166-189: Docstring: mention julia parameter and fix minor typo

Add docs for the new parameter and correct “jula” → “Julia”.

Apply this diff:

     packmol : str, optional
         The path to the packmol executable, by default "packmol".
-        When installing packmol via jula, use "packmol.jl".
+        When installing packmol via Julia, use "packmol.jl".
+    julia : str | None, optional
+        Path to the Julia interpreter when using "packmol.jl".
+        Defaults to the "JULIA" env var or "julia" on PATH.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3724a9d and 5c040a2.

📒 Files selected for processing (3)
  • rdkit2ase/pack.py (4 hunks)
  • rdkit2ase/utils.py (2 hunks)
  • tests/test_utils.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
tests/test_utils.py (1)
rdkit2ase/utils.py (1)
  • get_packmol_julia_version (226-255)
rdkit2ase/pack.py (1)
rdkit2ase/utils.py (2)
  • calculate_box_dimensions (71-80)
  • get_packmol_julia_version (226-255)
🪛 Ruff (0.12.2)
rdkit2ase/utils.py

236-236: Starting a process with a partial executable path

(S607)


246-249: Avoid specifying long messages outside the exception class

(TRY003)


252-255: Avoid specifying long messages outside the exception class

(TRY003)

rdkit2ase/pack.py

223-223: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


226-226: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


228-228: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


229-232: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pytest (3.11, ubuntu-latest, 20.15.1)
  • GitHub Check: pytest (3.10, ubuntu-latest, 20.15.1)
  • GitHub Check: pytest (3.12, ubuntu-latest, 20.15.1)
  • GitHub Check: pytest (3.13, ubuntu-latest, 20.15.1)

Comment on lines 252 to 255
def test_get_packmol_julia_version():
"""Test the retrieval of the Packmol version when using Julia."""
version = get_packmol_julia_version()
assert version == "v0.1.12"
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid brittle version pin; skip if Julia missing and validate via regex

Asserting an exact Packmol.jl version will be flaky across environments and time. Also skip when Julia isn’t available on CI/dev machines.

Apply this diff:

-def test_get_packmol_julia_version():
-    """Test the retrieval of the Packmol version when using Julia."""
-    version = get_packmol_julia_version()
-    assert version == "v0.1.12"
+def test_get_packmol_julia_version(monkeypatch):
+    """Test retrieval of Packmol version via Julia (environment-agnostic)."""
+    import os, re, shutil
+    julia = os.environ.get("JULIA", "julia")
+    if shutil.which(julia) is None:
+        pytest.skip("Julia not available")
+    version = get_packmol_julia_version(julia=julia)
+    assert re.fullmatch(r"v\d+\.\d+\.\d+(?:[+-].+)?", version)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In tests/test_utils.py around lines 252 to 255, the test currently asserts an
exact Packmol.jl version which is brittle and will fail when Julia is not
present; change it to skip the test when Julia isn't available (e.g., use
pytest.skip or check shutil.which("julia") and call pytest.skip) and replace the
strict equality check with a regex-based assertion such as matching
r"^v\d+\.\d+\.\d+" (or similar) to validate the version format instead of
pinning an exact version.

@PythonFZ PythonFZ merged commit 338f4b3 into main Sep 9, 2025
6 checks passed
@PythonFZ PythonFZ deleted the julia-fix branch September 9, 2025 13:15
@coderabbitai coderabbitai bot mentioned this pull request Sep 9, 2025
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.

packmol julia improvements

3 participants