Last active
May 10, 2020 16:03
-
-
Save Holt59/17a5fec03b57de83d90b09ca7f620823 to your computer and use it in GitHub Desktop.
Stubs file for ModOrganizer2 Python Interface - For 2.3.0a8 version.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- 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