It's always difficult to determine how to structure an application, not only before starting the application, but also WHILE developing your application. OpenLP has grown organically over the last 10 years, and this means that the codebase has become unwieldy with a number of circular imports and other questionable coding practices.
This page will hopefully help us to structure OpenLP's codebase better.
Module Structure
- openlp
- core
- api
- display
- common
- projector
- mediaplayers
- ui
- widgets
- plugins
- songs
- bibles
- presentations
- media
- images
- custom
- core
Tips
- Keep
__init__.py
fairly empty. One of the issues we've had in the past is importing a lot of things into__init__.py
in an effort to makeimport
statements shorter, but this has only caused more problems than it is worth, including the terror of coding: circular imports. - Keep
import
statements to a minimum. Only include the imports you absolutely need. - Use as full of a module path as possible in your
import
statements. This ties in with the first tip. Rather import fromopenlp.core.common.registry
(avoiding circular imports) than importingopenlp.core.common.registry
contents intoopenlp.core.common
and then importing fromopenlp.core.common
.
GUI vs Library
Always keep GUI and Library code separate. The easiest way to do this is to make sure that all windows, dialogs, widgets and message boxes originate from another GUI element.
For example, if you encounter an error in your networking code while the user is downloading a Bible, rather let an exception bubble up to the import wizard and let the wizard handle the exception and show an error message.
Multiple Inheritance Uglies
Single inheritance is always simple. You have one parent class, and
calling super()
just calls the parent's __init__()
method. Python
supports multiple inheritance, and this can get quite tricky.
For more information on multiple inheritance and the super()
function
in Python, I highly recommend reading the following article:
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
Base Classes vs Mixins
In the OpenLP codebase, we make use of both base classes and mixins. The
key difference between these two is that a mixin does not have an
__init__()
method. If you add an __init__()
method to a mixin, you
will encounter a number of inheritance-related error messages.
When adding mixins to your classes, make sure that you always add them LAST. For example:
class AlertsManager(QtCore.QObject, RegistryBase, LogMixin, RegistryProperties):
pass
-
QtCore.QObject
This is a PyQt5 class. PyQt5 classes (thankfully) are already configured to handle multiple inheritance correctly. Place PyQt5 classes first where possible. -
RegistryBase
This is a Python base class. This class will need the correct__init__()
method in order to handle multiple inheritance correctly. Python base classes should be placed after PyQt5 classes. -
LogMixin
This is a Python mixin class. Mixin classes should ALWAYS be placed last. -
RegistryProperties
Also a mixin class.
Within OpenLP, the Ui_{name}
classes are also mixins. Make sure they
also go last.
Here's an example of a UI widget that inherits from PyQt5, our own base class, and a bunch of mixins. Take note of the order of the inherited classes.
class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixin, RegistryProperties):
pass