How to develop a PySide application plugin? - qt

As I understand it Qt provides 2 main plugin mechanisms:
Plugins that extend Qt "Qt Extensions"
Plugins that extend applications developed with Qt
I'm interested in developing plugins for my application (2).
I use PySide but can't find any resources about developing application plugins using PySide/PyQt.
Following the C++ Qt documentation I understand that the application has to use the Q_DECLARE_INTERFACE() macro and the plugins have to use both Q_INTERFACES() and Q_EXPORT_PLUGIN2() macros but I don't know the code they represent to try and translate it to python. Or is there another way I've missed?
Update:
The closest thing to a solution I could find so far is Alex Martelli's answer to a similar question. Although it looks like it would work I'd rather use an official Qt approach to avoid any cross-platform issues.

I think Qt's plugin system is intended to allow people to write C++ plugins compiled as binaries. I don't even know if it's theoretically possible to write plugins in Python that will use a C++ binary interface like that.
If you want to support Python plugins your best bet would be to use one of the many pure python plugin systems out there. I have written a PySide app that uses YAPSY to load plugin scripts. YAPSY is a very simple, compact plugin module. It's very easy to include directly in your app as it's a single file, and is BSD licensed so you can use it commercially. Just search for it on Google. I was even able to package my app using py2exe and still retain the ability to import python source file plugins from a plugin directory.

I'm a fan of the Roll Your Own approach.
By a plugin, I mean:
Plugin: a module or package loaded during runtime which augments or modifies the main module's behavior
I see two requirements for a plugin:
Can the main module load the plugin during runtime?
Is data accessible between the main module and the plugin?
Assumptions
Developing a plugin system is highly subjective. There are many design decisions to make and there is no One True Way. The usual constraints are time, effort, and experience. Be aware that assumptions must be made and that the implementation often defines terminology (e.g. "plugin" or "package"). Be kind and document those as best as possible.
This plugin implementation assumes:
A plugin is either a Python file or a directory (i.e. "plugin package")
A plugin package is a directory with structure:
plugin_package/
plugin_package.py <-- entry point
other_module.py <-+
some_subdir/ |- other files
icon.png <-+
Note that a Plugin package isn't necessarily a Python package. Whether or not a plugin package is a Python package depends on how you want to handle imports.
The plugin name is the same as the plugin entry point's file name, as well as the plugin package directory
The QApplication's QMainWindow is the primary source of data to share from the main module
1. Loading a plugin module
Two pieces of information are required to load a plugin module: the path to the entry point and the plugin's name. How these are obtained can vary and getting them usually requires string/path parsing. For what follows, assume:
plugin_path is the absolute path of the plugin entry point and,
plugin_name is the plugin name (which is the module name by the above assumptions).
Python has implemented and re-implemented the import mechanism numerous times. The import method used at the time of writing (using Python 3.8) is:
import importlib
from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import SourceFileLoader
loader = SourceFileLoader(plugin_name, plugin_path)
spec = spec_from_loader(plugin_name, loader)
plugin_module = module_from_spec(spec)
spec.loader.exec_module(plugin_module)
# Required for reloading a module
sys.modules[plugin_name] = plugin_module
# # This is how to reload a module
# importlib.reload(plugin_module)
It's probably a good idea to include error handling and a record of which modules have been loaded (e.g. by storing them in a dict). Those details are excluded here for brevity.
2. Sharing data
There are two directions which data may be shared:
Can the main module access the plugin module's data?
Can the plugin module access the main module's data?
The main module can access plugin data for free after import (by definition). Simply access the plugin module's __dict__:
# Accessing plugin data from the main module
some_data = plugin_module.__dict__.get('data')
Accessing the main module's data from within the plugin is a trickier problem.
As stated above, I usually consider the QMainWindow as synonymous with the end-user's notion of "the application". It's the primary widget users interact with and, as such, typically houses most of the data or has easy access to it. The challenge is sharing the QMainWindow instance.
The solution to sharing QMainWindow instance data is to make it a singleton. This forces any QMainWindow to be the main window users interact with. There are several ways to create singletons in Python. The two most common ways are probably using metaclasses or modules. I've had the most success using the module singleton approach.
Break the QMainWindow code into a separate Python module. At the module level, create but don't initialize a QMainWindow. Create a module-level instance so that other modules can access the instance as a module attribute. Don't initialize it because init requires a QApplication (and because the main window module is not the application entry point).
# main_window.py
from PySide2 import QtWidgets
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
main_window_instance = MyMainWindow.__new__(MyMainWindow)
Use a separate module, such as main.py, for the application entry point. Import the main window instance, create the QApplication, and initialize the main window.
# main.py
import sys
# Importing the instance makes it a module singleton
from main_window import main_window_instance
from PySide2 import QtWidgets
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main_window_instance.__init__()
main_window_instance.show()
sys.exit(app.exec_())
Importing the main window instance is what makes it a singleton. Creating the QApplication here isn't strictly necessary (it's a singleton, too), but feels cleaner to me.
Now, when plugins are loaded during runtime, they can import the main_window_instance. Because the main_window module has already been loaded by the main.py entry point, it's the main window used by the main module (and not a new instance). Data from the main module can now be accessed from the plugin module.
# plugin.py
# The plugin can now access the main module's data
from main_window import main_window_instance
main_window_instance.setWindowTitle('Changed from plugin')
Comments
The minimum setup requires three files: main.py, main_window.py, and plugin.py. The main_window.py defines and instantiates the main window, the main.py makes it a singleton and initializes it, and the plugin.py imports and uses the instance.
Lots of details were left out in hopes that the primary components could be made apparent. Ideally, the Qt and Python documentation should be enough to fill in the gaps...
There are further considerations, such as how to distribute and manage plugins. Plugins could be hosted remotely, packaged as (zip) archives, bundled as proper Python packages, etc. Plugins can be as simple (or as complex) as you want. Hopefully this answer provides ample inspiration for how you want your plugin system to look.

