Question: How to properly use action callbacks in a multi-file app
Hi, currently I'm trying to create an app using a multi-file approach. I've seen the examples (one file and multi file), but I find it difficult to translate some of the workings from one file to multi-file.
Broadly speaking, I'm trying to decouple the application as much as possible from the GUI. Therefore, I have e.g. a controller instance in the app_model Application instance. With the controller I can e.g. load a file, modify it, save it etc. without ever touching Qt.
- Is having the "business logic" (if you can call it that) on the Application instance the recommended way of doing it?
Now when I have an Action callback operating on the main window (e.g. mainWindow.open()) in the single file approach we can just use callback=mainWindow.open. In the multi-file approach example we can't because of circular imports (but can use QApplication to get the window).
- How can I connect a callback to something like
mainWindow.open? I tried using a provider there but that gets called when there is nomainWindowyet.
It'd really help, if the same functionality of the single file app was replicated in the multi file app example.
Thanks
Hey @ndxmrb
Broadly speaking, I'm trying to decouple the application as much as possible from the GUI.
👍 definitely part of the goal here
Is having the "business logic" (if you can call it that) on the Application instance the recommended way of doing it?
i'm not sure. the Application is, minimally, a place to organize the three registries: Commands (the actual business logic that executes actions), Menu placements (places where those commands appear in the GUI) and Keybindings (ways to trigger those commands via keys).
So, there's a sense in which the command registry already is a controller. But naturally, any real application is going to need additional routing and stuff on top of that. whether you put that logic on the Application instance or not, I think doesn't matter?
Now when I have an Action callback operating on the main window (e.g. mainWindow.open()) in the single file approach we can just use callback=mainWindow.open. In the multi-file approach example we can't because of circular imports (but can use QApplication to get the window).
Yeah, definitely the hardest concept here is going from a procedural pattern (where you build an instance, and connect its signals to some callback), to a declarative pattern that needs to somehow represent the concept of an action before any instances have been created. Basically, that means that all of your actions need some way to be provided with the objects that they need to act on. So, you should not try to grab some actual instance you've constructed in another file... you need to come up with a way to fetch it at function call time, not at function definition time.
Concretely, they either need to fetch those objects themselves, or you need to "provide" or inject those dependencies when the function is called.
One way is for the function to fetch the dependency, as is done here in the multi-file example:
https://github.com/pyapp-kit/app-model/blob/25b47999abdfa7d1c808e36026fc4d08b828ce9d/demo/multi_file/functions.py#L9-L12
alternatively, you could use the dependency injection pattern
def close(window: QWidget | None) -> None:
"""Give me a widget, I'll close it!"""
if window:
window.close()
app = Application(name="my_app")
app.injection_store.register_provider(provider=lambda: QApplication.activeWindow(), type_hint=QWidget)
[!tip] I myself still struggle with the merits of these two patterns. Dependency injection has such a lovely separation of concerns, but absolutely struggles with "reconstructing" the context in which the function was called. That could also be solved with dependency injection, if you had a
context: Contextdependency... you could define exactly how it is constructed. But in any case, if you're struggling to decide/weigh these patterns here, you're not alone :)