Skip to content

PR1: forms facade + IronPython backend extraction + CPython stubs#3039

Merged
jmcouffin merged 14 commits intopyrevitlabs:developfrom
OriAshkenazi:refactor/forms-facade-pr1
Feb 16, 2026
Merged

PR1: forms facade + IronPython backend extraction + CPython stubs#3039
jmcouffin merged 14 commits intopyrevitlabs:developfrom
OriAshkenazi:refactor/forms-facade-pr1

Conversation

@OriAshkenazi
Copy link
Copy Markdown
Contributor

PR1: Refactor pyrevit.forms into a lightweight facade that routes to an IronPython backend, and add a CPython stub backend that preserves importability while enforcing a consistent error contract.

What changed

  • Extracted the existing IronPython implementation into pyrevitlib/pyrevit/forms/_ipy_forms.py (intended to be behavior-identical).
  • Added pyrevitlib/pyrevit/forms/__init__.py facade routing based on pyrevit.compat.IRONPY (no WPF/clr imports in facade).
  • Added CPython stub backend pyrevitlib/pyrevit/forms/_cpy_forms.py so import pyrevit.forms succeeds under CPython.
  • Enforced CPython contract: unsupported symbols raise PyRevitCPythonNotSupported("pyrevit.forms.<symbol>").
  • Updated pyRevitDevTools CPython regression test to validate the PR1 contract (no dialogs invoked).
  • Fixed output.print_md under CPython by handling sys.stdout.Flush() vs flush().
  • Documented the facade/backend split and CPython contract in docs/architecture.md.

Manual validation (Revit 2025)

  • CPython: pyRevitDevTools → Debug → Engine Tests → Test CPython Regressions (passes).
  • IronPython: pyRevitDevTools → Debug → Unit Tests → Forms Module Tests (no regressions observed).

Contributing while PR1 is open

  • Please do not add new commits to this PR branch unless requested by review.
  • Start follow-up work (PR2+) on separate branches.
  • If your work depends on this refactor, branch off this PR branch locally and keep it as draft/stacked until PR1 merges; then rebase onto develop.
  • Avoid mixing CPython WPF/loader/dialog implementations into this PR; PR1 scope is limited to refactor + CPython import/error contract.

@devloai
Copy link
Copy Markdown
Contributor

devloai bot commented Jan 29, 2026

Unable to trigger custom agent "Code Reviewer"You have run out of credits 😔
Please upgrade your plan or buy additional credits from the subscription page.

Copy link
Copy Markdown
Contributor

@sanzoghenzo sanzoghenzo left a comment

Choose a reason for hiding this comment

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

Thanks for the PR!

I've made a few comments, most of them are due to the overuse of AI in the documentation side.

Another thing I would suggest, and I'm sorry if I didn't tell you before, is to remove the "_forms" from the modules names, since we know we are talking about forms from the package name.
But this is my personal preference and just an implementation detail that should remain hidden to the user, so it's fine to leave it like that.

@jmcouffin jmcouffin added the Python 3 Issues related to cpython engines [subsystem] label Jan 29, 2026
@OriAshkenazi OriAshkenazi force-pushed the refactor/forms-facade-pr1 branch from 1f17d7c to b017c93 Compare February 1, 2026 11:10
@OriAshkenazi
Copy link
Copy Markdown
Contributor Author

Rebased on latest develop and resolved the pyrevitlib/pyrevit/forms/__init__.py conflict by taking the updated upstream forms implementation into _ipy_forms.py (only adjusting the two self-imports to be relative) and keeping __init__.py as a lightweight facade.

Addressed review feedback:

  • Docstrings/docs now describe what the module does and avoid milestone/history language.
  • CPython regression test split into three focused tests; simplified exception handling.
  • Removed aliasing of PyRevitCPythonNotSupported in the facade and CPython stubs.
  • docs/architecture.md section shortened and aligned with surrounding tone.

Validation:

  • python -m compileall pyrevitlib\pyrevit\forms
  • python -m py_compile "extensions\pyRevitDevTools.extension\pyRevitDev.tab\Debug.panel\Engine Tests.pulldown\Test CPython Regressions.pushbutton\script.py"
  • pipenv run mkdocs build -q

Revit manual checks were run earlier (CPython regression + IronPython forms tests); not re-run after this rebase since changes were docstrings/tests/docs only.

@jmcouffin
Copy link
Copy Markdown
Contributor

Please remove the _forms from the names of the ipy_forms and cpy_forms modules and change the code accordingly in the init and anywhere else necessary

@OriAshkenazi
Copy link
Copy Markdown
Contributor Author

OriAshkenazi commented Feb 1, 2026

Rebased on latest develop and addressed the rename request:

  • Renamed backend modules to drop the _forms suffix (_ipy.py, _cpy.py) and updated facade imports + architecture docs.

Test results (Revit 2025):

  • CPython: pyRevitDevTools -> Debug -> Engine Tests -> Test CPython Regressions (pass)
  • IronPython: pyRevitDevTools -> Debug -> Unit Tests -> Forms Module Tests (pass; no dialogs/tracebacks)

@OriAshkenazi OriAshkenazi force-pushed the refactor/forms-facade-pr1 branch from 1013ad9 to 6f0c683 Compare February 1, 2026 11:50
Copy link
Copy Markdown
Contributor

@sanzoghenzo sanzoghenzo left a comment

Choose a reason for hiding this comment

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

final round 😉

@jmcouffin
Copy link
Copy Markdown
Contributor

Would you mind trying it out @sanzoghenzo ?

@jmcouffin
Copy link
Copy Markdown
Contributor

FYI,
I will release now, and if this PR and the next go well, I will do a specific release right after

@sanzoghenzo
Copy link
Copy Markdown
Contributor

Would you mind trying it out @sanzoghenzo ?

I'll try to squeeze some time tomorrow, not promising anything though

@OriAshkenazi
Copy link
Copy Markdown
Contributor Author

I'll free some time tomorrow to rebase the branch with the latest develop and fix all the requested changes. I think a release should be done only after we finish implementing the second step of the agreed plan, as it would feel more like a cohesive feature than a behind-the-scenes change. I'll also try to elaborate on the second step, so we will have more implementation heuristics to choose from.

@OriAshkenazi OriAshkenazi force-pushed the refactor/forms-facade-pr1 branch from 6f0c683 to 267a5ca Compare February 5, 2026 14:57
@OriAshkenazi
Copy link
Copy Markdown
Contributor Author

Resolved the two remaining threads:

  • Simplified docs/architecture.md by removing the detailed pyrevit.forms subsection and keeping a single high-level note about engine-specific backends.
  • Updated CPython stdout flush handling to try: sys.stdout.flush() then except AttributeError: sys.stdout.Flush(), gated behind not IRONPY.

Scoped validations run:

  • py -3.12 -m compileall pyrevitlib\pyrevit\forms
  • py -3.12 -m compileall pyrevitlib\pyrevit\output
  • py -3.12 -m py_compile "extensions\pyRevitDevTools.extension\pyRevitDev.tab\Debug.panel\Engine Tests.pulldown\Test CPython Regressions.pushbutton\script.py"
  • pipenv run mkdocs build -q

Please re-review when you get a chance.

@OriAshkenazi
Copy link
Copy Markdown
Contributor Author

As promised, here's a clearer plan for the follow-up (PR2) once this PR1 facade/stub contract lands.

PR2 goal (small + reviewable): make one built-in pyrevit.forms dialog work under CPython in Revit (GetValueWindow.xaml, which backs forms.ask_for_string()), while keeping IronPython behavior unchanged and keeping everything else stubbed behind the existing PyRevitCPythonNotSupported("pyrevit.forms.<symbol>") contract.

Proposed PR2 slices

  • PR2A (foundation): add a minimal CPython WPF/XAML loader helper (based on XamlReader.Load) + name lookup helpers + (if needed) owner/dispatcher helpers. No UI parity claims yet.
  • PR2B (PoC): implement CPython WPFWindow/TemplateUserInputWindow just enough for GetValueWindow and ask_for_string() end-to-end (show dialog, wire events in code, return value).

Implementation heuristics (based on the Dynamo PythonNet3 WPF writeups):

  • Load XAML via XmlReader.Create(StringReader(xaml)) + System.Windows.Markup.XamlReader.Load(reader) (instead of wpf.LoadComponent).
  • "Merge" the loaded WPF Window with a Python controller class via the __new__ + window.__class__ = cls pattern.
  • Discover named controls after load via window.FindName("...") and/or LogicalTreeHelper.FindLogicalNode(window, "...").
  • Wire events in code (button.Click += handler) rather than relying on XAML event attributes.
  • If needed, wrap UI entrypoints in Application.Current.Dispatcher.Invoke(...) to guarantee execution on the Revit UI thread.

Key heuristic / decision point (existing XAML event attributes): CPython/pythonnet won't reliably bind XAML event attributes like Click="..." / TextChanged="...", and GetValueWindow.xaml currently includes both. For the PoC I'm leaning toward:

  • strip a small allowlist of event attributes at load time (only what GetValueWindow.xaml uses), then
  • wire events explicitly in code (okayButton.Click += ..., stringValue_tb.TextChanged += ...).
    If you'd prefer a different approach (e.g., editing XAML to remove event attributes, or a broader preprocessor), let me know before I proceed.

Validation approach

  • Keep the existing CPython regressions non-interactive.
  • Add a separate DevTools interactive smoke command that opens the PoC dialog under CPython so reviewers can validate quickly inside Revit.

@jmcouffin
Copy link
Copy Markdown
Contributor

The plan sounds good. No opinion on the decision point, this is far from my regular playground, so I'll trust you and your AI friends ;p

I'll test PR1 whenever I get the chance this weekend

@OriAshkenazi
Copy link
Copy Markdown
Contributor Author