Related

Alfresco data export and import

we have to export alfresco data and import into another repository, and not necessarily backup/restore process. we are aiming for script which can be run and extract data on some conditions, set of files, with all its metedata.
i have got below link, which talks about same, but it is old , and things cant be done like versions of the file, condition based extract.
https://www.ibm.com/developerworks/cn/xml/x-cmis2/index.html
i would like to know any approaches available for extract and import of the alfresco data into other repositories..
There is nothing out-of-the-box that will do this. The replication sub-system is not suitable for frequent replication of more than a small handful of nodes.
So, you will have to write a custom solution or look at third-party solutions that can do this. Simflofy is one example. Another example is the Parashift Alfstream module.
If you would like to develop this yourself, I suggest you do something like:
Write code to export one or more files to the file system. This should be storage that is shared between the source and all target repositories.
Alongside each file, write a "manifest" file that descries the file's metadata, including custom properties and property values. You should use the same format that the Bulk File System Import Tool expects when doing an import.
Add a message to a queue that describes where the exported data is sitting and where it needs to be imported.
In the target repository, write a listener that is subscribe to the queue.
When the listener gets a message it can initiate a Bulk File System Import in the target system. The BFSIT will import the files and set the metadata as described in the manifest file you generated in Step 2.
Optionally, the target system can place another message on the queue acknowledging that the import has been performed, which the source repo can then pick up to complete the task.
Some people have been successful using Apache Camel for this, but it is not strictly necessary.

How do you import config sync files in a Drupal 8 functional test?

I would like to know how to import config sync files in my functional tests for modules I am testing. For instance, I have some custom content types I would like to test against, and there are a number of files in config/sync that pertain to the node modules that defines the custom content type.
class ArticleControllerTest extends BrowserTestBase {
protected static $modules = ['node', 'dist_source'];
}
At the top of my test I define the modules which do succesfully import, but it doesn't include the config sync settings so none of my custom content types are present. How can I import these into my test environment?
At the beginning of testing for Drupal 8, I had the same question. After reading some documents and tutorials, I tried and know several methods:
$this->configImporter() helps import the configurations from sync to active
$this->configImporter() exits in Drupal\Tests\ConfigTestTrait. And the trait has been used in some based test classes, like BrowserTestBase.
However, the method doesn't work for me. Because, I used thunder installation profile. The default content exists after the profile installation was completed. Once the $this->configImporter() starts to import sync configurations, it encounters errors that some entity types fail to be updated, because the entities already exists.
Create a testing profile
(Haven't tried)
If the Drupal site installed by standard profile, you may try to put the sync configurations into a testing profile. And Install Profile Generator module may helps you create the testing profile. There is a related issue #2788777 with config and profile
Put the configurations which depend on module into config/install or config/optional
(Work for me)
Contributed modules always put the config into config/install and config/optional. Once the module is installed, the configurations will also write into database or active storage. Documentation - Include default configuration in your Drupal 8 module
When developing configurations in module, Configuration development helps export the config into config/install of the developed module.
If anyone has the same experience, look forward to share with us.
I do it in my testing base class (extends BrowserTestBase) in setUp() like this:
// import config from sync
$config_path = '/home/rainer/src/asdent/config/sync';
$config_source = new FileStorage($config_path);
\Drupal::service('config.installer')->installOptionalConfig($config_source);
works great.
It's just like a drush cim sync
And provides the production config to my end-2-end automated phpunit tests in gitlab CI.

Django CMS search engine, few questions

It's tough to find good documentation on this. I am trying to build a simple search engine for a very small Django CMS site built with CMS version 2.4. I have found out the best way is with Haystack + django-cms-search, which then tells me that package is deprecated and to use aldryn-search instead, the documentation is lacking big time.
From what I can gather, I need to create a search_indexes.py but where does this go? I thought this was app specific? If I am just using it to index Page model from the CMS, how do I go about integrating that?
For cms versions < 3.0 then django-cms-search would be your solution, once you switch to >= 3.x then aldryn-search is the way to go.
The reason for this is that aldryn-search is basically a fork/refactor of django-cms-search targeting haystack 2.x and cms 3.x
I'm considering adding backwards compatibility for aldryn-search to work on cms 2.4 but to get you up and running, here's what you can do.
Install django-cms-search and add haystack and cms_search to your INSTALLED_APPS setting.
This should allow you to start indexing pages already, if not please post a traceback :).
Once you are able to index pages, now it's up to you if you want to manage the search page through the cms using an apphook or a fixed url.
Usually when working with search, one needs to tweak some things and add search specific templates, for this I highly recommend creating a search app in your project, add a models.py and then add this app to your INSTALLED_APPS.
Now back to the search page, if you choose to use an apphook, then in your search app, create a cms_app.py file and add the following:
from cms_search.cms_app import HaystackSearchApphook
apphook_pool.register(HaystackSearchApphook)
this registers the packaged apphook from django-cms-search with the cms (this used to happen by default, but not anymore).
Otherwise, if you chose to have a fixed url not managed by the cms, you can simply add a urls.py in your search app with the following:
from django.conf.urls import *
from haystack.views import search_view_factory
urlpatterns = patterns('search.views',
url('^$', search_view_factory(), name='haystack-search'),
)
then in your project's urls.py you can include the search.urls under any name you want, like so:
url(r'^search/', include('search.urls')),

Import class from Mainprojekt in subproject

I have an play application with the following structure:
-mainApp
-app
- controllers
- models
- services
- views
-test
-modules
...
And i have a subproject in the modules folder.
Now I want to use the class Test.class in the "services"-folder in a class of a submodule (called "listadmin"). I thought it's possible with the import:
import services.Test;
But this doesn't work. I get the error (if I compile) that the system don't know the package service.
How do i import a class of the main-project in a subproject?
Thanks for help!
I think the only way you can do that is to have the sub-project dependOn the main project. Of course, the main project also will dependOn the subproject, so I'm not sure what you gain by creating a sub-project. I'm having a similar issue. I'm trying to separate my big project into self-contained smaller projects, but I need some info from the big project (the main template file so my views fit in with the rest of the application, a default URL to send pages to when users don't have permission to see pages, etc.) If I can't find that someone has already asked this question, I'm going to, so keep an eye out.

conditional compilation in Flex (Actionscript) and import statements

I have a web application written in Flex and I'm trying to build an AIR application from the same code base using conditional compilation.
In the AIR application, I need to have import statements such as the following:
import flash.data.SQLConnection;
import flash.filesystem.File;
but I cannot have these import statements in the web application because they are AIR only classes.
I thought I could use conditional compilation to overcome this issue but then I read the following on page http://help.adobe.com/en_US/flex/using/WS2db454920e96a9e51e63e3d11c0bf69084-7abd.html:
"You cannot use constant Boolean values to conditionalize metadata or import statements."
If that's the case, how can I have common codebase for Flex based web as well as desktop applications? Has anyone solved this conundrum?
Thanks,
Dilip
More on this question after some trials and errors...
I have 3 projects in Eclipse for this project... one for web application, one for AIR application and one for the common source code. In the web and AIR project, I point to the common source code. In the common code, I used conditional compilation and it looks like you can do something like the following:
CONFIG::desktopMode {
import flash.data.SQLConnection;
import flash.events.SQLEvent;
import flash.events.SQLErrorEvent;
import flash.filesystem.File;
}
and similar approach to include web or AIR specific functions during compilation. The approach seems to have worked so far!
The only place I have run across issues is in my Cairngorm's model locator. If I put CONFIG::desktopMode around import statements in Cairngorm's model locator, it starts giving "Uncaught exception in compiler" or "1131 classes must not be nested" error. I'm not sure how to address this error!
Dilip
You can avoid imports referring fully qualified class names in code. This way you can use conditional compilation.
A common 'codebase' isn't really the situation. Your common codebase is the views and such, but from your Flex to your Air application, your business layer changes. For this, I would recommend you create 2 different projects (one for web, the other for air) and have a library project for all common components, classes, whatever, that can be shared between the two.
It's impossible to have a class like you're saying that says 'if flex, only use this code; if air use this one' since the air SDK adds extra functionality and just saying 'import this' won't work because you also need to remove all references to that import which makes it unreadable.
You need to architect your project properly so that you can separate and abstract out most classes so they can be used for both projects, and then within each project have their specific implementation. Using an application framework like Parsley might help you to accomplish this; I know it helped me.
You can always switch to some full preprocessor like M4 and build your program using normal build tools and not the IDE.

Resources