Skip to content

Instantly share code, notes, and snippets.

@Holt59
Last active May 10, 2020 16:03
Show Gist options
  • Save Holt59/17a5fec03b57de83d90b09ca7f620823 to your computer and use it in GitHub Desktop.
Save Holt59/17a5fec03b57de83d90b09ca7f620823 to your computer and use it in GitHub Desktop.
Stubs file for ModOrganizer2 Python Interface - For 2.3.0a8 version.
from enum import Enum
from typing import Dict, Iterable, Iterator, List, Tuple, Union, Any, Optional, Callable, overload
import PyQt5.QtCore
import PyQt5.QtGui
import PyQt5.QtWidgets
class InterfaceNotImplemented: pass
class GuessQuality(Enum):
INVALID = 0
FALLBACK = 1
GOOD = 2
META = 3
PRESET = 4
USER = 5
class InstallResult(Enum):
SUCCESS = 0
FAILED = 1
CANCELED = 2
MANUAL_REQUESTED = 3
NOT_ATTEMPTED = 4
class LoadOrderMechanism(Enum):
FileTime = 0
PluginsTxt = 1
class ModState(Enum):
exists = 1
active = 2
essential = 4
empty = 8
endorsed = 16
valid = 32
alternate = 64
class PluginState(Enum):
missing = 0
inactive = 1
active = 2
class ProfileSetting(Enum):
mods = 1
configuration = 2
savegames = 4
preferDefaults = 8
class ReleaseType(Enum):
prealpha = 0
alpha = 1
beta = 2
candidate = 3
final = 4
class SortMechanism(Enum):
NONE = 0
MLOX = 1
BOSS = 2
LOOT = 3
class VersionScheme(Enum):
discover = 0
regular = 1
decimalmark = 2
numbersandletters = 3
date = 4
literal = 5
class BSAInvalidation:
def __init__(self): pass
def activate(self, arg1: "IProfile"): pass
def deactivate(self, arg1: "IProfile"): pass
def isInvalidationBSA(self, arg1: str) -> bool: pass
class DataArchives:
def __init__(self): pass
def addArchive(self, arg1: "IProfile", arg2: int, arg3: str): pass
def archives(self, arg1: "IProfile") -> List[str]: pass
def removeArchive(self, arg1: "IProfile", arg2: str): pass
def vanillaArchives(self) -> List[str]: pass
class ExecutableInfo:
def __init__(self, arg1: str, arg2: PyQt5.QtCore.QFileInfo): pass
def arguments(self) -> List[str]: pass
def asCustom(self) -> "ExecutableInfo": pass
def binary(self) -> PyQt5.QtCore.QFileInfo: pass
def isCustom(self) -> bool: pass
def isValid(self) -> bool: pass
def steamAppID(self) -> str: pass
def title(self) -> str: pass
def withArgument(self, arg1: str) -> "ExecutableInfo": pass
def withSteamAppId(self, arg1: str) -> "ExecutableInfo": pass
def withWorkingDirectory(self, arg1: PyQt5.QtCore.QDir) -> "ExecutableInfo": pass
def workingDirectory(self) -> PyQt5.QtCore.QDir: pass
class FileInfo:
@property
def archive(self) -> str: pass
@archive.setter
def archive(self, arg0: str): pass
@property
def filePath(self) -> str: pass
@filePath.setter
def filePath(self, arg0: str): pass
@property
def origins(self) -> List[str]: pass
@origins.setter
def origins(self, arg0: List[str]): pass
def __init__(self): pass
class FileTreeEntry:
class FileTypes(Enum):
DIRECTORY = 1
FILE = 2
FILE_OR_DIRECTORY = 3
DIRECTORY = 1
FILE = 2
FILE_OR_DIRECTORY = 3
@overload
def __eq__(self, arg1: str) -> bool: pass
@overload
def __eq__(self, arg1: "FileTreeEntry") -> bool: pass
@overload
def __eq__(self, arg1: object) -> bool: pass
def __repr__(self) -> str: pass
def detach(self) -> bool: pass
def fileType(self) -> FileTreeEntry.FileTypes: pass
def isDir(self) -> bool: pass
def isFile(self) -> bool: pass
def moveTo(self, arg1: "IFileTree") -> bool: pass
def name(self) -> str: pass
def parent(self) -> Optional[IFileTree]: pass
def path(self, arg1: str = '\\') -> str: pass
def pathFrom(self, arg1: "IFileTree", arg2: str = '\\') -> str: pass
def setTime(self, arg1: PyQt5.QtCore.QDateTime): pass
def suffix(self) -> str: pass
def time(self) -> PyQt5.QtCore.QDateTime: pass
class GamePlugins:
def __init__(self): pass
def getLoadOrder(self, arg1: List[str]): pass
def lightPluginsAreSupported(self) -> bool: pass
def readPluginLists(self, arg1: "IPluginList"): pass
def writePluginLists(self, arg1: "IPluginList"): pass
class GuessedString:
@overload
def __init__(self): pass
@overload
def __init__(self, arg1: str, arg2: "GuessQuality"): pass
def __str__(self) -> str: pass
@overload
def reset(self) -> "GuessedString": pass
@overload
def reset(self, arg1: str, arg2: "GuessQuality") -> "GuessedString": pass
@overload
def reset(self, arg1: "GuessedString") -> "GuessedString": pass
def setFilter(self, arg1: Callable[[str], Union[str, bool]]): pass
@overload
def update(self, arg1: str) -> "GuessedString": pass
@overload
def update(self, arg1: str, arg2: "GuessQuality") -> "GuessedString": pass
def variants(self) -> List[str]: pass
class IDownloadManager:
def __init__(self): pass
def downloadPath(self, arg1: int) -> str: pass
def startDownloadNexusFile(self, arg1: int, arg2: int) -> int: pass
def startDownloadURLs(self, arg1: List[str]) -> int: pass
class IFileTree(FileTreeEntry):
class InsertPolicy(Enum):
FAIL_IF_EXISTS = 0
REPLACE = 1
MERGE = 2
FAIL_IF_EXISTS = 0
MERGE = 2
REPLACE = 1
def __bool__(self) -> bool: pass
def __getitem__(self, arg1: int) -> "FileTreeEntry": pass
def __iter__(self) -> Iterator[FileTreeEntry]: pass
def __len__(self) -> int: pass
def __repr__(self) -> str: pass
def addDirectory(self, arg1: str) -> Optional[IFileTree]: pass
def addFile(self, arg1: str, arg2: PyQt5.QtCore.QDateTime = PyQt5.QtCore.QDateTime()) -> Optional[FileTreeEntry]: pass
def clear(self) -> bool: pass
def createOrphanTree(self, arg1: str = '') -> "IFileTree": pass
def exists(self, arg1: str, arg2: FileTreeEntry.FileTypes = FileTreeEntry.FileTypes.FILE_OR_DIRECTORY) -> bool: pass
def find(self, arg1: str, arg2: FileTreeEntry.FileTypes = FileTreeEntry.FileTypes.FILE_OR_DIRECTORY) -> Optional[FileTreeEntry]: pass
def insert(self, arg1: "FileTreeEntry", arg2: IFileTree.InsertPolicy = IFileTree.InsertPolicy.FAIL_IF_EXISTS) -> bool: pass
def merge(self, arg1: "IFileTree", arg2: bool = False) -> Union[Dict["FileTreeEntry", "FileTreeEntry"], int, bool]: pass
def move(self, arg1: "FileTreeEntry", arg2: str, arg3: IFileTree.InsertPolicy = IFileTree.InsertPolicy.FAIL_IF_EXISTS) -> bool: pass
def pathTo(self, arg1: "FileTreeEntry", arg2: str = '\\') -> str: pass
@overload
def remove(self, arg1: str) -> bool: pass
@overload
def remove(self, arg1: "FileTreeEntry") -> bool: pass
def removeAll(self, arg1: List[str]) -> int: pass
def removeIf(self, arg1: Callable[["FileTreeEntry"], bool]) -> int: pass
class IInstallationManager:
def extractFile(self, arg1: "FileTreeEntry") -> str: pass
def extractFiles(self, arg1: List["FileTreeEntry"]) -> List[str]: pass
def installArchive(self, arg1: "GuessedString", arg2: str, arg3: int) -> "InstallResult": pass
def setURL(self, arg1: str): pass
class IModInterface:
def __init__(self): pass
def absolutePath(self) -> str: pass
def addCategory(self, arg1: str): pass
def addNexusCategory(self, arg1: int): pass
def categories(self) -> List[str]: pass
def name(self) -> str: pass
def remove(self) -> bool: pass
def removeCategory(self, arg1: str) -> bool: pass
def setGamePlugin(self, arg1: "IPluginGame"): pass
def setIsEndorsed(self, arg1: bool): pass
def setName(self, arg1: str) -> bool: pass
def setNewestVersion(self, arg1: VersionInfo): pass
def setNexusID(self, arg1: int): pass
def setVersion(self, arg1: VersionInfo): pass
class IModList:
def __init__(self): pass
def allMods(self) -> List[str]: pass
def displayName(self, arg1: str) -> str: pass
def onModMoved(self, arg1: Callable[[str, int, int], None]) -> bool: pass
def onModStateChanged(self, arg1: Callable[[str, int], None]) -> bool: pass
def priority(self, arg1: str) -> int: pass
def setActive(self, arg1: str, arg2: bool) -> bool: pass
def setPriority(self, arg1: str, arg2: int) -> bool: pass
def state(self, arg1: str) -> int: pass
class IModRepositoryBridge:
def __init__(self): pass
def requestDescription(self, arg1: str, arg2: int, arg3: PyQt5.QtCore.QVariant): pass
def requestDownloadURL(self, arg1: str, arg2: int, arg3: int, arg4: PyQt5.QtCore.QVariant): pass
def requestFileInfo(self, arg1: str, arg2: int, arg3: int, arg4: PyQt5.QtCore.QVariant): pass
def requestFiles(self, arg1: str, arg2: int, arg3: PyQt5.QtCore.QVariant): pass
def requestToggleEndorsement(self, arg1: str, arg2: int, arg3: str, arg4: bool, arg5: PyQt5.QtCore.QVariant): pass
class IOrganizer:
def appVersion(self) -> VersionInfo: pass
def basePath(self) -> str: pass
def createMod(self, arg1: "GuessedString") -> "IModInterface": pass
def createNexusBridge(self) -> "IModRepositoryBridge": pass
def downloadManager(self) -> "IDownloadManager": pass
def downloadsPath(self) -> str: pass
def findFileInfos(self, arg1: str, arg2: Callable[["FileInfo"], bool]) -> List["FileInfo"]: pass
def findFiles(self, arg1: str, arg2: Callable[[str], bool]) -> List[str]: pass
def getFileOrigins(self, arg1: str) -> List[str]: pass
def getGame(self, arg1: str) -> "IPluginGame": pass
def getMod(self, arg1: str) -> "IModInterface": pass
def installMod(self, arg1: str, arg2: str = '') -> "IModInterface": pass
def listDirectories(self, arg1: str) -> List[str]: pass
def managedGame(self) -> "IPluginGame": pass
def modDataChanged(self, arg1: "IModInterface"): pass
def modList(self) -> "IModList": pass
def modsPath(self) -> str: pass
def modsSortedByProfilePriority(self) -> List[str]: pass
def onAboutToRun(self, arg1: Callable[[str], bool]) -> bool: pass
def onFinishedRun(self, arg1: Callable[[str, int], None]) -> bool: pass
def onModInstalled(self, arg1: Callable[[str], None]) -> bool: pass
def overwritePath(self) -> str: pass
def persistent(self, arg1: str, arg2: str, arg3: PyQt5.QtCore.QVariant = None) -> PyQt5.QtCore.QVariant: pass
def pluginDataPath(self) -> str: pass
def pluginList(self) -> "IPluginList": pass
def pluginSetting(self, arg1: str, arg2: str) -> PyQt5.QtCore.QVariant: pass
def profile(self) -> "IProfile": pass
def profileName(self) -> str: pass
def profilePath(self) -> str: pass
def refreshModList(self, arg1: bool = True): pass
def removeMod(self, arg1: "IModInterface") -> bool: pass
def resolvePath(self, arg1: str) -> str: pass
def setPersistent(self, arg1: str, arg2: str, arg3: PyQt5.QtCore.QVariant, arg4: bool = True): pass
def setPluginSetting(self, arg1: str, arg2: str, arg3: PyQt5.QtCore.QVariant): pass
def startApplication(self, arg1: str, arg2: List[str] = [], arg3: str = '', arg4: str = '', arg5: str = '', arg6: bool = False) -> int: pass
def waitForApplication(self, arg1: int) -> Tuple[bool, int]: pass
class IPlugin:
def __init__(self): pass
def author(self) -> str: pass
def description(self) -> str: pass
def init(self, arg1: "IOrganizer") -> bool: pass
def isActive(self) -> bool: pass
def name(self) -> str: pass
def settings(self) -> List["PluginSetting"]: pass
def version(self) -> VersionInfo: pass
class IPluginDiagnose(IPlugin):
def __init__(self): pass
def _invalidate(self): pass
def activeProblems(self) -> List[int]: pass
def fullDescription(self, arg1: int) -> str: pass
def hasGuidedFix(self, arg1: int) -> bool: pass
def shortDescription(self, arg1: int) -> str: pass
def startGuidedFix(self, arg1: int): pass
class IPluginFileMapper(IPlugin):
def __init__(self): pass
def mappings(self) -> List["Mapping"]: pass
class IPluginGame(IPlugin):
def __init__(self): pass
def CCPlugins(self) -> List[str]: pass
def DLCPlugins(self) -> List[str]: pass
def binaryName(self) -> str: pass
def dataDirectory(self) -> PyQt5.QtCore.QDir: pass
def documentsDirectory(self) -> PyQt5.QtCore.QDir: pass
def executables(self) -> List["ExecutableInfo"]: pass
def featureList(self) -> dict: pass
def gameDirectory(self) -> PyQt5.QtCore.QDir: pass
def gameIcon(self) -> PyQt5.QtGui.QIcon: pass
def gameName(self) -> str: pass
def gameNexusName(self) -> str: pass
def gameShortName(self) -> str: pass
def gameVariants(self) -> List[str]: pass
def gameVersion(self) -> str: pass
def getLauncherName(self) -> str: pass
def iniFiles(self) -> List[str]: pass
def initializeProfile(self, arg1: PyQt5.QtCore.QDir, arg2: int): pass
def isInstalled(self) -> bool: pass
def loadOrderMechanism(self) -> "LoadOrderMechanism": pass
def looksValid(self, arg1: PyQt5.QtCore.QDir) -> bool: pass
def nexusGameID(self) -> int: pass
def nexusModOrganizerID(self) -> int: pass
def primaryPlugins(self) -> List[str]: pass
def primarySources(self) -> List[str]: pass
def savegameExtension(self) -> str: pass
def savegameSEExtension(self) -> str: pass
def savesDirectory(self) -> PyQt5.QtCore.QDir: pass
def setGamePath(self, arg1: str): pass
def setGameVariant(self, arg1: str): pass
def sortMechanism(self) -> "SortMechanism": pass
def steamAPPId(self) -> str: pass
def validShortNames(self) -> List[str]: pass
class IPluginInstaller(IPlugin):
def isArchiveSupported(self, arg1: "IFileTree") -> bool: pass
def isManualInstaller(self) -> bool: pass
def priority(self) -> int: pass
def setInstallationManager(self, arg1: "IInstallationManager"): pass
def setParentWidget(self, arg1: PyQt5.QtWidgets.QWidget): pass
class IPluginInstallerCustom:
def __init__(self): pass
def _manager(self) -> "IInstallationManager": pass
def _parentWidget(self) -> PyQt5.QtWidgets.QWidget: pass
def install(self, arg1: "GuessedString", arg2: str, arg3: str, arg4: str, arg5: int) -> "InstallResult": pass
@overload
def isArchiveSupported(self, arg1: "IFileTree") -> bool: pass
@overload
def isArchiveSupported(self, arg1: str) -> bool: pass
def supportedExtensions(self) -> List[str]: pass
class IPluginInstallerSimple(IPluginInstaller):
def __init__(self): pass
def _manager(self) -> "IInstallationManager": pass
def _parentWidget(self) -> PyQt5.QtWidgets.QWidget: pass
def install(self, arg1: "GuessedString", arg2: "IFileTree", arg3: str, arg4: int) -> Union["InstallResult", "IFileTree", Tuple["InstallResult", "IFileTree", str, int]]: pass
class IPluginList:
def __init__(self): pass
def isMaster(self, arg1: str) -> bool: pass
def loadOrder(self, arg1: str) -> int: pass
def masters(self, arg1: str) -> List[str]: pass
def onPluginMoved(self, arg1: Callable[[str, int, int], None]) -> bool: pass
def onRefreshed(self, arg1: Callable[[None], None]) -> bool: pass
def origin(self, arg1: str) -> str: pass
def pluginNames(self) -> List[str]: pass
def priority(self, arg1: str) -> int: pass
def setLoadOrder(self, arg1: List[str]): pass
def setState(self, arg1: str, arg2: int): pass
def state(self, arg1: str) -> int: pass
class IPluginModPage:
def __init__(self): pass
def _parentWidget(self) -> PyQt5.QtWidgets.QWidget: pass
def displayName(self) -> str: pass
def handlesDownload(self, arg1: PyQt5.QtCore.QUrl, arg2: PyQt5.QtCore.QUrl, arg3: ModRepositoryFileInfo) -> bool: pass
def icon(self) -> PyQt5.QtGui.QIcon: pass
def pageURL(self) -> PyQt5.QtCore.QUrl: pass
def setParentWidget(self, arg1: PyQt5.QtWidgets.QWidget): pass
def useIntegratedBrowser(self) -> bool: pass
class IPluginPreview(IPlugin):
def __init__(self): pass
def genFilePreview(self, arg1: str, arg2: PyQt5.QtCore.QSize) -> PyQt5.QtWidgets.QWidget: pass
def supportedExtensions(self) -> List[str]: pass
class IPluginTool(IPlugin):
def __init__(self): pass
def _parentWidget(self) -> PyQt5.QtWidgets.QWidget: pass
def displayName(self) -> str: pass
def icon(self) -> PyQt5.QtGui.QIcon: pass
def setParentWidget(self, arg1: PyQt5.QtWidgets.QWidget): pass
def tooltip(self) -> str: pass
class IProfile:
def __init__(self): pass
def absolutePath(self) -> str: pass
def invalidationActive(self, arg1: InterfaceNotImplemented) -> bool: pass
def localSavesEnabled(self) -> bool: pass
def localSettingsEnabled(self) -> bool: pass
def name(self) -> str: pass
class ISaveGame:
def __init__(self): pass
def allFiles(self) -> List[str]: pass
def getCreationTime(self) -> PyQt5.QtCore.QDateTime: pass
def getFilename(self) -> str: pass
def getSaveGroupIdentifier(self) -> str: pass
def hasScriptExtenderFile(self) -> bool: pass
class ISaveGameInfoWidget(PyQt5.QtWidgets.QWidget):
def __init__(self, arg1: PyQt5.QtWidgets.QWidget = None): pass
def setSave(self, arg1: str): pass
class LocalSavegames:
def __init__(self): pass
def mappings(self, arg1: PyQt5.QtCore.QDir) -> List["Mapping"]: pass
def prepareProfile(self, arg1: "IProfile") -> bool: pass
class Mapping:
@property
def createTarget(self) -> bool: pass
@createTarget.setter
def createTarget(self, arg0: bool): pass
@property
def destination(self) -> str: pass
@destination.setter
def destination(self, arg0: str): pass
@property
def isDirectory(self) -> bool: pass
@isDirectory.setter
def isDirectory(self, arg0: bool): pass
@property
def source(self) -> str: pass
@source.setter
def source(self, arg0: str): pass
def __init__(self): pass
class ModRepositoryBridge:
@overload
def __init__(self): pass
@overload
def __init__(self, arg1: "IModRepositoryBridge"): pass
def onDescriptionAvailable(self, arg1: object): pass
def onEndorsementToggled(self, arg1: object): pass
def onFileInfoAvailable(self, arg1: object): pass
def onFilesAvailable(self, arg1: object): pass
def onRequestFailed(self, arg1: object): pass
def requestDescription(self, arg1: str, arg2: int, arg3: PyQt5.QtCore.QVariant): pass
def requestFileInfo(self, arg1: str, arg2: int, arg3: int, arg4: PyQt5.QtCore.QVariant): pass
def requestFiles(self, arg1: str, arg2: int, arg3: PyQt5.QtCore.QVariant): pass
def requestToggleEndorsement(self, arg1: str, arg2: int, arg3: str, arg4: bool, arg5: PyQt5.QtCore.QVariant): pass
class ModRepositoryFileInfo:
@property
def categoryID(self) -> Any: pass
@categoryID.setter
def categoryID(self, arg0: Any): pass
@property
def description(self) -> Any: pass
@description.setter
def description(self, arg0: Any): pass
@property
def fileCategory(self) -> Any: pass
@fileCategory.setter
def fileCategory(self, arg0: Any): pass
@property
def fileID(self) -> Any: pass
@fileID.setter
def fileID(self, arg0: Any): pass
@property
def fileName(self) -> Any: pass
@fileName.setter
def fileName(self, arg0: Any): pass
@property
def fileSize(self) -> Any: pass
@fileSize.setter
def fileSize(self, arg0: Any): pass
@property
def fileTime(self) -> Any: pass
@fileTime.setter
def fileTime(self, arg0: Any): pass
@property
def gameName(self) -> Any: pass
@gameName.setter
def gameName(self, arg0: Any): pass
@property
def modID(self) -> Any: pass
@modID.setter
def modID(self, arg0: Any): pass
@property
def modName(self) -> Any: pass
@modName.setter
def modName(self, arg0: Any): pass
@property
def name(self) -> Any: pass
@name.setter
def name(self, arg0: Any): pass
@property
def newestVersion(self) -> Any: pass
@newestVersion.setter
def newestVersion(self, arg0: Any): pass
@property
def repository(self) -> Any: pass
@repository.setter
def repository(self, arg0: Any): pass
@property
def uri(self) -> Any: pass
@uri.setter
def uri(self, arg0: Any): pass
@property
def userData(self) -> Any: pass
@userData.setter
def userData(self, arg0: Any): pass
@property
def version(self) -> Any: pass
@version.setter
def version(self, arg0: Any): pass
@overload
def __init__(self): pass
@overload
def __init__(self, arg1: "ModRepositoryFileInfo"): pass
@overload
def __init__(self, arg1: str = None, arg2: int = None, arg3: int = None): pass
def __str__(self) -> str: pass
@staticmethod
def createFromJson(arg0: str) -> "ModRepositoryFileInfo": pass
class PluginSetting:
def __init__(self, arg1: str, arg2: str, arg3: PyQt5.QtCore.QVariant): pass
class SaveGameInfo:
def __init__(self): pass
def getMissingAssets(self, arg1: str) -> Dict[str, List[str]]: pass
def getSaveGameInfo(self, arg1: str) -> "ISaveGame": pass
def getSaveGameWidget(self, arg1: PyQt5.QtWidgets.QWidget) -> Optional[ISaveGameInfoWidget]: pass
def hasScriptExtenderSave(self, arg1: str) -> bool: pass
class ScriptExtender:
def __init__(self): pass
def BinaryName(self) -> str: pass
def PluginPath(self) -> str: pass
def getArch(self) -> int: pass
def getExtenderVersion(self) -> str: pass
def isInstalled(self) -> bool: pass
def loaderName(self) -> str: pass
def loaderPath(self) -> str: pass
def saveGameAttachmentExtensions(self) -> List[str]: pass
class UnmanagedMods:
def __init__(self): pass
def displayName(self, arg1: str) -> str: pass
def mods(self, arg1: bool) -> List[str]: pass
def referenceFile(self, arg1: str) -> PyQt5.QtCore.QFileInfo: pass
def secondaryFiles(self, arg1: str) -> List[str]: pass
class VersionInfo:
@overload
def __init__(self): pass
@overload
def __init__(self, arg1: str): pass
@overload
def __init__(self, arg1: str, arg2: "VersionScheme"): pass
@overload
def __init__(self, arg1: int, arg2: int, arg3: int): pass
@overload
def __init__(self, arg1: int, arg2: int, arg3: int, arg4: "ReleaseType"): pass
@overload
def __init__(self, arg1: int, arg2: int, arg3: int, arg4: int): pass
@overload
def __init__(self, arg1: int, arg2: int, arg3: int, arg4: int, arg5: "ReleaseType"): pass
@overload
def __eq__(self, arg1: "VersionInfo") -> bool: pass
@overload
def __eq__(self, arg1: object) -> bool: pass
def __ge__(self, arg1: "VersionInfo") -> bool: pass
def __gt__(self, arg1: "VersionInfo") -> bool: pass
def __le__(self, arg1: "VersionInfo") -> bool: pass
def __lt__(self, arg1: "VersionInfo") -> bool: pass
@overload
def __ne__(self, arg1: "VersionInfo") -> bool: pass
@overload
def __ne__(self, arg1: object) -> bool: pass
def __str__(self) -> str: pass
def canonicalString(self) -> str: pass
def clear(self): pass
def displayString(self, arg1: int) -> str: pass
def isValid(self) -> bool: pass
def parse(self, arg1: str, arg2: "VersionScheme", arg3: bool): pass
def scheme(self) -> "VersionScheme": pass
# -*- encoding: utf-8 -*-
# Stubs generator for the mobase module.
#
# This file can be used to generate stubs for mobase by parsing the docstrings
# generated by boost::python. You need to be able to import the mobase module:
# - Create an empty repository.
# - Copy all the DLLs from the dlls folder to this repository.
# - Copy uibase.dll, pythonXX.dll and boost_pythonXX.dll to this repository.
# - Copy plugins/data/pythonrunner.dll to mobase.pyd in this repository.
# You need to be in a python environment with PyQt5 installed.
#
# IMPORTANT: All the outputs are made to stdout so if you want to log anything,
# use sys.stderr: print("...").
from collections import OrderedDict, defaultdict
import inspect
import logging
import re
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from typing import Tuple, Dict, List, Union, Optional, Any
# Better not import the stubs in the stubs generation script:
import mobase # type: ignore
logging.basicConfig(stream=sys.stderr, format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)
logger.setLevel(logging.WARNING)
class MobaseRegister:
""" Class that register class. """
def __init__(self):
""" Create a new register with the list of objects. """
self.raw_objects: Dict[str, Union[type]] = OrderedDict()
self.objects: Dict[str, "Class"] = {}
self._cpptypes = {}
self.cpp2py = {}
def add_object(self, name, object):
self.raw_objects[name] = object
def make_object(self, name: str, e: Optional[type] = None) -> "Class":
""" Construct a 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.
"""
if e is None:
e = self.raw_objects[name]
if name not in self.raw_objects:
self.raw_objects[name] = e
if name not in self.objects:
if is_enum(e):
self.objects[name] = make_enum(name, e)
else:
self.objects[name] = make_class(name, e, self)
return self.objects[name]
def register_type(self, ptype: "Type", ctype: "CType"):
""" Register an equivalence between a python name and a C++ name.
Args:
python_name: Name of the Python class.
cpp_name: Name of the C++ class.
"""
# Register the const equivalent for smart pointers:
cname = ctype.name
if ctype.is_smart_pointer():
if cname.find(" const >") != -1:
c2name = cname.replace(" const >", ">")
if c2name in self._cpptypes:
ptype = self.cpp2py[c2name]
# Not the const, replace the const one:
else:
c2name = cname.replace(">", " const >")
if c2name in self._cpptypes and self.cpp2py[c2name].is_object():
self._cpptypes[c2name] = ctype
self.cpp2py[c2name] = ptype
logger.warning("Replace registration {} [c++] with {} [python] using {} information.".format(c2name, ptype.name, cname))
if cname not in MOBASE_REGISTER.cpp2py:
self._cpptypes[cname] = ctype
self.cpp2py[cname] = ptype
logger.info("Registered {} [c++] as {} [python].".format(cname, ptype.name))
@property
def py2cpp(self):
result = {v.name: [] for v in self.cpp2py.values()}
for k in self.cpp2py:
result[self.cpp2py[k].name].append(self._cpptypes[k])
return result
MOBASE_REGISTER = MobaseRegister()
def magic_split(value: str, sep=",", open="(<", close=")>"):
""" Split the value according to the given separator, but keeps together elements
within the given separator. Useful to split C++ signature function since type names
can contain special characters...
Examples:
- magic_split("a,b,c", sep=",") -> ["a", "b", "c"]
- magic_split("a<b,c>,d(e,<k,c>),p) -> ["a<b,c>", "d(e,<k,c>)", "p"]
Args:
value: String to split.
sep: Separator to use.
open: List of opening characters.
close: List of closing characters. Order must match open.
Returns: The list of split parts from value.
"""
i, j = 0, 0
s: List[str] = []
r = []
while i < len(value):
j = i + 1
while j < len(value):
c = value[j]
# Separator found and the stack is empty:
if c == sep and not s:
break
# Check close/open:
if c in open:
s.append(open.index(c))
elif c in close:
# The stack might be empty if the separator is also an opening element:
if not s and sep in open and j + 1 == len(value):
pass
else:
t = s.pop()
if t != close.index(c):
raise ValueError("Found closing element {} for opening element {}.".format(
c, open[t]))
j += 1
r.append(value[i:j])
i = j + 1
return r
class Type:
""" Class representing a python type. """
def __init__(self, name: str):
self.name = name.strip()
# Find PyQt types:
for m in (QtCore, QtGui, QtWidgets):
if self.name in dir(m):
self.name = "{}.{}".format(m.__name__, self.name)
def typing(self) -> str:
""" Returns a valid typing representation for this type. """
# Check if this is a mobase object, in which case we escape:
if self.name in MOBASE_REGISTER.objects:
return '"{}"'.format(self.name)
# Check in existing objects (also inner classes) - This may cause
# issue with conflicts, but those should not be present:
for k in MOBASE_REGISTER.objects:
if k.split(".")[-1] == self.name:
return k
return self.name
def is_none(self) -> bool:
""" Check if this type represent None. """
return self.name.lower() == "none"
def is_object(self) -> bool:
""" Check if this type represent the generic "object" type """
return self.name.lower() == "object"
def __str__(self):
return "Type({})".format(self.name)
def __repr__(self):
return str(self)
def __hash__(self):
return hash(self.name)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Type):
return NotImplemented
return self.name == other.name
class CType(Type):
""" 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):
SMART_POINTERS = [
"std::shared_ptr",
"boost::shared_ptr",
"QSharedPointer"
]
# Standard conversions (usually, the python signature is sufficient for
# those, unless we found them inside a tuple):
STANDARD_TYPES = {
"short": "int",
"int": "int",
"long": "int",
"float": "float",
"double": "float"
}
# Replacements for typing (without warnings):
REPLACEMENTS = {
"QString": "str",
"QStringList": "List[str]",
"QWidget *": "PyQt5.QtWidgets.QWidget",
"void *": "object",
"api::object": "object"
}
REPLACEMENTS_WARN = {
# Apparently boost::python does not like nested class:
"Organizer::FileInfo": "FileInfo",
"IOrganizer::FileInfo": "FileInfo",
"IPluginInstaller::EInstallResult": "InstallResult"
}
def __init__(self, name: str, optional: bool = False):
super().__init__(name)
self._optional = optional
def _is_not_valid(self, str):
for x in str:
if x in "<>():*":
return True
return False
def _try_fix(self, name):
pname = name
# Unconverted QFlags are int in python:
if name.startswith("QFlags"):
name = "int"
# If pointer, try to fix the corresponding python name:
if self.is_pointer():
namename: Optional[str] = None
# For display purpose:
optstr = ""
if self.is_optional():
optstr = " [optional]"
# Check if there is a type registered:
if self.name in MOBASE_REGISTER.cpp2py:
newtype = MOBASE_REGISTER.cpp2py[self.name]
if newtype.is_object():
logger.critical("Found {} pointer but did not found any corresponding python type, the interface is likely missing.".format(name))
return "InterfaceNotImplemented"
if self._is_builtin_python_type(newtype):
logger.critical("Found {} which is a pointer to a built-in python type.".format(self.name))
return "InterfaceNotImplemented"
newname = newtype.name
# Note: No WARNING here as this is safe:
logger.info("Replacing {} with {}{}.".format(name, newname, optstr))
# "Tricky" fix for pointer raw pointer and smart pointers:
elif self.is_raw_pointer():
newname = name.strip("*").replace("const", "").strip()
logger.warning("Replacing {} with {}{}.".format(name, newname, optstr))
else:
for ptr in self.SMART_POINTERS:
if name.startswith(ptr):
newname = name[len(ptr):][1:-1].replace("const", "").strip()
logger.warning("Replacing {} with {}{}.".format(name, newname, optstr))
if newname is not None:
if self.is_optional():
return "Optional[{}]".format(newname)
else:
return newname
# Variant:
for c in ("boost::variant", "std::variant"):
if name.startswith(c):
name = name[len(c):].strip()[1:-1].strip()
args = [parse_ctype(c).typing() for c in magic_split(name)]
name = "Union[{}]".format(", ".join(args))
for c in ("std::vector", "std::set", "std::unordered_set", "std::list", "QList", "QVector", "QSet"):
if name.startswith(c):
name = name[len(c):].strip()[1:-1].strip()
arg = parse_ctype(magic_split(name)[0]).typing()
name = "List[{}]".format(arg)
for c in ("std::map", "std::unordered_map", "QMap"):
if name.startswith(c):
name = name[len(c):].strip()[1:-1].strip()
a1, a2 = [parse_ctype(x).typing() for x in magic_split(name)[:2]]
name = "Dict[{}, {}]".format(a1, a2)
for c in ("boost::tuples::tuple", "std::tuple"):
if name.startswith(c):
name = name[len(c):].strip()[1:-1].strip()
args = [parse_ctype(c).typing() for c in magic_split(name)]
args = [a for a in args if a != "boost::tuples::null_type"]
name = "Tuple[{}]".format(", ".join(args))
# Fix for function...
if name.startswith("std::function"):
name = name[13:].strip()[1:-1].strip()
rtype, args = parse_csig(name, "")
name = "Callable[[{}], {}]".format(
", ".join(a.type.typing() for a in args),
rtype.typing()
)
logger.info("Fixed {} to {}. ".format(pname, name))
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 be a C++
reference. This is mainly used to report errors when pointers to such type are present
in the interface. """
return t.name in ["bool", "int", "float", "str", "list", "std", "dict", "bytes"]
def typing(self) -> str:
""" Returns a valid typing representation for this type. """
name = self.name
if self.is_none():
return "None"
if name in CType.STANDARD_TYPES:
name = CType.STANDARD_TYPES[name]
if name in CType.REPLACEMENTS:
name = CType.REPLACEMENTS[name]
if name in CType.REPLACEMENTS_WARN:
logger.warning("Replacing {} with {}.".format(name, CType.REPLACEMENTS_WARN[name]))
name = CType.REPLACEMENTS_WARN[name]
# If the name contains stuff that should not be there, try some
# "magic" conversion:
if self._is_not_valid(name):
name = self._try_fix(name)
if name in dir(mobase):
name = '"{}"'.format(name)
return name
def is_raw_pointer(self) -> bool:
""" Check if this type is a raw pointer type. """
return self.name.endswith("*")
def is_smart_pointer(self) -> bool:
""" Check if this type is a smart pointer type. """
for ptr in self.SMART_POINTERS:
if self.name.startswith(ptr):
return True
return False
def is_pointer(self) -> bool:
""" Check if this type corresponds to a pointer type. """
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). """
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:
return "CType({})".format(self.name)
def __repr__(self) -> str:
return str(self)
class Arg:
""" Class representing a function argument (type and eventual default value). """
# Constant representing None since None indicates no default value:
DEFAULT_NONE = "None"
def __init__(self, type: Type, value: Optional[str] = None):
self.type = type
self._value = value
@property
def value(self) -> Optional[str]:
value = self._value
if value is None:
return None
# Boost has a tendency to put `mobase.` in front of default values...
if value is not None and value.startswith("mobase."):
value = value[7:]
# I have issues with inner classes, so we need to prepend manually...
if value.find(".") != -1:
parts = value.split(".")
tvalue = ".".join(parts[:-1])
for k in MOBASE_REGISTER.objects:
if k != tvalue and k.endswith(tvalue) and k[-len(tvalue) - 1] == ".":
value = "{}.{}".format(k, parts[-1])
return value
def has_default_value(self) -> bool:
return self.value is not None
def __str__(self):
if self.has_default_value():
return "Arg({}={})".format(self.type, self.value)
return "Arg({})".format(self.type)
def __repr__(self):
return str(self)
def __hash__(self):
return hash(self.type)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Arg):
return NotImplemented
return self.type == other.type
class Function:
""" Class representing a function. """
def __init__(self, name: str, rtype: Type, args: List[Arg], has_overloads: bool = False):
self.name = name
self.rtype = rtype
self.args = args
self.overloads = has_overloads
def has_overloads(self):
return self.overloads
class Method(Function):
""" Class representing a method. """
def __init__(self, cls: str, name: str, rtype: Type,
args: List[Arg], static: bool, has_overloads: bool = False):
super().__init__(name, rtype, args, has_overloads)
self.class_name = cls
self.static = static
def is_static(self):
return self.static
def is_special(self):
return self.name.startswith("__")
def is_constructor(self):
return self.name == "__init__"
class Constant:
""" Class representing a constant. """
def __init__(self, name: str, type: Type, value: Any):
self.name = name
self.type = type
self.value = value
class Property:
""" Class representing a property. """
def __init__(self, name: str, type: Type, read_only: bool):
self.name = name
self.type = type
self.read_only = read_only
def is_read_only(self):
return self.read_only
class Class:
""" Class representing a class. """
def __init__(self, name: str, bases: List[str], methods: List[Method],
constants: List[Constant] = [], properties: List[Property] = [],
inner_classes: List["Class"] = []):
self.name = name
self.bases = bases
self.methods = methods
self.properties = properties
self.constants = constants
self.inner_classes = inner_classes
class Enum(Class):
""" 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
# of stubs, I think making them inherit enum.Enum is more appropriate:
super().__init__(
name, ["Enum"], [], inner_classes=[],
constants=[Constant(k, Type(name), v) for k, v in values.items()])
def parse_ctype(s: str) -> CType:
""" Parse a C++ type from the given string.
Args:
s: String to parse.
Returns: A C++ type parsed from the given string.
"""
# List of strings that can be removed from the names:
for d in ["__64", "__cdecl", "__ptr64", "{lvalue}", "class", "struct", "enum", "unsigned"]:
s = s.replace(d, "")
# Remove the namespace remaing:
for d in ["MOBase", "boost::python"]:
s = s.replace(d + "::", "")
# Specific replacement:
s = s.replace("__int64", "int")
s = s.replace(" const &", "")
s = s.replace("&", "")
return CType(s.strip())
def parse_carg(s: str, has_default: bool) -> Arg:
""" Parse the given C++ argument.
Args:
s: The string to parse.
has_default: Indicates if this argument as a default.
Returns: An argument parsed from the given string.
"""
v, d = s, None
if s.find("=") != -1:
v, d = [x.strip() for x in s.split("=")]
if d is None and has_default:
d = Arg.DEFAULT_NONE
return Arg(parse_ctype(v), d)
def parse_csig(s, name) -> Tuple[CType, List[Arg]]:
""" Parse a boost::python C++ signature.
Args:
s: The signature to parse.
name: Name of the function, or "" if the signature correspond to a type.
Returns: (RType, Args) where RType is a CType object, and Args is a list of Arg objects
containing CType.
"""
# Remove the [ and ] which specifies default arguments but are useless since
# we already have = to tell us - The replacement is weird because the way boost
# present these is weird, and to avoid breaking default argument such as = []:
c = s.count("[,")
s = s.replace("[,", ",")
s = s.replace("]" * c, "")
# Split return type/arguments:
if name:
rtype_s, args_s = s.split(name)
# Remove the ( and ).
args_s = args_s.strip()[1:-1]
else:
rtype_s, args_s = magic_split(s, "(", "(<", ")>")
# Only remove the last ) because the first one is removed by magic_split:
args_s = args_s.strip()[:-1]
# Parse return type:
rtype = parse_ctype(rtype_s.strip())
# Parse arguments:
# Strip spaces and remove the first and last ():
args_s = args_s.strip()
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
def parse_psig(s: str, name: str) -> Tuple[Type, List[Arg]]:
""" Parse a boost::python python signature.
Args:
s: The signature to parse.
name: Name of the function.
Returns: (RType, Args) where RType is a Type object, and Args is a list of Arg objects
containing Type.
"""
c = s.count("[,")
s = s.replace("[,", ",")
s = s.replace("]" * c, "")
# This is pretty brutal way of extracting stuff... But most things can be
# retrieve from the C++ signature, here we are mainly interested in extracting
# the python type if possible:
m: re.Match[str] = re.search(r"{}\((.*)\)\s*->\s*([^\s]+)\s*:".format(name), s) # type: ignore
pargs = []
args = list(filter(bool, m.group(1).strip().split(",")))
for i, pa in enumerate(args):
pa = pa.strip()
t = pa[1:pa.find(")")]
d: Optional[str] = None
if pa.find("=") != -1:
d = pa.split("=")[-1].strip()
elif i > len(args) - c - 1:
d = Arg.DEFAULT_NONE
pargs.append(Arg(Type(t), d))
return Type(m.group(2)), pargs
def find_best_type(ptype: Type, ctype: CType) -> Type:
""" Find the best type from the given python and C++ type.
Args:
ptype: The python type.
ctype: The C++ type.
Returns: The best of the two types.
"""
if ptype.name == ctype.name:
return ptype
elif ptype.is_none() and ctype.is_none():
return ptype
assert ptype.is_none() == ctype.is_none()
MOBASE_REGISTER.register_type(ptype, ctype)
if ptype.is_object():
if ctype.is_object():
return ptype
return ctype
# Returned pointer are treated differently because they can often be null:
if ctype.is_pointer():
return ctype
return ptype
def find_best_value(pvalue: str, cvalue: str) -> str:
""" Find the best value (default value) from the given python and C++ one.
WARNING: This currently always return pvalue and only warns the user if
the two values are not identical.
Args:
pvalue: Python default value.
cvalue: C++ default value.
Returns: The best of the two values.
"""
if pvalue != cvalue:
logger.warning("Mismatch default value: {} {}.".format(pvalue, cvalue))
return pvalue
def is_enum(e: type) -> bool:
""" Check if the given class is an enumeration.
Args:
e: The class object to check.
Returns: True if the object is an enumeration (boost::python enumeration, not python)
False otherwize.
"""
# Yet to find a better way...
return any(
"{}.{}".format(c.__module__, c.__name__) == "Boost.Python.enum"
for c in inspect.getmro(e))
def make_enum(name: str, e: type) -> Enum:
""" Construct a Enum object from the given class.
Args:
name: Fully qualified name of the enumeration.
e: The class representing a boost::python enumeration.
Returns: An Enum object representing the given enumeration.
"""
# All boost enums have a .values attributes:
return Enum(e.__name__, OrderedDict((e.values[k].name, k) for k in sorted(e.values.keys()))) # type: ignore
class Overload:
""" Small class to avoid mypy issues... """
rtype: Type
args: List[Arg]
def __init__(self, rtype, args):
self.rtype = rtype
self.args = args
def parse_bpy_function_docstring(e) -> List[Overload]:
""" Parse the docstring of the given element.
Args:
e: The function to "parse".
Returns: A list of overloads for the given function, where each overload is
a dictionary with a "rtype" entry containing the return type and a "args"
entry containing the list of arguments.
"""
lines = e.__doc__.split("\n")
# Find the various overloads:
so = [i for i, line in enumerate(lines) if line.strip().startswith(e.__name__)]
so.append(len(lines))
# We are going to parse the python and C++ signature, and try to merge
# them...
overloads: List[Overload] = []
for i, j in zip(so[:-1], so[1:]):
psig = lines[i].strip()
for k in range(i, j):
if lines[k].strip().startswith("C++ signature"):
csig = lines[k + 1].strip()
prtype, pargs = parse_psig(psig, e.__name__)
crtype, cargs = parse_csig(csig, e.__name__)
# Currently there is no way to automatically check so we add [optional]
# in the doc:
if e.__doc__.find("[optional]") != -1:
crtype._optional = True
assert len(pargs) == len(cargs)
# Now we need to find the "best" type from both signatures:
rtype = find_best_type(prtype, crtype)
args = []
for parg, carg in zip(pargs, cargs):
args.append(Arg(find_best_type(parg.type, carg.type), # type: ignore
find_best_value(parg.value, carg.value))) # type: ignore
overloads.append(Overload(rtype=rtype, args=args))
return overloads
def make_class(name: str, e: type, register: "MobaseRegister") -> Class:
""" Constructs a Class objecgt from the given python class.
Args:
name: Name of the class (might be different from __name__ for inner classes).
e: The python class (created from boost) to construct an object for.
class_register:
Returns: A Class object corresponding to the given class.
"""
base_classes_s: List[str] = []
# Kind of ugly, but...:
for c in inspect.getmro(e):
if c != e and c.__module__ == "mobase":
base_classes_s.append(c.__name__)
if c.__module__ == "Boost.Python":
break
# Keep as a comment but this is/should be fixed in the actual C++ code:
# Lots of class exposed do not inherit IPlugin while they should:
# if "IPlugin" not in base_classes_s and e.__name__.startswith("IPlugin") and e.__name__ != "IPlugin":
# base_classes_s.append("IPlugin")
# This contains ALL the parent classes, not the direct ones:
base_classes = [register.make_object(name) for name in base_classes_s]
# Retrieve all the attributes... The hasattr is required but I don't know why:
all_attrs = [(n, getattr(e, n)) for n in dir(e) if hasattr(e, n)]
# Some exclusions:
EXCLUDED_MEMBERS = [
"__weakref__", "__dict__", "__doc__", "__instance_size__", "__module__", "__getattr__"]
all_attrs = [
a for a in all_attrs
# Using getattr() here since some attribute do not have name (e.g. constants):
if a[0] not in EXCLUDED_MEMBERS]
# Fetch all attributes from the base classes:
base_attrs: Dict[str, List[Union[Constant, Property, Method, Class]]] = defaultdict(list)
for bc in base_classes:
# Thanks mypy for the naming...
for a1 in bc.constants:
base_attrs[a1.name].append(a1)
for a2 in bc.methods:
base_attrs[a2.name].append(a2)
for a3 in bc.properties:
base_attrs[a3.name].append(a3)
for a4 in bc.inner_classes:
base_attrs[a4.name].append(a4)
# Find the methods:
methods = [m[1] for m in all_attrs if callable(m[1])]
methods = sorted(methods, key=lambda m: m.__name__)
# Filter out methods not provided or implemented:
methods = [
m for m in methods if m.__doc__ is not None and m.__doc__.find("C++ signature") != -1]
# List of methods that must return bool:
BOOL_METHODS = ["__eq__", "__lt__", "__le__", "__ne__", "__gt__", "__ge__"]
pmethods = []
for method in methods:
if method.__doc__ is None:
continue
overloads = parse_bpy_function_docstring(method)
# __eq__ must accept an object in python, so we need to add an overload:
if method.__name__ in ["__eq__", "__ne__"]:
overloads.append(Overload(
rtype=Type("bool"),
args=[Arg(Type(e.__name__)), Arg(Type("object"))]))
cmethods = []
for ovld in overloads:
args = ovld.args
# This is a very heuristic way of checking if the method is static but I did
# not find anything better yet...
static = False
if len(args) == 0:
static = True
elif method.__name__.startswith("__"): # Special method cannot be static
static = False
else:
arg0_name = args[0].type.name
if arg0_name in MOBASE_REGISTER.cpp2py:
arg0_name = MOBASE_REGISTER.cpp2py[arg0_name].name
arg0_name = arg0_name.replace("*", "").replace("&", "").replace("const", "").strip()
static = arg0_name not in [e.__name__, e.__name__ + "Wrapper"] + base_classes_s
pmethod = Method(
e.__name__, method.__name__, ovld.rtype, ovld.args,
static=static, has_overloads=len(overloads) > 1)
if method.__name__ in BOOL_METHODS:
pmethod.rtype = Type("bool")
cmethods.append(pmethod)
pmethods.extend(cmethods)
# Retrieve the enumerations and classes:
inner_classes = [
ic[1] for ic in all_attrs if isinstance(ic[1], type) and ic[1].__name__ != "class" and ic[0] not in base_attrs]
pinner_classes = [register.make_object("{}.{}".format(name, ic.__name__), ic) for ic in inner_classes]
# Retrieve the attributes:
constants = []
properties = []
for name, attr in all_attrs:
if callable(attr) or isinstance(attr, type):
continue
# Maybe we should check an override here (e.g., different value for a constant):
if name in base_attrs:
continue
if isinstance(attr, property):
properties.append(Property(name, Type("Any"), attr.fset is None))
elif not hasattr(attr, "__name__"):
constants.append(Constant(name, Type(type(attr).__name__), attr))
direct_bases_s = []
for c in e.__bases__:
if c.__module__ != "Boost.Python":
direct_bases_s.append(c.__name__)
# Forcing QWidget base for XWidget classes since these do not show up
# and we use a trick:
if e.__name__.endswith("Widget"):
logger.info("Forcing base {} for class {}.".format("PyQt5.QtWidgets.QWidget", e.__name__))
direct_bases_s.append("PyQt5.QtWidgets.QWidget")
return Class(e.__name__, direct_bases_s, pmethods, inner_classes=pinner_classes,
properties=properties, constants=constants)
def clean_class(cls: Class):
""" Clean the given class object.
Args:
cls: The class object to clean.
"""
# Note: This currently only remove duplicates that are added by wrapping
# C++ classes with boost::python::wrapper.
methods: Dict[Tuple[str, Tuple[Arg, ...]], List[Method]] = OrderedDict()
methods_by_name = defaultdict(list)
for m in cls.methods:
k = (m.name, tuple(m.args[1:]))
if k not in methods:
methods[k] = []
methods[k].append(m)
methods_by_name[m.name].append(m)
clean_methods: List[Method] = []
for name, args in methods:
ms = methods[name, args]
method: Method = ms[0]
if len(ms) > 1:
# If we have more than two methods, there is a problem...
assert len(methods[name, args]) == 2
assert ms[0].rtype.is_none() or ms[1].rtype.is_none()
if ms[0].rtype.is_none():
method = ms[1]
else:
method = ms[0]
# If those were the only two, we need to remove the overload:
if len(methods_by_name[name]) == 2:
method.overloads = False
# Filter methods from parent class:
if method.is_static():
clean_methods.append(method)
else:
arg0_name = method.args[0].type.name
if arg0_name in MOBASE_REGISTER.cpp2py:
arg0_name = MOBASE_REGISTER.cpp2py[arg0_name].name
if arg0_name in [cls.name, "object"]:
clean_methods.append(method)
else:
logger.info("Removing {}({}) from {} (already in base {}).".format(
name, ", ".join(a.type.typing() for a in args), cls.name, method.args[0].type.typing()))
# We need to filter-out __eq__(X, object) and __ne__(X, object) because these won't be filtered since
# the first arg is not of the right type:
for name in ("__eq__", "__ne__"):
fns = [m for m in clean_methods if m.name == name]
if len(fns) == 1 and fns[0].args[1].type.is_object():
clean_methods.remove(fns[0])
cls.methods = clean_methods
def patch_class(cls: Class, ow: Dict[str, Any]):
""" Patch the given class using the given overwrites.
The following overwrites are supported:
- For each method `m` in `cls`, if `ow` contains a key corresponding
to `m.name`, the method is overriden by `ow[m.name]` (i.e., the
value must be a `Method` object):
- If `ow` contains `"[properties]"` key, it should contain a dictionary
from property name to property type (as str).
Examples:
```
{
# Specify an override for the __iter__ method (overrides existing one):
"__iter__": Method(
"IFileTree", # Name of the class (must be specified).
"__iter__", # Name of the method.
Type("Iterator[FileTreeEntry]"), # Return type of the method (instance of Type).
[
Arg(Type("")) # List of arguments (list of Arg object).
],
False, # Indicates if the method is static.
False # Indicates if the method has overloads.
),
# Specify property:
"[properties]": {
"files": "List[str]" # Specify that the type of the property "files" is a list of string.
}
}
```
Args:
cls: The class to patch.
ow: The overwrite dictionary.
"""
for i, m in enumerate(cls.methods):
if m.name in ow:
cls.methods[i] = ow[m.name]
properties: Dict[str, str] = ow.get("[properties]", {})
for prop in cls.properties:
if prop.name in properties:
prop.type = Type(properties[prop.name])
def print_function(fn: Function, indent: str = ""):
""" Print the given Function object at the given indentation level. """
if fn.has_overloads():
print("{}@overload".format(indent))
srtype = ""
if not fn.rtype.is_none():
srtype = " -> " + fn.rtype.typing()
largs = []
for i, arg in enumerate(fn.args):
tmp = "arg{}: {}".format(i, arg.type.typing())
if arg.has_default_value():
tmp += " = {}".format(arg.value)
largs.append(tmp)
if isinstance(fn, Method):
if fn.is_static():
print("{}@staticmethod".format(indent))
else:
largs[0] = "self"
sargs = ", ".join(largs)
print("{}def {}({}){}: pass".format(indent, fn.name, sargs, srtype))
def print_property(prop: Property, indent: str):
""" Print the given Property object at the given indentation level. """
print("{}@property".format(indent))
print("{}def {}(self) -> {}: pass".format(indent, prop.name, prop.type.typing()))
if not prop.is_read_only():
print("{}@{}.setter".format(indent, prop.name))
print("{}def {}(self, arg0: {}): pass".format(indent, prop.name, prop.type.typing()))
print()
def print_class(cls: Class, indent: str = ""):
""" Print the given Class object at the given indentation level. """
bc = ""
if cls.bases:
bc = "(" + ", ".join(cls.bases) + ")"
# Class declaration:
print("{}class {}{}:".format(indent, cls.name, bc))
# Inner classes:
for iclass in cls.inner_classes:
print_class(iclass, indent=indent + " ")
print()
# Constants:
for constant in cls.constants:
value = constant.value
if isinstance(value, int):
value = int(value)
print("{}{} = {}".format(indent + " ", constant.name, value))
if cls.constants and (cls.properties or cls.methods):
print()
# Properties:
for prop in cls.properties:
print_property(prop, indent=indent + " ")
# Methods:
# Put __init__ first, then special, then by name:
methods = sorted(cls.methods, key=lambda m: (m.name != "__init__", not m.is_special(), m.name))
for method in methods:
print_function(method, indent=indent + " ")
if cls.methods:
print()
# MAIN CODE:
IMPORTS = [
("enum", ["Enum"]),
("typing", ["Dict", "Iterable", "Iterator", "List", "Tuple", "Union", "Any", "Optional", "Callable", "overload"]),
"PyQt5.QtCore",
"PyQt5.QtGui",
"PyQt5.QtWidgets"
]
for imp in IMPORTS:
if isinstance(imp, str):
print("import {}".format(imp))
else:
print("from {} import {}".format(imp[0], ", ".join(imp[1])))
print()
# This is a class to represent interface not implemented:
print("class InterfaceNotImplemented: pass")
print()
# Names that should not be in the stubs (I don't even know why this name
# is actually exposed... ):
BAD_NAMES = ["toPyQt"]
# Those are overwrites, because yeah...
OVERWRITES: Dict[str, Dict[str, Any]] = {
"IFileTree": {
"__iter__": Method(
"IFileTree", "__iter__",
Type("Iterator[FileTreeEntry]"),
[Arg(Type(""))],
False, False)
},
"FileInfo": {
"[properties]": {
"filePath": "str",
"archive": "str",
"origins": "List[str]"
}
},
"Mapping": {
"[properties]": {
"source": "str",
"destination": "str",
"isDirectory": "bool",
"createTarget": "bool"
}
}
}
# List of objects:
objects = []
for name in dir(mobase):
if name.startswith("__"):
continue
if name in BAD_NAMES:
continue
# if name not in ["GuessedString"]:
# continue
objects.append((name, getattr(mobase, name)))
# Enum first, and then alphabetical. Might cause issue with base classes, so
# maybe create a kind of dependency...
# For argument or return types, this should not be an issue since we quote
# everything from mobase.
objects = sorted(objects, key=lambda e: (not is_enum(e[1]), e[0]))
for n, o in objects:
MOBASE_REGISTER.add_object(n, o)
for n, o in objects:
# Create the corresponding object:
c = MOBASE_REGISTER.make_object(n, o)
# Clean the class (e.g., remove duplicates methods due to wrappers):
clean_class(c)
# Fix the class if required:
if c.name in OVERWRITES:
ow = OVERWRITES[c.name]
patch_class(c, ow)
# Print the class:
print_class(c)
if isinstance(c, Enum):
print()
print()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment