Skip to content

34 feature pyelk reformating and integration - #39

Open
LudoBroche wants to merge 12 commits into
mainfrom
34-feature-pyelk-reformating-and-integration
Open

LudoBroche wants to merge 12 commits into
mainfrom
34-feature-pyelk-reformating-and-integration

Conversation

@LudoBroche

@LudoBroche LudoBroche commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

PR summary

Import elk algorithms into ewoksdraw

AI Disclosure

  • Claude used to learn how Maturin works and how to set up a project.
  • Claude used to assess licensing needs

@LudoBroche LudoBroche linked an issue Sep 18, 2026 that may be closed by this pull request
@codecov

codecov Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@LudoBroche

Copy link
Copy Markdown
Member Author

WIP: This PR is intended to give an overview of what each solution would imply.

Solution 1: Python implementation

Rewrite the required ELK layout algorithm in Python:

Implications

  • Pure Python.
  • Easy installation.
  • We maintain the layout algorithm ourselves.
  • Only the features we need are implemented, not the complete ELK behavior.

Solution 2: Use elk-rs

Use elk-rs through a small Rust/Python wrapper:

Implications

  • Reuses the ELK implementation instead of maintaining our own algorithm.
  • Adds Rust, PyO3, Maturin, and elk-rs as maintenance dependencies.
  • Requires CI builds and tests on every supported platform.
  • Requires platform-specific wheels.
  • Development requires Rust and Cargo to be installed.

@LudoBroche

LudoBroche commented Sep 18, 2026 •

Copy link
Copy Markdown
Member Author

@loichuder Please have a look at these two solutions described here. I would love to have your opinion.

Comment thread rust/src/lib.rs

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The actual rust wrapper

Comment thread rust/src/lib.rs
Comment on lines +6 to +10
#[pyfunction]
#[pyo3(signature = (graph_json, options_json = "{}"))]
fn layout_json(graph_json: &str, options_json: &str) -> PyResult<String> {
layout_api::layout_json(graph_json, options_json).map_err(PyRuntimeError::new_err)
}

@LudoBroche LudoBroche Sep 18, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We create here our pyfunction

Note this wrapper holds the Python GIL lock.
If necessary, we can improve that by releasing the GIL since Rust doesn't need it.
In the near future I don't think multi-threading of ewoksdraw will be common,

Comment thread rust/src/lib.rs
Comment on lines +12 to +16
#[pymodule]
fn _elk_rs(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(layout_json, module)?)?;
Ok(())
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

And the python module

Comment thread rust/Cargo.toml
Comment on lines +15 to +18
[dependencies.org-eclipse-elk-graph-json]
git = "https://github.com/openedges/elk-rs.git"
rev = "2191680292b9565592223c22a28b5ef33c3acbad"
package = "org-eclipse-elk-graph-json"

@LudoBroche LudoBroche Sep 18, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

elk-rs doesn't have an official crates and only lives in github.
So we pin the github

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

They have tags at least. Could we use a tag instead of revision?

@LudoBroche LudoBroche Sep 18, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We call here our rust/python module and Python function to define the desired elk API.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

And we can use it directly

@loichuder

loichuder commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

@loichuder Please have a look at these two solutions described here. I would love to have our opinion.

I'd have liked to have yours before 😄

Personally, when I see the Python code of the layout, that looks like several hundred lines of code that I don't wish to maintain. I'd prefer to use the Rust binding.

@LudoBroche

Copy link
Copy Markdown
Member Author

Agree, I'll go with the rust binding then.
I ll keep this branch and PR.

@LudoBroche

Copy link
Copy Markdown
Member Author

I tested the rust-elk wrapper to generate the graphs.
Rusk-Elk uses a fancier algorithm for the tasks' placement, trying to make links direct and as short as possible.

Here is a comparison:

comparison_pyelk_vs_elk-rs

By tuning the option, we can get back to the old system, but I think I like the new compact way better.

@loichuder What your take on it ?

Comment thread LICENSES/elk-rs/LICENSE

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Since we bundle elk/elk-rs.
I'm adding the licenses for it

Comment thread THIRD_PARTY_NOTICES.md

@LudoBroche LudoBroche Oct 7, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Explain the licensing,
MIT Ewoksdraw
EPL2 for the elk bindings

This is AI Generated I have no knowledge of licensing convention

id: str
startPoint: ElkPoint
bendPoints: list[ElkPoint]
bendPoints: NotRequired[list[ElkPoint]]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

A straight edge has no bend point.
In that case elk-rs ignore the key

@LudoBroche LudoBroche Oct 7, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

testing the binding.
Fake graph and making sure the routing of elks-rs behave as expected:

@LudoBroche
LudoBroche requested a review from loichuder October 7, 2026 12:59
@LudoBroche

Copy link
Copy Markdown
Member Author

@loichuder It's a little big for my liking; don't be scared to bother and look at it together

@loichuder

Copy link
Copy Markdown
Member

Just merged my stack of PRs. Can you rebase?

@LudoBroche
LudoBroche force-pushed the 34-feature-pyelk-reformating-and-integration branch from 27dd3ea to 4a34c58 Compare October 8, 2026 09:11
@LudoBroche

Copy link
Copy Markdown
Member Author

Just merged my stack of PRs. Can you rebase?
@loichuder Good for review

@loichuder loichuder left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you update CONTRIBUTING with the additional requirements to setup the project in dev mode? I think at minimum, there should be a rust installation but perhaps something else?

result = _elk_rs.layout_json(json.dumps(graph))
except RuntimeError as e:
raise ElkLayoutError(str(e)) from e
return cast(ElkGraph, json.loads(result))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you think we should use a pydantic model instead of a dict for ElkGraph ? This would allow runtime validation and static typing.

Comment thread src/ewoksdraw/_elk_rs.pyi
@@ -0,0 +1 @@
def layout_json(graph_json: str, options_json: str = "{}") -> str: ...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this for mypy?

Comment thread THIRD_PARTY_NOTICES.md
are reproduced without modification and included in the wheel and source
distributions. The upstream notice describes the wider ELK project.

When updating the elk-rs revision in `rust/Cargo.toml`, update these source

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should be part of CONTRIBUTING IMO

Comment on lines +162 to +172
@pytest.mark.parametrize(
"graph_json, message",
[
("{not json", "Failed to parse graph JSON"),
("[]", "must be a json object"),
("{}", "Every element must have an id"),
],
)
def test_invalid_graph_raises(graph_json: str, message: str) -> None:
with pytest.raises(RuntimeError, match=message):
_elk_rs.layout_json(graph_json)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

parametrize seems often useful but I feel that it hampers readability.

I think it would read better if this was three separate tests.

path: dist

# No checkout and no Rust toolchain: only the wheel is tested
- name: Install wheel

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are we not testing the test from the sources anymore?

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.

[Feature]: pyElk reformating and integration

2 participants