mirror of
https://github.com/ModOrganizer2/pystubs-generation.git
synced 2026-07-27 14:07:13 -07:00
Compare commits
22
Commits
v2.4.0.alpha3
...
v2.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5b374f3f3 | ||
|
|
14fa117b2d | ||
|
|
6cab2a555b | ||
|
|
b555debad7 | ||
|
|
4f9e212b93 | ||
|
|
1388c1113a | ||
|
|
15dae1d2bc | ||
|
|
0aa1e2d24f | ||
|
|
df29edf5a5 | ||
|
|
15d43de37e | ||
|
|
7b9c6ab32c | ||
|
|
318a4acfb1 | ||
|
|
820bc3e0fe | ||
|
|
c980dd024b | ||
|
|
c7d8442398 | ||
|
|
ae09efbab3 | ||
|
|
a203ee8215 | ||
|
|
9fa09d1b88 | ||
|
|
4d40644aa5 | ||
|
|
fa0facabe8 | ||
|
|
02be57db1d | ||
|
|
233c8f51e8 |
@@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: Build Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -11,22 +11,24 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Standard drop-in approach that should work for most people.
|
||||
- uses: ammaraskar/sphinx-action@master
|
||||
env:
|
||||
PYTHONPATH: .
|
||||
with:
|
||||
pre-build-command: "apt-get update -y && apt-get install -y libgl1-mesa-glx && cp stubs/2.3.0/mobase.pyi docs/mobase.py"
|
||||
pre-build-command: "apt-get update -y && apt-get install -y libgl1-mesa-glx && cp stubs/2.4.0/mobase.pyi docs/mobase.py"
|
||||
docs-folder: "docs/"
|
||||
|
||||
- name: Install SSH Client 🔑
|
||||
uses: webfactory/ssh-agent@v0.2.0
|
||||
uses: webfactory/ssh-agent@v0.4.1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@3.6.1
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
SSH: true
|
||||
REPOSITORY_NAME: ModOrganizer2/python-plugins-doc
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Check Linting
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tox
|
||||
- name: Test with tox
|
||||
run: tox -e py38-lint
|
||||
@@ -1,4 +1,4 @@
|
||||
name: CI
|
||||
name: Check Documentation
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
@@ -14,5 +14,5 @@ jobs:
|
||||
env:
|
||||
PYTHONPATH: .
|
||||
with:
|
||||
pre-build-command: "apt-get update -y && apt-get install -y libgl1-mesa-glx && cp stubs/2.3.0/mobase.pyi docs/mobase.py"
|
||||
docs-folder: "docs/"
|
||||
pre-build-command: "apt-get update -y && apt-get install -y libgl1-mesa-glx && cp stubs/2.4.0/mobase.pyi docs/mobase.py"
|
||||
docs-folder: "docs/"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Mod Organizer 2 - Python stubs generation
|
||||
|
||||
This little project can be used to generate python stubs (`.pyi` file) for the MO2 python
|
||||
This project can be used to generate python stubs (`.pyi` file) for the MO2 python
|
||||
interface `mobase`.
|
||||
|
||||
## Using the stubs
|
||||
@@ -31,35 +31,35 @@ 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.
|
||||
- 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., `isinstance(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**.
|
||||
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}
|
||||
python main.py -c configs\config-2.4.yml ${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`.
|
||||
The stubs are generated under `stubs/setup/mobase-stubs/__init__.pyi`, you
|
||||
can change the output file by using the `-o` option
|
||||
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`.
|
||||
|
||||
A few options are available for `main.py`:
|
||||
|
||||
@@ -72,16 +72,18 @@ positional arguments:
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-o OUTPUT, --output OUTPUT
|
||||
output file (output to stdout if not specified)
|
||||
output file (default stubs/setup/mobase-stubs/__init__.pyi)
|
||||
-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:
|
||||
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 shown.
|
||||
For instance, here is the output with the current `config-2.4.yml` file:
|
||||
|
||||
```
|
||||
WARNING: Replacing IOrganizer::FileInfo with FileInfo.
|
||||
@@ -90,33 +92,18 @@ 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`).
|
||||
As you can see, only a few types were manually fixed (specified in
|
||||
`config-2.4.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.
|
||||
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`.
|
||||
The upload of the stubs to https://pypi.org/project/mobase-stubs/ should be
|
||||
done automatically when a new Github release is made.
|
||||
|
||||
## Extras — Starts a python interpreter with `mobase`
|
||||
|
||||
@@ -138,4 +125,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
See [LICENSE](LICENSE).
|
||||
See [LICENSE](LICENSE).
|
||||
|
||||
+198
-76
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -13,7 +13,7 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../../stubs/2.3rc1/"))
|
||||
sys.path.insert(0, os.path.abspath("../../stubs/2.4.0/"))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
@@ -84,6 +84,64 @@ And then add the following to ``settings.json`` (with the correct path):
|
||||
2. You can set the ``MYPYPATH`` environment variable to ``$MO2DIR\plugins\data`` (this requires
|
||||
restarting VS code).
|
||||
|
||||
4. [Optional] Automatically reload plugins during development
|
||||
.............................................................
|
||||
|
||||
This section is optional and requires you to already have written a "working"
|
||||
plugin (a plugin that MO2 can load).
|
||||
|
||||
Since MO2 2.4 alpha 6, a new command has been added to ``ModOrganizer.exe`` to
|
||||
reload plugins during execution.
|
||||
If your plugin is named "My Plugin", you can use the following command to reload
|
||||
it while MO2 is running:
|
||||
|
||||
.. code::
|
||||
|
||||
$MO2DIR\ModOrganizer.exe reload-plugin "My Plugin"
|
||||
|
||||
If you are using Visual Studio Code, you can send this command to MO2 automatically
|
||||
after saving files from your project.
|
||||
|
||||
1. Create a "reload plugin" task in Visual Studio Code (Ctrl+Shift+P then
|
||||
``Tasks: Configure task`` or open ``.vscode/tasks.json``) using the following
|
||||
snippet (replace the name and directory as needed):
|
||||
|
||||
.. code:: javascript
|
||||
|
||||
// .vscode/tasks.json
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "reload plugin",
|
||||
"type": "shell",
|
||||
"command": "$MO2DIR/ModOrganizer.exe",
|
||||
"args": [
|
||||
"reload-plugin", "My Plugin"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
2. Install the `Trigger Task on Save <https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.triggertaskonsave>`_
|
||||
extension from Visual Studio Code marketplace.
|
||||
|
||||
3. Add the following to your Visual Studio Code settings (``.vscode/settings.json``)
|
||||
|
||||
.. code:: javascript
|
||||
|
||||
// .vscode/settings.json
|
||||
{
|
||||
"triggerTaskOnSave.on": true,
|
||||
"triggerTaskOnSave.tasks": {
|
||||
"reload plugin": [
|
||||
"*.py"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Testing the setup
|
||||
-----------------
|
||||
|
||||
@@ -128,8 +186,8 @@ If your setup is valid, here is what you should have.
|
||||
|
||||
.. image:: images/check-setup-3.png
|
||||
|
||||
If everything is as above, you can delete the test file and move on to writting
|
||||
If everything is as above, you can delete the test file and move on to writing
|
||||
your own plugin!
|
||||
|
||||
|
||||
.. |error-window| image:: images/error-window.png
|
||||
.. |error-window| image:: images/error-window.png
|
||||
|
||||
@@ -226,6 +226,8 @@ These plugins are not as well documented as the ones in the repository above.
|
||||
plugins and should mostly be investigated if you want to add a game to it.
|
||||
- `FNIS Tool <https://github.com/ModOrganizer2/modorganizer-fnistool>`_ [``IPluginTool``]:
|
||||
Plugin to integrate FNIS into MO2.
|
||||
- `Installer Wizard <https://github.com/ModOrganizer2/modorganizer-installer_wizard>`_ [``IPluginInstaller``]:
|
||||
Installer for BAIN archives containing wizard scripts.
|
||||
- `Preview DDS <https://github.com/ModOrganizer2/modorganizer-preview_dds>`_ [``IPluginPreview``]:
|
||||
Plugin to preview DDS files. Quite complex due to the use
|
||||
of OpenGL for display.
|
||||
@@ -243,7 +245,7 @@ These plugins are not as well documented as the ones in the repository above.
|
||||
Unofficial Plugins
|
||||
..................
|
||||
|
||||
These plugins have been created by developpers for MO2 and are usually distributed on Nexus.
|
||||
These plugins have been created by developers for MO2 and are usually distributed on Nexus.
|
||||
|
||||
- `Merge Plugins Hide <https://github.com/deorder/mo2-plugins>`_ [``IPluginTool``]:
|
||||
Hide / unhide plugins that were merged using ``Merge Plugins`` or ``zMerge``.
|
||||
@@ -254,4 +256,67 @@ These plugins have been created by developpers for MO2 and are usually distribut
|
||||
- `Sync Mod Order <https://github.com/deorder/mo2-plugins>`_ [``IPluginTool``]:
|
||||
Synchronize mod order from current profile to another while keeping the (enabled/disabled) state intact.
|
||||
|
||||
*Feel free to open an issue or a pull-request if you want to add your own plugin to the list.*
|
||||
*Feel free to open an issue or a pull-request if you want to add your own plugin to the list.*
|
||||
|
||||
Internationalization
|
||||
--------------------
|
||||
|
||||
If you plan to distribute your plugin, it is often a good idea to provide translations for it.
|
||||
|
||||
Adding translation code
|
||||
.......................
|
||||
|
||||
Mod Organizer uses Qt translation system, so you need to adapt your plugin code to provide
|
||||
translation strings.
|
||||
To do this, you need two things:
|
||||
|
||||
1. In every class containing strings you need to translate, you must add a ``__tr`` function
|
||||
that takes a ``str`` input and call `QApplication.translate` on it (see example below).
|
||||
2. You need to wrap all translatable strings in a call to ``self.__str("My String")`` (see
|
||||
example below).
|
||||
|
||||
.. code:: python
|
||||
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
class MyPlugin(...):
|
||||
|
||||
def localizedName(self) -> str:
|
||||
# Use self.__tr to wrap string you want translatable.
|
||||
return self.__tr("My Plugin Name")
|
||||
|
||||
def __tr(self, txt: str) -> str:
|
||||
# The first argument must EXACTLY match the class name:
|
||||
return QApplication.translate("MyPlugin", txt)
|
||||
|
||||
Generating Qt translation files
|
||||
...............................
|
||||
|
||||
Once your code is updated, you need to generate the Qt translation file ``.ts``.
|
||||
You can use ``PyQt5.lupdate_main`` for this:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
PyQt5.lupdate_main mysourcefile.py -ts mysourcefile.ts
|
||||
|
||||
You should generate a single translation file for your whole plugin even if it contains
|
||||
multiple files by passing all Python file and Qt UI (``.ui``) file to the command above.
|
||||
|
||||
Translating
|
||||
...........
|
||||
|
||||
Now that you have the original ``.ts`` file, you need to translate it in order to obtain
|
||||
translation files for other languages.
|
||||
To do so, you can use online services such as `Transifex <https://www.transifex.com>`_ or
|
||||
simply Qt Linguistic tools.
|
||||
|
||||
Distributing translations
|
||||
.........................
|
||||
|
||||
Once you have obtained translation files for another language, e.g. French, you need to
|
||||
compile it into a ``.qm`` file and then ship it.
|
||||
|
||||
- If you are using a single Python file plugin ``myplugin.py``, the name of the compiled
|
||||
translation must be ``myplugin_fr.qm``.
|
||||
- If you are shipping a module ``mymoduleplugin``, the name of the compiled translation
|
||||
must be ``mymoduleplugin``.
|
||||
|
||||
+32
-12
@@ -1,22 +1,44 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load_module(name: str, path: Path):
|
||||
# Create the loader:
|
||||
loader = importlib.machinery.ExtensionFileLoader( # type: ignore
|
||||
name, path.as_posix()
|
||||
)
|
||||
|
||||
# Extract the spec:
|
||||
spec = importlib.util.spec_from_loader(name, loader)
|
||||
|
||||
# Create the module and execute it?
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
if module is None:
|
||||
raise ImportError(f"Failed to import module {name} from {path}.")
|
||||
|
||||
loader.exec_module(module)
|
||||
|
||||
return module
|
||||
|
||||
|
||||
def load_mobase(path: Path, moprivate: bool = False):
|
||||
""" Load the mobase from the given MO2 installation path and
|
||||
"""
|
||||
Load the mobase from the given MO2 installation path and
|
||||
returns it.
|
||||
|
||||
Args:
|
||||
path: Path to the MO2 installation (folder containg the ModOrganizer.exe).
|
||||
path: Path to the MO2 installation (folder containing the ModOrganizer.exe).
|
||||
moprivate: If True, the moprivate module will also be loaded and returned
|
||||
alongisde mobase.
|
||||
alongside mobase.
|
||||
|
||||
Returns: The mobase module. """
|
||||
Returns: The mobase module.
|
||||
"""
|
||||
|
||||
# We need absolute path for loading DLL and modules:
|
||||
path = path.resolve()
|
||||
@@ -29,22 +51,20 @@ def load_mobase(path: Path, moprivate: bool = False):
|
||||
[str(path), str(path.joinpath("dlls")), os.environ.get("PATH", "")]
|
||||
)
|
||||
else:
|
||||
os.add_dll_directory(str(path))
|
||||
os.add_dll_directory(str(path.joinpath("dlls")))
|
||||
os.add_dll_directory(str(path)) # type: ignore[attr-defined]
|
||||
os.add_dll_directory(str(path.joinpath("dlls"))) # type: ignore[attr-defined]
|
||||
|
||||
# We need to add plugins/data to sys.path, mainly for PyQt5
|
||||
sys.path.insert(1, path.joinpath("plugins", "data").as_posix())
|
||||
|
||||
mobase = importlib.machinery.ExtensionFileLoader(
|
||||
"mobase", path.joinpath("plugins", "data", "pythonrunner.dll").as_posix()
|
||||
).load_module()
|
||||
mobase = load_module("mobase", path.joinpath("plugins", "data", "pythonrunner.dll"))
|
||||
|
||||
if not moprivate:
|
||||
return mobase
|
||||
|
||||
moprivate = importlib.machinery.ExtensionFileLoader(
|
||||
"moprivate", path.joinpath("plugins", "data", "pythonrunner.dll").as_posix()
|
||||
).load_module()
|
||||
moprivate = load_module(
|
||||
"moprivate", path.joinpath("plugins", "data", "pythonrunner.dll")
|
||||
)
|
||||
|
||||
return mobase, moprivate
|
||||
|
||||
|
||||
+117
-30
@@ -7,7 +7,9 @@ from . import utils
|
||||
|
||||
|
||||
class Type:
|
||||
""" Class representing a python type. """
|
||||
"""
|
||||
Class representing a python type.
|
||||
"""
|
||||
|
||||
# The `MoVariant` actual type - This should be List["MoVariant"] and
|
||||
# Dict[str, "MoVariant"], but mypy (and other type checkers) do not
|
||||
@@ -35,7 +37,10 @@ class Type:
|
||||
self.name = "{}.{}".format(m.__name__, self.name)
|
||||
|
||||
def typing(self, settings: utils.Settings) -> str:
|
||||
""" Returns a valid typing representation for this type. """
|
||||
"""
|
||||
Returns:
|
||||
A valid typing representation for this type.
|
||||
"""
|
||||
from .register import MOBASE_REGISTER
|
||||
|
||||
# Check if this is a mobase object, in which case we escape:
|
||||
@@ -51,15 +56,30 @@ class Type:
|
||||
return self.name
|
||||
|
||||
def is_none(self) -> bool:
|
||||
""" Check if this type represent None. """
|
||||
"""
|
||||
Check if this type represent None.
|
||||
|
||||
Returns:
|
||||
True if this type represents None.
|
||||
"""
|
||||
return self.name.lower() in ("none", "nonetype")
|
||||
|
||||
def is_object(self) -> bool:
|
||||
""" Check if this type represent the generic "object" type. """
|
||||
"""
|
||||
Check if this type represent the generic "object" type.
|
||||
|
||||
Returns:
|
||||
True if this type represent the generic object type.
|
||||
"""
|
||||
return self.name.lower() == "object"
|
||||
|
||||
def is_any(self) -> bool:
|
||||
""" Check if this type repesent the typing "Any". """
|
||||
"""
|
||||
Check if this type represent the typing "Any".
|
||||
|
||||
Returns:
|
||||
True if this type represent the typing "Any".
|
||||
"""
|
||||
return self.name == "Any"
|
||||
|
||||
def __str__(self):
|
||||
@@ -78,7 +98,9 @@ class Type:
|
||||
|
||||
|
||||
class CType(Type):
|
||||
""" Class representing a C++ type from boost::python. """
|
||||
"""
|
||||
Class representing a C++ type from boost::python.
|
||||
"""
|
||||
|
||||
# List of smart pointer types - Not including pointer that should not
|
||||
# be exposed (unique_ptr, weak_ptr):
|
||||
@@ -221,7 +243,12 @@ class CType(Type):
|
||||
args = [a for a in args if a != "boost::tuples::null_type"]
|
||||
name = "Tuple[{}]".format(", ".join(args))
|
||||
|
||||
# Fix for function...
|
||||
# Fix for optional:
|
||||
if name.startswith("std::optional"):
|
||||
name = name[13:].strip()[1:-1].strip()
|
||||
name = "Optional[{}]".format(parse_ctype(name).typing(settings))
|
||||
|
||||
# Fix for function:
|
||||
if name.startswith("std::function"):
|
||||
name = name[13:].strip()[1:-1].strip()
|
||||
rtype, vargs = parse_csig(name, "")
|
||||
@@ -254,14 +281,30 @@ class CType(Type):
|
||||
|
||||
return name
|
||||
|
||||
def _is_builtin_python_type(self, t: Type):
|
||||
"""Check if the given type is a 'raw' python type, i.e., a type that cannot
|
||||
def _is_builtin_python_type(self, t: Type) -> bool:
|
||||
"""
|
||||
Check if the given type is a 'raw' python type, i.e., a type that cannot
|
||||
be a C++ reference. This is mainly used to report errors when pointers to such
|
||||
type are present in the interface."""
|
||||
type are present in the interface.
|
||||
|
||||
Args:
|
||||
t: The type to check.
|
||||
|
||||
Returns:
|
||||
True if the given type is a raw python type, False otherwise.
|
||||
"""
|
||||
return t.name in ["bool", "int", "float", "str", "list", "std", "dict", "bytes"]
|
||||
|
||||
def typing(self, settings: utils.Settings) -> str:
|
||||
""" Returns a valid typing representation for this type. """
|
||||
"""
|
||||
Create a valid typing representation for this type.
|
||||
|
||||
args:
|
||||
settings: The settings to use.
|
||||
|
||||
Returns:
|
||||
A valid typing representing for this type.
|
||||
"""
|
||||
from .register import MOBASE_REGISTER
|
||||
|
||||
name = self.name
|
||||
@@ -292,11 +335,21 @@ class CType(Type):
|
||||
return name
|
||||
|
||||
def is_raw_pointer(self) -> bool:
|
||||
""" Check if this type is a raw pointer type. """
|
||||
"""
|
||||
Check if this type is a raw pointer type.
|
||||
|
||||
Returns:
|
||||
True if this type is a raw point type, False otherwise.
|
||||
"""
|
||||
return self.name.endswith("*")
|
||||
|
||||
def is_smart_pointer(self) -> bool:
|
||||
""" Check if this type is a smart pointer type. """
|
||||
"""
|
||||
Check if this type is a smart pointer type.
|
||||
|
||||
Returns:
|
||||
True if this type is a smart pointer type, False otherwise.
|
||||
"""
|
||||
|
||||
for ptr in self.SMART_POINTERS:
|
||||
if self.name.startswith(ptr):
|
||||
@@ -305,19 +358,28 @@ class CType(Type):
|
||||
return False
|
||||
|
||||
def is_pointer(self) -> bool:
|
||||
""" Check if this type corresponds to a pointer type. """
|
||||
"""
|
||||
Check if this type corresponds to a pointer type.
|
||||
|
||||
Returns:
|
||||
True if this type represents a pointer type (raw or smart), False
|
||||
otherwise.
|
||||
"""
|
||||
return self.is_raw_pointer() or self.is_smart_pointer()
|
||||
|
||||
def is_optional(self) -> bool:
|
||||
""" Check if this type is optional (i.e., can be None in python). """
|
||||
"""
|
||||
Check if this type is optional (i.e., can be None in python).
|
||||
|
||||
Returns:
|
||||
True if this type can be optional, False otherwise.
|
||||
"""
|
||||
return self._optional
|
||||
|
||||
def is_none(self) -> bool:
|
||||
""" Check if this type represent None. """
|
||||
return self.name.lower() == "void"
|
||||
|
||||
def is_object(self) -> bool:
|
||||
""" Check if this type represent the generic "object" type """
|
||||
return self.name.lower() == "_object *"
|
||||
|
||||
def __str__(self) -> str:
|
||||
@@ -328,7 +390,9 @@ class CType(Type):
|
||||
|
||||
|
||||
class Ret:
|
||||
""" Class representing the return value of a function (type and documentation). """
|
||||
"""
|
||||
Class representing the return value of a function (type and documentation).
|
||||
"""
|
||||
|
||||
type: Type
|
||||
doc: str
|
||||
@@ -339,7 +403,9 @@ class Ret:
|
||||
|
||||
|
||||
class Arg:
|
||||
""" Class representing a function argument (type and eventual default value). """
|
||||
"""
|
||||
Class representing a function argument (type and eventual default value).
|
||||
"""
|
||||
|
||||
# Constant representing None since None indicates no default value:
|
||||
DEFAULT_NONE = "None"
|
||||
@@ -393,7 +459,9 @@ class Arg:
|
||||
|
||||
class Exc:
|
||||
|
||||
""" Small class representing exception that can be raised from functions. """
|
||||
"""
|
||||
Small class representing exception that can be raised from functions.
|
||||
"""
|
||||
|
||||
type: Type
|
||||
doc: str
|
||||
@@ -404,7 +472,9 @@ class Exc:
|
||||
|
||||
|
||||
class Function:
|
||||
""" Class representing a function. """
|
||||
"""
|
||||
Class representing a function.
|
||||
"""
|
||||
|
||||
name: str
|
||||
ret: Ret
|
||||
@@ -438,7 +508,9 @@ class Function:
|
||||
|
||||
|
||||
class Method(Function):
|
||||
""" Class representing a method. """
|
||||
"""
|
||||
Class representing a method.
|
||||
"""
|
||||
|
||||
cls: "Class"
|
||||
abstract: Union[str, bool]
|
||||
@@ -475,7 +547,9 @@ class Method(Function):
|
||||
|
||||
|
||||
class Constant:
|
||||
""" Class representing a constant. """
|
||||
"""
|
||||
Class representing a constant.
|
||||
"""
|
||||
|
||||
name: str
|
||||
type: Optional[Type]
|
||||
@@ -494,7 +568,9 @@ class Constant:
|
||||
|
||||
|
||||
class Property:
|
||||
""" Class representing a property. """
|
||||
"""
|
||||
Class representing a property.
|
||||
"""
|
||||
|
||||
name: str
|
||||
type: Type
|
||||
@@ -512,7 +588,9 @@ class Property:
|
||||
|
||||
|
||||
class Class:
|
||||
""" Class representing a class. """
|
||||
"""
|
||||
Class representing a class.
|
||||
"""
|
||||
|
||||
name: str
|
||||
bases: List["Class"]
|
||||
@@ -562,7 +640,10 @@ class Class:
|
||||
|
||||
@property
|
||||
def canonical_name(self):
|
||||
""" Return the canonical name of this class. """
|
||||
"""
|
||||
Returns:
|
||||
The canonical name of this class.
|
||||
"""
|
||||
name = self.name
|
||||
oc = self.outer_class
|
||||
while oc is not None:
|
||||
@@ -591,18 +672,24 @@ class Class:
|
||||
|
||||
class PyClass(Class):
|
||||
|
||||
"""Class use to wrap Python class to be used as parent class for some classes
|
||||
in mobase."""
|
||||
"""
|
||||
Class use to wrap Python class to be used as parent class for some classes
|
||||
in mobase.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, name: str,
|
||||
self,
|
||||
name: str,
|
||||
):
|
||||
super().__init__(name, [], [])
|
||||
self.abstract = False
|
||||
|
||||
|
||||
class Enum(Class):
|
||||
""" Class representing an enum. """
|
||||
|
||||
"""
|
||||
Class representing an enum.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, values: Dict[str, int]):
|
||||
# Note: Boost.Python.enum inherits int() not enum.Enum() but for the sake
|
||||
|
||||
+8
-4
@@ -167,7 +167,7 @@ def parse_csig(s, name) -> Tuple[CType, List[Arg]]:
|
||||
args_ss = magic_split(args_s, ",", "(<", ")>")
|
||||
args = [parse_carg(v, i > len(args_ss) - c - 1) for i, v in enumerate(args_ss)]
|
||||
|
||||
return rtype, args
|
||||
return rtype, [arg for arg in args if not arg.type.is_none()]
|
||||
|
||||
|
||||
def parse_psig(s: str, name: str) -> Tuple[Type, List[Arg]]:
|
||||
@@ -313,7 +313,8 @@ def make_enum(fullname: str, e: type) -> Enum:
|
||||
values = e.values # type: ignore
|
||||
|
||||
return Enum(
|
||||
e.__name__, OrderedDict((values[k].name, k) for k in sorted(values.keys())),
|
||||
e.__name__,
|
||||
OrderedDict((values[k].name, k) for k in sorted(values.keys())),
|
||||
)
|
||||
|
||||
|
||||
@@ -387,7 +388,10 @@ def make_functions(name: str, e) -> List[Function]:
|
||||
|
||||
return [
|
||||
Function(
|
||||
e.__name__, Ret(ovld.rtype), ovld.args, has_overloads=len(overloads) > 1,
|
||||
e.__name__,
|
||||
Ret(ovld.rtype),
|
||||
ovld.args,
|
||||
has_overloads=len(overloads) > 1,
|
||||
)
|
||||
for ovld in overloads
|
||||
]
|
||||
@@ -475,7 +479,7 @@ def make_class(fullname: str, e: type, register: MobaseRegister) -> Class:
|
||||
|
||||
# Find the methods:
|
||||
methods = [m[1] for m in all_attrs if callable(m[1])]
|
||||
methods = sorted(methods, key=lambda m: m.__name__)
|
||||
methods = sorted(methods, key=lambda m: str(m.__name__))
|
||||
|
||||
# Filter out methods not provided or implemented:
|
||||
methods = [
|
||||
|
||||
@@ -8,12 +8,13 @@ from .mtypes import Class, Type, CType, Function
|
||||
|
||||
|
||||
class MobaseRegister:
|
||||
""" Class that register class. """
|
||||
"""
|
||||
Class that register classes.
|
||||
"""
|
||||
|
||||
objects: Dict[str, Union[Class, List[Function]]]
|
||||
|
||||
def __init__(self):
|
||||
""" Create a new register with the list of objects. """
|
||||
self.raw_objects: Dict[str, Union[type]] = OrderedDict()
|
||||
self.objects = {}
|
||||
|
||||
@@ -26,13 +27,15 @@ class MobaseRegister:
|
||||
def make_object(
|
||||
self, name: str, e: Optional[type] = None
|
||||
) -> Union["Class", List["Function"]]:
|
||||
"""Construct a Function, Class or Enum for the given object.
|
||||
"""
|
||||
Construct a Function, Class or Enum for the given object.
|
||||
|
||||
Args:
|
||||
name: The name of the object to inspect.
|
||||
e: The object to inspect, or None to fetch it from the underlying list.
|
||||
|
||||
Returns: A Class object for the given type, or a list of function overloads.
|
||||
Returns:
|
||||
A Class object for the given type, or a list of function overloads.
|
||||
"""
|
||||
from .parser import make_enum, make_class, is_enum, make_functions
|
||||
|
||||
|
||||
+28
-13
@@ -1,14 +1,27 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from collections import OrderedDict, defaultdict
|
||||
from typing import List, Dict, Any, Tuple, TextIO, Optional, Union, NamedTuple, Set
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Set,
|
||||
TextIO,
|
||||
Tuple,
|
||||
Union,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
from . import logger
|
||||
from . import register
|
||||
from . import mtypes
|
||||
|
||||
import yaml
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .register import MobaseRegister
|
||||
|
||||
|
||||
class Settings:
|
||||
class FunctionSettings(NamedTuple):
|
||||
@@ -21,7 +34,7 @@ class Settings:
|
||||
abstract: Optional[bool] = None
|
||||
deprecated: bool = False
|
||||
|
||||
register: "register.MobaseRegister"
|
||||
register: "MobaseRegister"
|
||||
|
||||
# Name to ignore:
|
||||
_ignore_names: List[str]
|
||||
@@ -32,9 +45,7 @@ class Settings:
|
||||
# Content of mobase:
|
||||
_mobase: Dict[str, Dict[str, Any]]
|
||||
|
||||
def __init__(
|
||||
self, register: "register.MobaseRegister", fp: Optional[TextIO] = None
|
||||
):
|
||||
def __init__(self, register: "MobaseRegister", fp: Optional[TextIO] = None):
|
||||
|
||||
self.register = register
|
||||
|
||||
@@ -63,7 +74,8 @@ class Settings:
|
||||
return self._mobase
|
||||
|
||||
def _get_class_settings(self, canonical_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""Retrieve the settings for the given class.
|
||||
"""
|
||||
Retrieve the settings for the given class.
|
||||
|
||||
Args:
|
||||
canonical_name: Canonical name of the class.
|
||||
@@ -84,7 +96,8 @@ class Settings:
|
||||
def _parse_function_settings(
|
||||
self, settings: Union[str, Dict[str, Any]]
|
||||
) -> "FunctionSettings":
|
||||
"""Parse settings for a function or method.
|
||||
"""
|
||||
Parse settings for a function or method.
|
||||
|
||||
Args:
|
||||
settings: Settings corresponding to a function.
|
||||
@@ -168,8 +181,7 @@ class Settings:
|
||||
if fsettings.args is not None:
|
||||
if len(fsettings.args) != len(fn.args):
|
||||
logger.warn(
|
||||
"Mismatch number of arguments for function mobase.{}."
|
||||
.format(sname) # noqa
|
||||
f"Mismatch number of arguments for function mobase.{sname}."
|
||||
)
|
||||
|
||||
for sarg, marg in zip(fsettings.args, fn.args):
|
||||
@@ -197,7 +209,8 @@ class Settings:
|
||||
logger.warn("Missing settings for function mobase.{}.".format(sname))
|
||||
|
||||
def patch_class(self, cls: "mtypes.Class"):
|
||||
"""Patch the given class using the given overwrites.
|
||||
"""
|
||||
Patch the given class using the given overwrites.
|
||||
|
||||
See config.json for some examples of valid overwrites.
|
||||
|
||||
@@ -398,12 +411,14 @@ class Settings:
|
||||
|
||||
|
||||
def clean_class(cls: "mtypes.Class", settings: Settings):
|
||||
"""Clean the given class object.
|
||||
"""
|
||||
Clean the given class object.
|
||||
|
||||
Args:
|
||||
cls: The class object to clean.
|
||||
settings: The settings.
|
||||
"""
|
||||
|
||||
from .register import MOBASE_REGISTER
|
||||
|
||||
# Remove duplicate methods (based on name and argument types):
|
||||
@@ -412,7 +427,7 @@ def clean_class(cls: "mtypes.Class", settings: Settings):
|
||||
] = OrderedDict()
|
||||
methods_by_name = defaultdict(list)
|
||||
for m in cls.methods:
|
||||
k = (m.name, tuple(m.args[1:]))
|
||||
k = (m.name, tuple(m.args if m.is_static() else m.args[1:]))
|
||||
if k not in methods:
|
||||
methods[k] = []
|
||||
methods[k].append(m)
|
||||
|
||||
+19
-6
@@ -21,7 +21,8 @@ class Writer:
|
||||
print(*args, **kwargs)
|
||||
|
||||
def _print_doc(self, doc: str, indent: str):
|
||||
"""Print the given documentation at the given indentaiton level.
|
||||
"""
|
||||
Print the given documentation at the given indentation level.
|
||||
|
||||
Args:
|
||||
doc: Documentation to print.
|
||||
@@ -36,7 +37,9 @@ class Writer:
|
||||
self._print()
|
||||
|
||||
def print_imports(self, imports: List[Union[str, Tuple[str, List[str]]]]):
|
||||
""" Print the given imports. """
|
||||
"""
|
||||
Print the given imports.
|
||||
"""
|
||||
for imp in imports:
|
||||
if isinstance(imp, str):
|
||||
self._print("import {}".format(imp))
|
||||
@@ -45,7 +48,9 @@ class Writer:
|
||||
self._print()
|
||||
|
||||
def print_function(self, fn: Function, indent: str = ""):
|
||||
""" Print the given Function object at the given indentation level. """
|
||||
"""
|
||||
Print the given Function object at the given indentation level.
|
||||
"""
|
||||
|
||||
if fn.has_overloads():
|
||||
self._print("{}@overload".format(indent))
|
||||
@@ -117,7 +122,9 @@ class Writer:
|
||||
self._print()
|
||||
|
||||
def print_property(self, cls: Class, prop: Property, indent: str):
|
||||
""" Print the given Property object at the given indentation level. """
|
||||
"""
|
||||
Print the given Property object at the given indentation level.
|
||||
"""
|
||||
|
||||
if prop.type.is_object() or prop.type.is_any():
|
||||
logger.warning(
|
||||
@@ -142,7 +149,9 @@ class Writer:
|
||||
self._print()
|
||||
|
||||
def print_class(self, cls: Class, indent: str = ""):
|
||||
""" Print the given Class object at the given indentation level. """
|
||||
"""
|
||||
Print the given Class object at the given indentation level.
|
||||
"""
|
||||
|
||||
bc = ""
|
||||
if cls.bases or cls.is_abstract():
|
||||
@@ -187,7 +196,11 @@ class Writer:
|
||||
# Note: We do not print the value, we use ...
|
||||
self._print(
|
||||
"{}{}{} = {}{}".format(
|
||||
indent + " ", constant.name, typing, "...", comment,
|
||||
indent + " ",
|
||||
constant.name,
|
||||
typing,
|
||||
"...",
|
||||
comment,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
@@ -29,8 +28,8 @@ parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
type=Path,
|
||||
default=sys.stdout,
|
||||
help="output file (output to stdout if not specified)",
|
||||
default="stubs/setup/mobase-stubs/__init__.pyi",
|
||||
help="output file (default stubs/setup/mobase-stubs/__init__.pyi)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--verbose", action="store_true", help="verbose mode (all logs go to stderr)"
|
||||
@@ -72,6 +71,11 @@ for name in dir(mobase):
|
||||
if name == "MoVariant":
|
||||
continue
|
||||
|
||||
# For now, ignore this since it is a submodule and we
|
||||
# not handle them.
|
||||
if name == "widgets":
|
||||
continue
|
||||
|
||||
objects.append((name, getattr(mobase, name)))
|
||||
|
||||
# Enum first, and then alphabetical. Might cause issue with base classes, so
|
||||
|
||||
@@ -14,3 +14,20 @@ per-file-ignores =
|
||||
warn_return_any = True
|
||||
warn_unused_configs = True
|
||||
namespace_packages = True
|
||||
|
||||
[tox:tox]
|
||||
skipsdist = true
|
||||
envlist = py38-lint
|
||||
|
||||
[testenv:py38-lint]
|
||||
skip_install = true
|
||||
deps =
|
||||
black
|
||||
mypy
|
||||
flake8
|
||||
flake8-black
|
||||
PyQt5-stubs
|
||||
commands =
|
||||
black generator main.py --check --diff
|
||||
flake8 generator main.py
|
||||
mypy generator main.py
|
||||
|
||||
+300
-113
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user