Compare commits

...
16 Commits
29 changed files with 17048 additions and 253 deletions
@@ -15,15 +15,16 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.11
python-version: 3.12
- uses: abatilo/actions-poetry@v2
- name: Install
run: |
poetry install
run: poetry install
- name: Install libgl1
run: sudo apt install -y libgl1 libegl1 libglib2.0-0 libxkbcommon0 libdbus-1-3
- name: Copy stubs
run: cp stubs/2.5.0/mobase-stubs/__init__.pyi docs/mobase.py
run: |
mkdir -p docs/src
cp -r stubs/2.5.2/mobase-stubs docs/src/mobase
- name: Build
run: poetry run sphinx-build -b html docs/source docs/build
env:
+2 -6
View File
@@ -10,15 +10,11 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.11
python-version: 3.12
- uses: abatilo/actions-poetry@v2
- name: Install
run: |
poetry install
- name: Lint
run: |
poetry run black src --check --diff
poetry run isort -c src
poetry run mypy src
poetry run ruff src
poetry run pyright src
poetry run poe lint
+3 -3
View File
@@ -22,7 +22,7 @@ jobs:
replace-with: "$1"
- uses: actions/setup-python@v2
with:
python-version: 3.11
python-version: 3.12
- uses: abatilo/actions-poetry@v2
- name: Build
run: |
@@ -34,7 +34,7 @@ jobs:
poetry version ${TAG#v}
poetry build
- name: Store the distribution packages
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v7
with:
name: python-package-distributions
path: stubs/setup/dist/
@@ -49,7 +49,7 @@ jobs:
steps:
- name: Download all the dists
uses: actions/download-artifact@v3
uses: actions/download-artifact@v7
with:
name: python-package-distributions
path: dist/
-1
View File
@@ -109,7 +109,6 @@ import mobase
import mobase.widgets
```
**Note:** Most classes in `mobase` cannot be instantiated, so this is mostly intended
for MO2 developers.
+574 -53
View File
File diff suppressed because it is too large Load Diff
-4
View File
@@ -1,4 +0,0 @@
sphinx-rtd-theme
sphinx-autodoc-typehints
sphinx-automodapi
PyQt6
+5 -1
View File
@@ -27,8 +27,9 @@ extensions = [
"sphinx.ext.autodoc",
"sphinx_autodoc_typehints",
"sphinx.ext.napoleon",
"autoapi.extension",
# "sphinx.ext.autosummary",
"sphinx_automodapi.automodapi",
# "sphinx_automodapi.automodapi",
]
# Add any paths that contain templates here, relative to this directory.
@@ -39,6 +40,9 @@ templates_path = ["_templates"]
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
autoapi_dirs = ["../src"]
autoapi_member_order = "groupwise"
# -- Options for HTML output -------------------------------------------------
-1
View File
@@ -18,7 +18,6 @@ This documentation is dedicated to writting MO2 **Python** plugins.
plugin-types
writing-plugins
faq
mobase
-11
View File
@@ -1,11 +0,0 @@
``mobase`` API
==============
.. currentmodule:: mobase
.. automodapi:: mobase
:no-inheritance-diagram:
:no-heading:
.. toctree::
:maxdepth: 4
-7
View File
@@ -1,7 +0,0 @@
mobase
======
.. toctree::
:maxdepth: 4
mobase
+3 -5
View File
@@ -64,10 +64,8 @@ These plugins (shall eventually) implement all the game specific features and fu
able to add support for further games.
The plugin is also responsible to help MO determine if (and where) the game is installed in the first place.
Since supporting a game properly requires extensions in many places of the UI.
To allow this without creating one huge plugin interface that involves every aspect of MO, game plugins
expose a *feature list*.
The list of possible features can be found in the "game_features" project and each feature can itself be
considered a plugin interface.
To allow this without creating one huge plugin interface that involves every aspect of MO,
game plugins can register only the features they need to MO2 using :meth:`registerFeature<mobase.IGameFeatures.registerFeature>`
As an example for a game feature take BSA invalidation: If the game requires BSA invalidation it will implement
this feature.
@@ -147,4 +145,4 @@ File Mappings
This interface allows plugins to add virtual file (or directory) links to the virtual file system in addition to the
mod files.
Profile-local save games, ini-files and load-orders are all implemented this way in MO2.
Profile-local save games, ini-files and load-orders are all implemented this way in MO2.
Generated
+892
View File
File diff suppressed because it is too large Load Diff
+39 -44
View File
@@ -1,61 +1,56 @@
[tool.poetry]
[project]
name = "mo2-pystubs-generation"
version = "0.1.0"
description = ""
authors = ["Holt59 <capelle.mikael@gmail.com>"]
license = "MIT"
version = "0.1.0"
readme = "README.md"
packages = [{ include = "mo2", from = "src" }]
authors = [{ name = "Holt59", email = "capelle.mikael@gmail.com" }]
requires-python = ">=3.12,<4.0"
dependencies = [
"pyqt6 (==6.11.0)",
"pyyaml (>=6.0.3,<7.0.0)",
"typing-extensions (>=4.15.0,<5.0.0)",
]
[tool.poetry.scripts]
[project.scripts]
mo2-stubs-generator = "mo2.stubs.generator.__main__:main"
[tool.poetry.dependencies]
python = "^3.11"
pyqt6 = "^6.5.2"
pyyaml = "^6.0.1"
[tool.poetry.group.dev.dependencies]
black = "^23.9.1"
mypy = "^1.5.1"
pyright = "^1.1.327"
isort = "^5.12.0"
ruff = "^0.0.290"
flake8 = "^6.1.0"
flake8-black = "^0.3.6"
flake8-pyproject = "^1.2.3"
types-pyyaml = "^6.0.12.11"
[tool.poetry.group.doc.dependencies]
sphinx-rtd-theme = "^1.3.0"
sphinx-autodoc-typehints = "^1.24.0"
sphinx-automodapi = "^0.16.0"
sphinx = "^7.2.6"
[build-system]
requires = ["poetry-core"]
requires = ['poetry-core (>=2.0,<3.0)']
build-backend = "poetry.core.masonry.api"
[tool.flake8]
max-line-length = 88
extend-ignore = ["E203"]
[tool.poetry]
packages = [{ include = "mo2", from = "src" }]
[tool.isort]
profile = "black"
multi_line_output = 3
[tool.poetry.group.dev.dependencies]
pyright = "^1.1.409"
ruff = "^0.15.12"
types-pyyaml = "^6.0.12.20260508"
poethepoet = "^0.45.0"
[tool.poetry.group.doc.dependencies]
sphinx-rtd-theme = "^3.0.2"
sphinx-autodoc-typehints = "^3.2.0"
sphinx-automodapi = "^0.20.0"
sphinx = "^8.2.3"
sphinx-autoapi = "^3.6.0"
[tool.poe.tasks]
format-imports = "ruff check --select I src --fix"
format-ruff = "ruff format src"
format.sequence = ["format-imports", "format-ruff"]
lint-ruff = "ruff check src"
lint-ruff-format = "ruff format --check src"
lint-pyright = "pyright src"
lint.sequence = ["lint-ruff", "lint-ruff-format", "lint-pyright"]
lint.ignore_fail = "return_non_zero"
[tool.ruff]
line-length = 88
target-version = "py311"
target-version = "py312"
[tool.mypy]
warn_return_any = true
warn_unused_configs = true
namespace_packages = true
[tool.ruff.lint]
extend-select = ["B", "Q", "I"]
[tool.pyright]
# reportMissingTypeStubs = true
# reportUntypedBaseClass = false
typeCheckingMode = "strict"
reportMissingTypeStubs = true
+46 -24
View File
@@ -1,25 +1,26 @@
import argparse
import inspect
import logging
import subprocess
import types
from collections.abc import Sequence
from pathlib import Path
from typing import Callable
import black
import isort
from .loader import load_mobase
from .mtypes import Class, PyTyping
from .mtypes import Class, Constant, Enum, Function, PyTyping
from .parser import is_enum
from .register import MobaseRegister
from .utils import Settings, clean_class
from .writer import Writer, is_list_of_functions
from .writer import Writer, is_list_of
LOGGER = logging.getLogger(__package__)
def extract_objects(module: object, skips: list[str] = []) -> list[tuple[str, type]]:
objects: list[tuple[str, type]] = []
def extract_objects(
module: object, skips: Sequence[str] = []
) -> list[tuple[str, object]]:
objects: list[tuple[str, object]] = []
assert hasattr(module, "__name__")
module_name: str = module.__name__ # type: ignore
@@ -56,12 +57,9 @@ def add_mobase_header(writer: Writer):
"Callable",
"Dict",
"Iterator",
"List",
"Optional",
"overload",
"Sequence",
"Set",
"Tuple",
"Type",
"TypeVar",
"Union",
@@ -142,7 +140,7 @@ def main() -> None:
}
# list of objects directly in mobase
module_objects: dict[str, list[tuple[str, type]]] = {
module_objects: dict[str, list[tuple[str, object]]] = {
"mobase": extract_objects(
mobase,
[
@@ -150,7 +148,7 @@ def main() -> None:
"IPlugin",
],
),
"mobase.widgets": extract_objects(getattr(mobase, "widgets")),
"mobase.widgets": extract_objects(mobase.widgets), # type: ignore
}
for name, objects in module_objects.items():
@@ -180,10 +178,10 @@ def main() -> None:
# Path the class using the configuration:
settings.patch_class(c)
elif isinstance(c, PyTyping):
elif isinstance(c, (PyTyping, Constant)):
...
elif is_list_of_functions(c):
elif is_list_of(c, Function):
settings.patch_functions(c)
else:
@@ -200,6 +198,21 @@ def main() -> None:
# create directory if required
output_folder.mkdir(parents=True, exist_ok=True)
# sort the stubs
def _key_fn(o: Class | Constant | list[Function] | PyTyping) -> tuple[int, ...]:
# order is PyTyping -> Constant -> Function -> Enum -> Top-Level Class -> Child Level Classes
return (
not isinstance(o, PyTyping),
not isinstance(o, Constant),
not isinstance(o, list),
not isinstance(o, Enum),
isinstance(o, Class) and len(o.all_bases),
)
stub_objects = sorted(
(register.get_object(n) for n, _o in objects), key=_key_fn
)
# write everything
with open(output_folder.joinpath("__init__.pyi"), "w") as output:
writer = Writer(package=name, output=output, settings=settings)
@@ -210,19 +223,28 @@ def main() -> None:
module_headers[name](writer)
for n, o in objects:
# Get the corresponding object:
c = register.get_object(n)
for c in stub_objects:
writer.print_object(c)
black.format_file_in_place(
output_folder.joinpath("__init__.pyi"),
fast=False,
mode=black.Mode(is_pyi=True),
write_back=black.WriteBack.YES,
subprocess.run(
[
"ruff",
"format",
"--silent",
output_folder.joinpath("__init__.pyi").as_posix(),
]
)
subprocess.run(
[
"ruff",
"check",
"--silent",
"--select",
"I",
"--fix",
output_folder.joinpath("__init__.pyi").as_posix(),
]
)
isort.api.sort_file(output_folder.joinpath("__init__.pyi"))
if __name__ == "__main__":
+11 -13
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import re
from collections.abc import Sequence
from typing import Final, TypeVar
@@ -164,7 +165,6 @@ class Argument:
class Exception:
"""
Small class representing exception that can be raised from functions.
"""
@@ -313,20 +313,20 @@ class Class:
self,
package: str,
name: str,
bases: list[Class],
methods: list[Method],
constants: list[Constant] = [],
properties: list[Property] = [],
inner_classes: list[Class] = [],
bases: Sequence[Class],
methods: Sequence[Method],
constants: Sequence[Constant] = [],
properties: Sequence[Property] = [],
inner_classes: Sequence[Class] = [],
doc: str = "",
):
self.package = package
self.name = name
self.bases = bases
self.methods = methods
self.properties = properties
self.constants = constants
self.inner_classes = inner_classes
self.bases = list(bases)
self.methods = list(methods)
self.properties = list(properties)
self.constants = list(constants)
self.inner_classes = list(inner_classes)
self.doc = ""
self.abstract = False
self.outer_class = None
@@ -385,7 +385,6 @@ class Class:
class PyClass(Class):
"""
Class use to wrap Python class to be used as parent class for some classes
in mobase.
@@ -401,7 +400,6 @@ class PyClass(Class):
class Enum(Class):
"""
Class representing an enum.
"""
+24 -18
View File
@@ -4,7 +4,7 @@ import re
import types
from collections import OrderedDict, defaultdict
from itertools import chain
from typing import Any, Iterable, cast
from typing import Any, Callable, Iterable, cast
from .mtypes import (
Argument,
@@ -108,14 +108,17 @@ def parse_python_signature(s: str, name: str) -> tuple[PyType, list[Argument]]:
raise ValueError(f"invalid argument: {pa}, {s}")
matches = m.groupdict()
arguments.append(
Argument(matches["name"], PyType(matches["type"]), matches["value"])
)
type_ = matches["type"]
if matches["value"] == "None":
if "None" not in type_ and "MoVariant" not in type_:
type_ = type_ + " | None"
arguments.append(Argument(matches["name"], PyType(type_), matches["value"]))
return PyType(return_type), arguments
def is_enum(e: type) -> bool:
def is_enum(e: object) -> bool:
"""Check if the given class is an enumeration.
Args:
@@ -131,7 +134,6 @@ def is_enum(e: type) -> bool:
class Overload:
"""Small class to avoid mypy issues..."""
return_type: PyType
@@ -142,17 +144,18 @@ class Overload:
self.arguments = arguments
def parse_pybind11_function_docstring(e: type) -> list[Overload]:
def parse_pybind11_function_docstring(name: str, doc: str | None) -> list[Overload]:
"""
Parse the docstring of the given element.
Parse the docstring of a Pybind11 function.
Args:
e: The function to "parse".
name: Name of the function.
doc: The docstring of the function generated by Pybind11.
Returns:
A list of overloads for the given function.
"""
lines = (e.__doc__ or "").strip().split("\n")
lines = (doc or "").strip().split("\n")
signatures: list[str]
if len(lines) == 1:
@@ -160,7 +163,7 @@ def parse_pybind11_function_docstring(e: type) -> list[Overload]:
else:
signatures = []
for line in lines:
m = re.match(rf"^[0-9]+[.]\s+({e.__name__}.*)$", line)
m = re.match(rf"^[0-9]+[.]\s+({name}.*)$", line)
if m:
signatures.append(m.group(1).strip())
@@ -172,16 +175,16 @@ def parse_pybind11_function_docstring(e: type) -> list[Overload]:
signature = signature.replace("MOBase::", "mobase.").replace("::", ".")
try:
return_type, arguments = parse_python_signature(signature, e.__name__)
except ValueError:
raise ValueError(f"invalid signature: {e.__name__}, {e.__doc__}")
return_type, arguments = parse_python_signature(signature, name)
except ValueError as err:
raise ValueError(f"invalid signature: {name}, {doc}") from err
overloads.append(Overload(return_type=return_type, arguments=arguments))
return overloads
def make_functions(e: type) -> list[Function]:
overloads = parse_pybind11_function_docstring(e)
def make_functions(e: Callable[..., Any]) -> list[Function]:
overloads = parse_pybind11_function_docstring(e.__name__, e.__doc__)
return [
Function(
@@ -218,7 +221,8 @@ def make_class(e: type, register: MobaseRegister) -> Class:
# This contains ALL the parent classes, not the direct ones:
base_classes: list[Class] = [
register.make_object(name) for name in base_classes_s # type: ignore
register.make_object(name)
for name in base_classes_s # type: ignore
]
# retrieve all the attributes that are not in a base class
@@ -292,7 +296,9 @@ def make_class(e: type, register: MobaseRegister) -> Class:
# otherwise we parse the docstring
else:
overloads = parse_pybind11_function_docstring(method)
overloads = parse_pybind11_function_docstring(
method.__name__, method.__doc__
)
for overload in overloads:
args = overload.arguments
+11 -11
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
from collections import OrderedDict
from .mtypes import Class, Function, PyTyping
from .mtypes import Class, Constant, Function, PyType, PyTyping
class MobaseRegister:
@@ -12,20 +12,20 @@ class MobaseRegister:
Class that register classes.
"""
objects: dict[str, Class | list[Function] | PyTyping]
objects: dict[str, Class | Constant | list[Function] | PyTyping]
def __init__(self) -> None:
self.raw_objects: dict[str, type] = OrderedDict()
self.raw_objects: dict[str, object] = OrderedDict()
self.objects = {}
def add_object(self, name: str, object: type) -> None:
self.raw_objects[name] = object
def add_object(self, n: str, o: object, /) -> None:
self.raw_objects[n] = o
def make_object(
self, name: str, e: type | None = None
) -> Class | list[Function] | PyTyping:
self, name: str, e: object | None = None
) -> Class | list[Function] | Constant | PyTyping:
"""
Construct a Function, Class or Enum for the given object.
Construct a Function, Class, Constant or Enum for the given object.
Args:
name: The name of the object to inspect.
@@ -47,14 +47,14 @@ class MobaseRegister:
self.objects[name] = make_class(e, self)
elif callable(e):
self.objects[name] = make_functions(e)
# typing stuff
elif type(e).__module__ == "types" or type(e).__module__ == "typing":
self.objects[name] = PyTyping(name, e)
else:
self.objects[name] = Constant(name, type=PyType(type(e)), value=e)
return self.objects[name]
def get_object(self, name: str) -> Class | list[Function] | PyTyping:
def get_object(self, name: str) -> Class | Constant | list[Function] | PyTyping:
"""
Retrieve the object if the given name. Fails if no object with this
name exists (if `make_object(name, ...)` has never been called).
+13 -11
View File
@@ -215,12 +215,14 @@ class Settings:
# Check the args:
if function_settings.args is not None:
if len(function_settings.args) != len(fn.args):
LOGGER.warn(
LOGGER.warning(
f"Mismatch number of arguments for function "
f"mobase.{setting_name}."
)
for setting_arg, method_arg in zip(function_settings.args, fn.args):
for setting_arg, method_arg in zip(
function_settings.args, fn.args, strict=True
):
method_arg.doc = setting_arg.doc
if not setting_arg.type.is_none():
method_arg.type = setting_arg.type
@@ -241,7 +243,7 @@ class Settings:
fn.deprecated = function_settings.deprecated
else:
LOGGER.warn(
LOGGER.warning(
"Missing settings for function mobase.{}.".format(setting_name)
)
@@ -266,7 +268,7 @@ class Settings:
class_settings = self._get_class_settings(cls.canonical_name)
if class_settings is None:
LOGGER.warn("Class {} not found in settings.".format(cls.canonical_name))
LOGGER.warning("Class {} not found in settings.".format(cls.canonical_name))
return
if "__doc__" in class_settings and class_settings["__doc__"] is not None:
@@ -304,7 +306,7 @@ class Settings:
if "type" in settings_property:
prop.type = PyType(settings_property["type"])
else:
LOGGER.warn(
LOGGER.warning(
"Missing type for property {}.{}.".format(
cls.canonical_name, prop.name
)
@@ -318,7 +320,7 @@ class Settings:
prop.doc = settings_property["desc"]
else:
LOGGER.warn(
LOGGER.warning(
"Missing description for property {}.{}.".format(
cls.canonical_name, prop.name
)
@@ -364,21 +366,21 @@ class Settings:
if function_settings.args is not None:
method_arguments = m.args if m.is_static() else m.args[1:]
if len(function_settings.args) != len(method_arguments):
LOGGER.warn(
LOGGER.warning(
"Mismatch number of arguments for method {}.{}.".format(
cls.canonical_name, settings_name
)
)
for settings_arg, method_arg in zip(
function_settings.args, method_arguments
function_settings.args, method_arguments, strict=True
):
method_arg.doc = settings_arg.doc
if (
not method_arg.name.startswith("arg")
and method_arg.name != settings_arg.name
):
LOGGER.warn(
LOGGER.warning(
(
"Mismatch argument name for method {}.{}: "
"{} {}, using {}."
@@ -427,7 +429,7 @@ class Settings:
if n_overloads > 0 and missing_settings:
for settings_name in missing_settings:
LOGGER.warn(
LOGGER.warning(
"Missing settings for method {}.{}.".format(
cls.canonical_name, settings_name
)
@@ -445,7 +447,7 @@ class Settings:
# Print items missing in mobase
missing_items = [k for k, v in keys.items() if not v]
if missing_items:
LOGGER.warn(
LOGGER.warning(
"The following members were found in settings but not in the actual"
" class {}: {}.".format(cls.canonical_name, ", ".join(missing_items))
)
+24 -9
View File
@@ -1,16 +1,20 @@
import logging
from typing import Any, Iterable, TextIO, TypeGuard
from typing import Any, TextIO
from .mtypes import Class, Enum, Function, Method, Property, PyTyping
from typing_extensions import TypeIs
from .mtypes import Class, Constant, Enum, Function, Method, Property, PyTyping
from .utils import Settings
LOGGER = logging.getLogger(__package__)
def is_list_of_functions(e: Any | Iterable[Any]) -> TypeGuard[list[Function]]:
if not isinstance(e, list):
return False
return all(isinstance(x, Function) for x in e)
def is_list_of_any(e: Any) -> TypeIs[list[Any]]:
return isinstance(e, list)
def is_list_of[T](e: Any, t: type[T]) -> TypeIs[list[T]]:
return is_list_of_any(e) and all(isinstance(x, t) for x in e)
class Writer:
@@ -163,7 +167,7 @@ class Writer:
if not prop.is_read_only():
self._print("{}@{}.setter".format(indent, prop.name))
self._print(
"{}def {}(self, arg0: {}): ...".format(
"{}def {}(self, arg0: {}) -> None: ...".format(
indent, prop.name, self._fix_typing(prop.type.typing())
)
)
@@ -256,13 +260,24 @@ class Writer:
def print_typing(self, typ: PyTyping):
self._print(f"{typ.name} = {typ.typing}")
def print_object(self, e: object):
def print_constent(self, constant: Constant):
assert constant.type is not None
self._print(
"{}: {} = ...".format(
constant.name, self._fix_typing(constant.type.typing())
)
)
def print_object(self, e: Class | Constant | list[Function] | PyTyping):
if isinstance(e, Class):
self.print_class(e)
elif is_list_of_functions(e):
elif is_list_of(e, Function):
for fn in e:
self.print_function(fn)
elif isinstance(e, PyTyping):
self.print_typing(e)
else:
self.print_constent(e)
+56 -21
View File
@@ -1639,7 +1639,7 @@ class IModList:
"""
...
def allModsByProfilePriority(
self: IModList, profile: IProfile = None
self: IModList, profile: IProfile | None = None
) -> Sequence[str]:
"""
Returns:
@@ -2141,19 +2141,43 @@ class IOrganizer:
The (absolute) path to the mods directory.
"""
...
@overload
def onAboutToRun(
self: IOrganizer, callback: Callable[[str, PyQt6.QtCore.QDir, str], bool]
) -> bool:
"""
Install a new handler to be called when an application is about to run.
Multiple handlers can be installed. If any of the handler returns `False`, the
application will not run.
Args:
callback: The function to call when an application is about to run. The function
receives the absolute path to the application to run, the working directory
for the run and a string containing the arguments passed to the executable.
The function can return False to prevent the application from running.
Returns:
True if the handler was installed properly (there are currently no
reasons for this to fail).
"""
...
@overload
def onAboutToRun(self: IOrganizer, callback: Callable[[str], bool]) -> bool:
"""
Install a new handler to be called when an application is about to run.
Multiple handlers can be installed. If any of the handler returns `False`, the application will
not run.
Multiple handlers can be installed. If any of the handler returns `False`, the
application will not run.
Args:
callback: The function to call when an application is about to run. The parameter is the absolute path
to the application to run. The function can return False to prevent the application from running.
callback: The function to call when an application is about to run. The parameter
is the absolute path to the application to run. The function can return False
to prevent the application from running.
Returns:
True if the handler was installed properly (there are currently no reasons for this to fail).
True if the handler was installed properly (there are currently no reasons for
this to fail).
"""
...
def onFinishedRun(self: IOrganizer, callback: Callable[[str, int], None]) -> bool:
@@ -2165,7 +2189,25 @@ class IOrganizer:
path to the application, and the second parameter is the exit code of the application.
Returns:
True if the handler was installed properly (there are currently no reasons for this to fail).
True if the handler was installed properly (there are currently no reasons for
this to fail).
"""
...
def onNextRefresh(
self: IOrganizer,
callback: Callable[[], None],
immediate_if_possible: bool = True,
) -> bool:
"""
Install a new handler to be called on the next refresh or immediately.
Args:
callback: Function to call on the next refresh (or immediately).
immediate_if_possible: If True, immediately run the callback if no refresh is currently running.
Returns:
True if the handler was installed properly (there are currently no reasons for
this to fail).
"""
...
@overload
@@ -2719,14 +2761,12 @@ class IPluginGame(IPlugin):
"""
def __init__(self: IPluginGame) -> None: ...
@abc.abstractmethod
def CCPlugins(self: IPluginGame) -> Sequence[str]:
"""
Returns:
The current list of active Creation Club plugins.
"""
...
@abc.abstractmethod
def DLCPlugins(self: IPluginGame) -> Sequence[str]:
"""
Returns:
@@ -2773,6 +2813,12 @@ class IPluginGame(IPlugin):
The directory of the documents folder where configuration files and such for this game reside.
"""
...
def enabledPlugins(self: IPluginGame) -> Sequence[str]:
"""
Returns:
A list of plugins enabled by the game but not in a strict load order.
"""
...
@abc.abstractmethod
def executableForcedLoads(
self: IPluginGame,
@@ -2782,7 +2828,6 @@ class IPluginGame(IPlugin):
A list of automatically discovered libraries that can be force loaded with executables.
"""
...
@abc.abstractmethod
def executables(self: IPluginGame) -> Sequence[ExecutableInfo]:
"""
Returns:
@@ -2834,7 +2879,6 @@ class IPluginGame(IPlugin):
The name of the game (as displayed to the user).
"""
...
@abc.abstractmethod
def gameNexusName(self: IPluginGame) -> str:
"""
Returns:
@@ -2848,7 +2892,6 @@ class IPluginGame(IPlugin):
The short name of the game.
"""
...
@abc.abstractmethod
def gameVariants(self: IPluginGame) -> Sequence[str]:
"""
Retrieve the list of variants for this game.
@@ -2883,7 +2926,6 @@ class IPluginGame(IPlugin):
An URL for the support page of this game.
"""
...
@abc.abstractmethod
def iniFiles(self: IPluginGame) -> Sequence[str]:
"""
Returns:
@@ -2928,7 +2970,6 @@ class IPluginGame(IPlugin):
The list of game saves in the given folder.
"""
...
@abc.abstractmethod
def loadOrderMechanism(self: IPluginGame) -> LoadOrderMechanism:
"""
Returns:
@@ -2958,7 +2999,6 @@ class IPluginGame(IPlugin):
The Nexus game ID for this game.
"""
...
@abc.abstractmethod
def nexusModOrganizerID(self: IPluginGame) -> int:
"""
Retrieve the Nexus mod ID of Mod Organizer for this game.
@@ -2970,14 +3010,12 @@ class IPluginGame(IPlugin):
The Nexus mod ID of Mod Organizer for this game.
"""
...
@abc.abstractmethod
def primaryPlugins(self: IPluginGame) -> Sequence[str]:
"""
Returns:
The list of plugins that are part of the game and not considered optional.
"""
...
@abc.abstractmethod
def primarySources(self: IPluginGame) -> Sequence[str]:
"""
Retrieve primary alternative 'short' names for this game.
@@ -2996,7 +3034,6 @@ class IPluginGame(IPlugin):
The directory where save games are stored.
"""
...
@abc.abstractmethod
def secondaryDataDirectories(self: IPluginGame) -> Dict[str, PyQt6.QtCore.QDir]:
"""
Retrieve the list of secondary data directories. Each directories should be
@@ -3032,14 +3069,12 @@ class IPluginGame(IPlugin):
variant: The game variant selected by the user.
"""
...
@abc.abstractmethod
def sortMechanism(self: IPluginGame) -> SortMechanism:
"""
Returns:
The sort mechanism for this game.
"""
...
@abc.abstractmethod
def steamAPPId(self: IPluginGame) -> str:
"""
Retrieve the Steam app ID for this game.
@@ -3818,7 +3853,7 @@ class ISaveGameInfoWidget(PyQt6.QtWidgets.QWidget):
"""
def __init__(
self: ISaveGameInfoWidget, parent: PyQt6.QtWidgets.QWidget = None
self: ISaveGameInfoWidget, parent: PyQt6.QtWidgets.QWidget | None = None
) -> None:
"""
Args:

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