Deploying to master from @ 2a06eab0a6601add904e7a8e9705cc575d4efda5 🚀

This commit is contained in:
Holt59
2020-09-24 13:38:54 +00:00
parent 140595e1d6
commit 1896293cf4
250 changed files with 73529 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
# Generated files:
.mypy_cache
__pycache__
.vscode
# The 'bin/' directory:
bin
docs/build
docs/mobase.py
docs/source/api
+7
View File
@@ -0,0 +1,7 @@
Copyright 2020 Mikaël Capelle
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+141
View File
@@ -0,0 +1,141 @@
# Mod Organizer 2 - Python stubs generation
This little project can be used to generate python stubs (`.pyi` file) for the MO2 python
interface `mobase`.
## Using the stubs
You can install the `stubs` with `pip`:
```bash
pip install mobase-stubs
```
This will install the latest stubs on PyPi which usually corresponds to the latest release of
MO2.
You can install stubs for a specific version of MO2:
```bash
pip install mobase-stubs==2.3.2.*
```
If you want development stubs, you can install them this way:
```bash
# Clone this repository:
git clone https://github.com/ModOrganizer2/pystubs-generation.git
# Install the stubs:
cd pystubs-generation/stubs/setup
pip install .
```
Some words of warning:
- The stubs are as correct as possible, but some errors are expected.
- If you see a `InterfaceNotImplemented` class anywhere in the stubs, it means that a proper interface is
currently not available.
- Some classes are said (in the stubs) to inherit `QWidget` or `QObject`. This is true on the C++ side but NOT
on the python side. The inheritance is only added to help with auto-completion since these classes also
override `__getattr__` to dispatch to the underlying `QWidget` or `QObject`. Some things might not work as
expected with these class (e.g., `isintance(myObject, QObject)` will return `False`), which is why a
`_object()` and `_widget()` method is also provided.
## Generating the stubs
The stubs are generated using python by parsing the `mobase` module.
You need the version of python that matches your current MO2 installation: e.g., if you have a `python38.dll` in
your MO2 installation path, then you need **python 3.8**.
To generate the stubs, you can run:
```
# Change the output folder to whatever you want:
python main.py -c configs\config-2.3.yml -o stubs\setup\mobase-stubs\__init__.pyi ${MO2_INSTALL_PATH}
```
Where `${MO2_INSTALL_PATH}` is the path to your MO2 installation (the one containing `ModOrganizer.exe`).
The latest stubs are kept under `stubs/setup/mobase-stubs/__init__.pyi`, and when a new version is released,
the stubs are backed-up under `stubs/x.y.z/mobase.pyi`.
If you do not specify a `-o` option, output will go to `stdout`, so you should redirect.
Warning and "critical" messages are printed to `stderr`.
A few options are available for `main.py`:
```
usage: Stubs generator for the MO2 python interface [-h] [-o OUTPUT] [-v] [-c CONFIG] INSTALL_DIR
positional arguments:
INSTALL_DIR installation directory of Mod Organizer 2
optional arguments:
-h, --help show this help message and exit
-o OUTPUT, --output OUTPUT
output file (output to stdout if not specified)
-v, --verbose verbose mode (all logs go to stderr)
-c CONFIG, --config CONFIG
configuration file
```
The stubs generator will try hard to find a valid stubs for all classes and methods of `mobase`.
A lot of information is available through the `-v` options. Without it, only conversions or fixes
considered "strange" will be outputed.
For instance, here is the output with the current `config-2.3.yml` file:
```
WARNING: Replacing IOrganizer::FileInfo with FileInfo.
WARNING: Replacing IOrganizer::FileInfo with FileInfo.
WARNING: Replacing IPluginInstaller::EInstallResult with InstallResult.
WARNING: Replacing IPluginInstaller::EInstallResult with InstallResult.
```
As you can see, only a few types were manually fixed (specified in `config-2.3.yml`).
## Configuration file
The configuration file contains information for the stubs that cannot be deduced by `main` (or are too
complex to deduce), and the documentation for everything.
## Uploading the stubs to pypi
The upload of the stubs to https://pypi.org/project/mobase-stubs/ has to be done manually. Here
are the steps:
1. Check the version of the stubs:
- The version is specified in the configuration file. If you need to modify the stubs of the
current version, you need to add a `.postX` after the version since PyPi does not allow
re-upload of the same release.
2. Generate the stubs using the procedure above. You should generate the stubs under `stubs/setup/mobase-stubs/__init__.pyi`.
3. If necessary, update the dependencies in `setup.py` (`PyQt5-stubs` version and python version).
4. Go to `stubs/setup` and run:
```bash
python setup.py sdist bdist_wheel
twine upload dist/*
```
You need to set the environment variables `TWINE_USERNAME` and `TWINE_PASSWORD` to appropriate values
before running `twine upload`.
## Extras — Starts a python interpreter with `mobase`
It is possible to start a (i)python interpret with `mobase` imported by running:
```
python -im generator.loader ${MO2_INSTALL_PATH}
```
This has no real usage except for MO2 developers since most classes from the `mobase` module cannot be instantiated.
# License
The MIT License (MIT)
Copyright (c) 2020, Holt59.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
See [LICENSE](LICENSE).
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+35
View File
@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd
+4
View File
@@ -0,0 +1,4 @@
sphinx-rtd-theme
sphinx-autodoc-typehints
sphinx-automodapi
PyQt5
+70
View File
@@ -0,0 +1,70 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath("../../stubs/2.3rc1/"))
# -- Project information -----------------------------------------------------
project = "MO2 Python Plugin API"
copyright = "2020, Holt59"
author = "Holt59"
# The full version, including alpha/beta/rc tags
release = "2.3rc1"
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx_autodoc_typehints",
"sphinx.ext.napoleon",
# "sphinx.ext.autosummary",
"sphinx_automodapi.automodapi",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"
html_theme_options = {
# Toc options
"collapse_navigation": False,
"titles_only": False,
}
html_sidebars = {
"**": ["globaltoc.html", "relations.html", "sourcelink.html", "searchbox.html"]
}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
+53
View File
@@ -0,0 +1,53 @@
FAQ
===
.. toctree::
:maxdepth: 4
1. Why is MO2 throwing an exception when I try to create a type inheriting one of MO2 class?
............................................................................................
This often happens if you forget to call ``super().__init__()`` with the right arguments.
Even if the list of arguments is empty (as in the example), it must be called due to a "bug"
in ``boost::python``:
.. code-block:: python
class MySaveGame(mobase.ISaveGame):
def __init__(self):
super().__init__() # Mandatory!
2. How can I be sure to implement all the required methods when creating a plugin?
..................................................................................
It is kind of annoying to create a MO2 python plugin that kind of works and have it crash
at some point simply because there plugin is missing a function implementation.
You can have ``mypy`` warn you for such issue by typing the ``createPlugin`` or ``createPlugins``
functions:
.. code-block:: python
import typing
# A plugin that is missing something (e.g. display()).
class MyPlugin(mobase.IPluginTool): ...
# If you type-hint `createPlugin` by adding `-> mobase.IPlugin`,
# `mypy` will warn you that `MyPlugin` is an incomplete class.
def createPlugin() -> mobase.IPlugin:
return MyPlugin()
# You can also type-hint `createPlugins`:
def createPlugins() -> typing.List[mobase.IPlugin]:
return [MyPlugin()]
3. Why are my ``isinstance(x, QObject)`` and ``isinstance(y, QWidget)`` not working?
....................................................................................
Some classes from ``mobase`` are said to inherit ``QObject`` and ``QWidget`` in the
stubs, such as :class:`mobase.IDownloadManager`.
This is not the case in practice due to limitation in Python and ``boost::python``.
The bases are in the stubs to allow for auto-completion and typing, which is then
simulated in the real ``mobase`` by overriding ``__getattr__`` and delegating to
the underlying ``QObject`` or ``QWidget``.
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+30
View File
@@ -0,0 +1,30 @@
.. MO2 Python Plugin API documentation master file, created by
sphinx-quickstart on Wed Jul 15 19:36:59 2020.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to MO2 Python Plugin API's documentation!
=================================================
Plugins are a way to extend Mod Organizers functionality. You can write plugins in either C++ or Python,
theoretically with either being as powerful as the other.
This documentation is dedicated to writting MO2 **Python** plugins.
.. toctree::
:maxdepth: 2
:caption: Contents:
setup-tools
plugin-types
writing-plugins
faq
mobase
.. Indices and tables
.. ==================
.. * :ref:`genindex`
.. * :ref:`modindex`
.. * :ref:`search`
+11
View File
@@ -0,0 +1,11 @@
``mobase`` API
==============
.. currentmodule:: mobase
.. automodapi:: mobase
:no-inheritance-diagram:
:no-heading:
.. toctree::
:maxdepth: 4
+7
View File
@@ -0,0 +1,7 @@
mobase
======
.. toctree::
:maxdepth: 4
mobase
+150
View File
@@ -0,0 +1,150 @@
.. _type-of-plugins:
Type of Plugins
===============
Plugins are passive, that is: they react to events emitted by the core application or extend
an existing functionality (like adding support for additional types of installers). Plugins
should integrate with Mod Organizers concepts. While you could write an INI editor that works with
the global INI files instead of the profile specific ones, what would be the point?
Depending on where/how the plugin integrates with MO you need to write a different type of plugin,
in practice this means you need to implement a different interface (inherit from a different base
plugin class from ``mobase``). As mentioned above, plugins are passive: the plugin type decides how/when
MO makes requests to/invokes your plugin.
All plugins however gain access to MOs own plugin interface so all plugins get to make the same requests
to MO.
Installers
----------
| **Interface:** :class:`IPluginInstaller<mobase.IPluginInstaller>`, :class:`IPluginInstallerSimple<mobase.IPluginInstallerSimple>`,
:class:`IPluginInstallerCustom<mobase.IPluginInstallerCustom>`
| **Examples:** ``installer_bain``, ``installer_bundle``, ``installer_fomod``, ``installer_ncc``, ``installer_quick``, ``installer_manual``.
An installer is invoked when the user tries to install a mod, either by double clicking in the download
view or through the "Install Mod.." button or "Reinstall mod" item from the mod lists context menu.
There are actually two ways to write an installer, *simple* or *complex*:
- With *simple* installers, MO does the unpacking of the file but this works only with standard archive formats.
The plugin can then select the files and folders that requires extraction, and where to extract them.
- *Complex* installers are more flexible but require a bit more work.
Previewers
----------
| **Interfaces:** :class:`IPluginPreview<mobase.IPluginPreview>`
| **Examples:** ``preview_base``
These plugins add support for previewing files in the data pane.
Right now all image formats supported by Qt are implemented (including `.dds`) but no audio files and
no 3d mesh formats.
Mod Page
--------
*WIP*
| **Interfaces:** :class:`IPluginModPage<mobase.IPluginModPage>`
| **Examples:** ``page_tesalliance``
Mod Page plugins implement interfaces to modding communities where mods can be downloaded, checked
for updates and so on.
This interface is not finished and some of the bits that are do not actually get used. The goal is
that the whole Nexus integration can be implemented through this interface and can then be removed
from the core application. This is a task for the distant future, unless someone wants to volunteer.
Game
----
| **Interfaces:** :class:`IPluginGame<mobase.IPluginGame>`
| **Examples:** ``game_oblivion``, ``game_fallout3``, ``game_falloutnv``, ``game_skyrim``, ...
These plugins (shall eventually) implement all the game specific features and further game plugins are
able to add support for further games.
The plugin is also responsible to help MO determine if (and where) the game is installed in the first place.
Since supporting a game properly requires extensions in many places of the UI.
To allow this without creating one huge plugin interface that involves every aspect of MO, game plugins
expose a *feature list*.
The list of possible features can be found in the "game_features" project and each feature can itself be
considered a plugin interface.
As an example for a game feature take BSA invalidation: If the game requires BSA invalidation it will implement
this feature.
Wherever the core can support BSA invalidation it will query whether the current game has this feature and if so
query the implementation on specifics (like "How should the invalidation BSA be called" and "what's the right bsa version").
Of course, the goal is for feature interfaces to be as generic as possible without limiting usefulness.
**Note:** The :class:`IPluginGame<mobase.IPluginGame>` interface is complex, and mostly designed for Gamebryo games. If you plan
on writting a game plugin for another type of games, you might be interested by a simpler interface and might
want to check out the `Basic Games <https://github.com/ModOrganizer2/modorganizer-basic_games>`_ meta-plugin.
Tool
----
| **Interfaces:** :class:`IPluginTool<mobase.IPluginTool>`
| **Examples:** ``tool_configurator``, ``tool_inieditor``, ``fnistool``
This is the simplest of plugin interfaces. Such plugins simply place an icon inside the tools submenu and get
invoked when the user clicks it. They are expected to have a user interface of some sort.
These are almost like independent applications except they can access all Mod Organizer interfaces like querying
and modifying the current profile, mod list, load order, use MO to install mods and so on.
A tool plugin can (and should!) integrate its UI as a window inside MO and thus doesn't have to initialize a
windows application itself.
Proxies
-------
| **Interfaces:** :class:`IPluginProxy<mobase.IPluginProxy>`
| **Examples:** ``plugin_python``
Proxy Plugins expose the plugin api to foreign languages. This is what allows you to write plugins using python
in the first place.
The python proxy is easily the most complicated plugin and requires constant updating so if you're considering
writing a Haskell plugin because that is your programming language of choice, I am fairly certain learning python
is easier than writing the haskell proxy. Just saying.
And no, you can not write a proxy for a third language in Python, do not be silly.
*Free Plugins*
--------------
| **Interfaces:** :class:`IPlugin<mobase.IPlugin>`
| **Examples:** ``check_fnis``, ``bsa_extractor``, ``diagnose_basic``, ``tool_inibakery``
"Free" plugins implement none of the interfaces and thus initially do not integrate with MO at all.
They are initialized by MO and get access to the MO interface.
This makes sense if you only want to implement one of the extension interfaces (see below) or register handlers
for events.
Extension Interfaces
--------------------
In Python, these interfaces are similar to other plugin types, but in C++, those do not inherit ``IPlugin`` so
that plugins can implement one or more of these interfaces, in addition to a normal plugin type.
Diagnose
........
| **Interfaces:** :class:`IPluginDiagnose<mobase.IPluginDiagnose>`
| **Examples:** ``diagnose_basic``, ``installer_ncc``, ``plugin_python``, ``script_extender_plugin_checker``
This interface lets the plugin report issues that are then listed in the "Problems" icon in the main window.
If possible the plugin can also provide an automatic or guided fix to the problem.
The ``diagnose_basic`` plugin does nothing but analyze the MO installation and report problems it discovers (like
*"There are files in your overwrite directory."*) but usually a plugin will want to report issues relevant for its
own operation.
For instance, ``installer_ncc`` requires a specific version of .NET and will report a problem if it is not installed.
This should always be the prefered way to communicate problems the user has to fix but should never be used for problems
he cannot fix (i.e. *"This plugin does not work with this game."*).
An empty problem list should always be achievable.
File Mappings
.............
| **Interfaces:** :class:`IPluginFileMapper<mobase.IPluginFileMapper>`
| **Examples:** ``tool_inibakery``, ``game_gamebryo``
This interface allows plugins to add virtual file (or directory) links to the virtual file system in addition to the
mod files.
Profile-local save games, ini-files and load-orders are all implemented this way in MO2.
+135
View File
@@ -0,0 +1,135 @@
Setting up the environment
==========================
This page will get you started for creating a MO2 python plugin. If you have troubles getting
everything setup, feel free to come by our discord server at `https://discord.gg/cYwdcxj <https://discord.gg/cYwdcxj>`_.
Required tools
--------------
It is possible to write a MO2 python plugin using any text editor, but this guide will focus on
`Visual Studio Code <https://code.visualstudio.com/>`_.
This guide assumes that:
- You have `Visual Studio Code <https://code.visualstudio.com/>`_ installed with the
`Python extension <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_.
- You have Python installed: https://www.python.org/downloads/.
- It is recommended but not mandatory to use the Python version that is used by MO2.
You can check the ``pythonXX.dll`` in the MO2 installation folder to find the Python version used by MO2 (``python38.dll`` means Python 3.8).
- You obviously need a valid MO2 installation: https://github.com/modorganizer2/modorganizer/releases
- This guide is written for MO2 ≥ 2.3.0.
**Note:** In the following, I will refer to the MO2 installation directory as ``$MO2DIR``. So if you
installed MO2 at ``C:\MO2`` and you are asked to copy a file to ``$MO2DIR/plugins``, it refers to ``C:\MO2\plugins``.
Preparation
-----------
**Note:** This part is optional but highly recommended if you want a proper environment to work with.
Everything here is written to be as simple as possible but you can of course adapt it to your preferences: use a python virtual
environment, use workspace settings instead of global ones, etc.
1. Get the ``mobase`` stubs
...........................
``mobase`` is the MO2 Python module. The module is written in C++ and thus cannot be read directly by tools such
as ``flake8`` or ``mypy``.
Instead, we provide `stubs <https://stackoverflow.com/questions/59051631/what-is-the-use-of-stub-files-pyi-in-python>`_
which can be used for auto-completion or type-checking.
The stubs for ``mobase`` are available at https://github.com/ModOrganizer2/pystubs-generation/tree/master/stubs.
You want to download the ``mobase.pyi`` file in the folder corresponding to your MO2 version and put it under ``$MO2DIR/plugins/data``.
**Note:** It is possible to put the stubs in a different location, but we are going to use ``$MO2DIR/plugins/data`` for PyQt5,
so we might as well use it for the stubs.
2. Configure Visual Studio Code for ``mobase``
..............................................
We are going to configure Visual Studio Code to have auto-completion and linting (error and type checking)
for the MO2 Python module.
Open ``settings.json`` (Ctrl+Shift+P, then "Open Settings (JSON)"), and add the following entries:
.. code-block:: json-object
"python.linting.enabled": true,
"python.linting.mypyEnabled": true,
"python.linting.flake8Enabled": true,
"python.autoComplete.extraPaths": [
"$MO2DIR\\plugins\\data",
]
3. Configure ``mypy`` to find the ``mobase`` stubs
..................................................
There are multiply way to configure ``mypy``:
1. You can create a ``mypy.ini`` file somewhere containing:
.. code-block:: ini
[mypy]
mypy_path = $MO2DIR\plugins\data
And then add the following to ``settings.json`` (with the correct path):
.. code-block:: json-object
"python.linting.mypyArgs": [
"--config-file=path-to-mypy.ini",
]
2. You can set the ``MYPYPATH`` environment variable to ``$MO2DIR\plugins\data`` (this requires
restarting VS code).
Testing the setup
-----------------
Create a new Python file in Visual Studio Code, and paste the following content (if you create
the file in ``$MO2DIR\plugins``, do not forget to delete it after since it is not a valid
plugin):
.. code-block:: python
import mobase
class MyPlugin(mobase.IPluginTool):
def __init__(self):
super().__init__()
def init(self, organizer: mobase.IOrganizer) -> bool:
organizer.dow # (1)
return True
def name(self) -> int:
return 0
def createPlugin() -> mobase.IPlugin:
return MyPlugin()
If your setup is valid, here is what you should have.
- You should see the following (with the errors underlined):
.. image:: images/check-setup-1.png
- If you press ``Ctrl+Space`` after ``.down`` (1), you should see the following
meaning that auto-complete is working:
.. image:: images/check-setup-2.png
- In the error window (click |error-window| on the status bar), you should see
the 3 following errors:
.. image:: images/check-setup-3.png
If everything is as above, you can delete the test file and move on to writting
your own plugin!
.. |error-window| image:: images/error-window.png
+201
View File
@@ -0,0 +1,201 @@
Writing Plugins
===============
Getting started
---------------
Now that you know which type of plugin you need, you can start writing your own plugin.
There is two way to write a Python plugin:
- :ref:`single-file-plugin`: You use a single ``.py`` file that you put directly in the ``plugins``
folder of the MO2 installation.
- :ref:`module-plugin`: You create a Python package (folder) with a ``__init__.py`` file that you
put in the ``plugins`` folder of the MO2 installation.
Most examples of plugins will be module plugins.
.. _single-file-plugin:
Single file plugins
...................
Prior to version 2.3, this was the only way of creating a Python plugin. You simply need
to create a ``myplugin.py`` file in the ``plugins`` folder of Mod Organizer 2 with a content
similar to:
.. code-block:: python
import mobase
class MyPlugin(...):
...
def createPlugin() -> mobase.IPlugin:
return MyPlugin()
We will see later on how to create the actual ``MyPlugin`` class.
The ``createPlugin`` function is the function that is called by Mod Organizer 2 to instantiate
the plugin.
You can also provide multiple plugins by using ``createPlugins`` instead of ``createPlugin``:
.. code-block:: python
from typing import List
import mobase
class MyPlugin1(...):
...
class MyPlugin2(...):
...
def createPlugins() -> List[mobase.IPlugin]:
return [MyPlugin1(), MyPlugin2()]
**Note:** If you provide neither ``createPlugin()`` nor ``createPlugins``, MO2 will display
an error message in the logs.
**Note:** If you add a return type-hint to ``createPlugin()`` or ``createPlugins`` (``->``), ``mypy``
will type-check the function and warn you if one of your plugins is invalid, e.g. if you
forgot to implement a required method.
If you need to provide other files with your ``.py`` (assets or other Python files), you can
put them in the ``plugins/data``, but this is deprecated since MO2 2.3, and you should instead
create a Python module plugin.
.. _module-plugin:
Module Plugins
..............
Module plugins were introduced in MO2 2.3 and are shipped as whole folder containg a python module.
The minimum content of the folder is a ``__init__.py`` file with ``createPlugin`` or ``createPlugins``
function.
A minimal module plugin could be as follows:
.. code-block:: bash
plugins/ # MO2 plugins folder
myplugin/
__init__.py
plugin.py
In ``plugin.py``, you could define your plugin:
.. code-block:: python
# plugin.py
import mobase
class MyPlugin(...):
...
And in ``__init__.py``, you should write ``createPlugin``:
.. code-block:: python
# __init__.py
import mobase # For type-checking createPlugin().
from .plugin import MyPlugin # Always use relative import:
def createPlugin() -> mobase.IPlugin:
return MyPlugin()
Similar to single-file plugins, you can expose ``createPlugins`` instead of ``createPlugin``
to instantiate multiple plugins.
**Note:** The name of the folder does not have to be a valid python package, and you should
always use relative imports within the module (``import .xxx``) instead of absolute ones.
Writing the plugin
------------------
``IPlugin`` interface
.....................
In the code snippets above, the ``MyPlugin`` class was not implemented.
Depending on the :ref:`type of plugins<type-of-plugins>` that you want to create, you will
need to extend a different class.
.. code-block:: python
class MyTool(mobase.IPluginTool): # Create a Tool plugin
...
class MyPreview(mobase.IPluginPreview): # Create a preview plugin
...
Each plugin class has its own abstract methods that you need to implement but all the classes
also extend ``IPlugin``, so you need to implement the methods from :class:`IPlugin<mobase.IPlugin>`:
.. code-block:: python
from typing import List
import mobase
class MyPlugin(...): # The base class depends on the actual type of plugin
_organizer: mobase.IOrganizer
def __init__(self):
super().__init__() # You need to call this manually.
def init(self, organizer: mobase.IOrganizer):
self._organizer = organizer
return True
def name(self) -> str:
return ""
def author(self) -> str:
return "Tannin"
def description(self) -> str:
return self._tr("Gives a friendly greeting")
def version(self) -> mobase.VersionInfo:
return mobase.VersionInfo(1, 0, 0, mobase.ReleaseType.FINAL)
def isActive(self) -> bool:
return self._organizer.pluginSetting(self.name(), "enabled")
def settings(self) -> List[mobase.PluginSetting]:
return [
mobase.PluginSetting("enabled", "enable this plugin", True)
]
Most of these are pretty simple to understand:
- ``name``: Returns the name of the plugin. The name of the plugin is used to
fetch settings, and in many places, so this should not change between versions.
- ``author``: Returns the name of the plugin author (you!).
- ``description``: Returns the description of the plugin.
- ``version``: Returns the version of the plugin. See :class:`VersionInfo<mobase.VersionInfo>` for
more details.
- ``isActive``: Returns ``True`` if the plugin is active, ``False`` otherwise. This
usually returns ``True``, unless you want to check for something to dynamically
enable the plugin. You can also use a plugin setting to allow users to disable
your plugins.
- ``settings``: Returns the list of settings (that user can modify) for this plugin.
Settings can be ``int```, ``bool``, ``str`` or list of ``str``. Here we indicate
that we have a "enabled" setting that user could use to disable the plugin (and
we use it in ``isActive``).
The ``__init__`` method is the normal Python constructor for our plugin, called when doing
``MyPlugin()``.
You should always call ``super().__init__()`` explicitly when extending MO2 classes (due to
a "bug" in ``boost.python``).
The ``init`` method is called by MO2 to initialize the plugin. The given argument, ``organizer``,
is an instance of :class:`IOrganizer<mobase.IOrganizer>` which is the class used to interface with MO2.
Here, we use it in the ``isActive()`` method to retrieve the "enabled" setting for our plugin.
See :class:`IOrganizer<mobase.IOrganizer>` for more details.

Some files were not shown because too many files have changed in this diff Show More