I'll be more than happy to share my AI stack for contributing to this repo 🙂
When we're done with the forms I intend to contribute some overdue scaffolding for AI oriented development in the repo (skills, tests, agents.md files etc.). I currently gitignore my stack until I'll plan a more robust and tool agnostic approach to it.

Copy link
Copy Markdown
Contributor

@sanzoghenzo sanzoghenzo left a comment

Choose a reason for hiding this comment

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

I can't help myself, just a little edit please 😉

@sanzoghenzo
Copy link
Copy Markdown
Contributor

I'll be more than happy to share my AI stack for contributing to this repo 🙂 When we're done with the forms I intend to contribute some overdue scaffolding for AI oriented development in the repo (skills, tests, agents.md files etc.). I currently gitignore my stack until I'll plan a more robust and tool agnostic approach to it.

Can you make it a little less verbose? Not really fond of lengthy issue comments that brings not that much of info to the table.... But maybe it's just my adversion to LLM generated content 😅

@OriAshkenazi
Copy link
Copy Markdown
Contributor Author

Well, I see the less verbose approach as not didactic enough. I believe that the more everything is documented and explained, more successful contributes will come. Moreover, AI-First development is a careful game of semantics-statistics, sometimes you need to let the model "leave its mark" in documentation to keep the development cohesive. Since large refactoring is a matter of a few dollars these days, I find it more useful to keep the clean up for when the feature is fully implemented. Lastly, I'm not sure exactly where, but i feel like the changes introduced in this PR need to be represented in the architecture documentation in some way, as the architecture changed.
Part of the AI stack the repo needs are more AGENTS.md files that document the expected behavior of AI agents in the repo, along side concrete skills that will help the agents get more work done, more reliably. Both are in the works, based on my pyRevit AI extension development template.

@sanzoghenzo
Copy link
Copy Markdown
Contributor

I believe that the more everything is documented and explained, more successful contributes will come.

Agreed, but when I have to read a full page describing a simple "let's create a forms module for the cpython engine and a facade to import the correct version transparently" (I'm exaggerating here, bear with me), I feel like the AI is wasted tokens for the creator and wasted time for the reader.
But again, it's just me that I'm against the overuse of AI.

I'm not sure exactly where, but i feel like the changes introduced in this PR need to be represented in the architecture documentation

I might have done the wrong thing when I named that document "architecture.md".
That was meant to be a gentle, bird-eye view introduction for new developers, not a detailed technical document of the entire architecture.

For that we should add a separate document, and of course an AI agent can do wonders!

Part of the AI stack the repo needs are more AGENTS.md files that document the expected behavior of AI agents in the repo, along side concrete skills that will help the agents get more work done, more reliably. Both are in the works, based on my pyRevit AI extension development template.

I still suck at this agents setup (just today I got an entire python module of my project wiped out by gpt4.1 on opencode and replaced with # rest of the code unchanged... comments 😅 ), so I'm really interested in this!

Since large refactoring is a matter of a few dollars these days

And quite a bit of CO2, unfortunately... but I digress.

@sanzoghenzo
Copy link
Copy Markdown
Contributor

Sorry for the off-topic.
With regards of the decision points, what you propose sounds like a good plan.

Hoping that sometime in the future we got support for event attributes from upstream pythonnet!

@OriAshkenazi OriAshkenazi force-pushed the refactor/forms-facade-pr1 branch from 601ff27 to 3910867 Compare February 8, 2026 11:47
sanzoghenzo
sanzoghenzo previously approved these changes Feb 8, 2026
Copy link
Copy Markdown
Contributor

@sanzoghenzo sanzoghenzo left a comment

Choose a reason for hiding this comment

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

Thank you for your contribution and your patience!

I still didn't test it, but it looks good, so I believe we can go ahead with the second step 😉

@OriAshkenazi
Copy link
Copy Markdown
Contributor Author

PR2 coming right up 😉 this time with less verbose documentation.

@jmcouffin
Copy link
Copy Markdown
Contributor

I haven't forgotten about this one
I just need to carve some time this weekend to take a look.

@jmcouffin jmcouffin self-assigned this Feb 16, 2026
jmcouffin
jmcouffin previously approved these changes Feb 16, 2026
Copy link
Copy Markdown
Contributor

@jmcouffin jmcouffin left a comment

Choose a reason for hiding this comment

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

Passes QA

@jmcouffin jmcouffin merged commit 2fdc5d6 into pyrevitlabs:develop Feb 16, 2026
@jmcouffin
Copy link
Copy Markdown
Contributor

One step beyond!
thanks @OriAshkenazi

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.0.0.26047+2110-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.0.0.26047+2131-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.0.0.26047+2147-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.0.0.26047+2248-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New work-in-progress (wip) builds are available for 6.1.0.26047+2255-wip

@github-actions
Copy link
Copy Markdown
Contributor

📦 New public release are available for 6.1.0.26047+2349

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

Labels

Python 3 Issues related to cpython engines [subsystem]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants