Re-organizer code into a proper package.

This commit is contained in:
Mikaël Capelle
2022-05-05 13:37:42 +02:00
parent 2e4fd7c1a8
commit 4f24d363ec
12 changed files with 242 additions and 196 deletions
+2 -1
View File
@@ -2,9 +2,10 @@
.mypy_cache
__pycache__
.vscode
**/*.egg-info
# The 'bin/' directory:
bin
docs/build
docs/mobase.py
docs/source/api
docs/source/api
-190
View File
@@ -1,190 +0,0 @@
# -*- encoding: utf-8 -*-
import argparse
import logging
from pathlib import Path
from typing import cast
import black
import isort
from generator import LOGGER
from generator.loader import load_mobase
from generator.mtypes import Class, Function, PyType
from generator.parser import is_enum
from generator.register import MOBASE_REGISTER
from generator.utils import Settings, clean_class
from generator.writer import Writer
parser = argparse.ArgumentParser("Stubs generator for the MO2 python interface")
parser.add_argument(
"install_dir",
metavar="INSTALL_DIR",
type=Path,
default=None,
help="installation directory of Mod Organizer 2",
)
parser.add_argument(
"-o",
"--output",
type=Path,
default="stubs/setup/mobase-stubs/__init__.pyi",
help="output file (default stubs/setup/mobase-stubs/__init__.pyi)",
)
parser.add_argument(
"-v", "--verbose", action="store_true", help="verbose mode (all logs go to stderr)"
)
parser.add_argument(
"-c",
"--config",
type=argparse.FileType("r"),
default=None,
help="configuration file",
)
args = parser.parse_args()
logging.basicConfig()
LOGGER.setLevel(logging.WARNING)
if args.verbose:
LOGGER.setLevel(logging.INFO)
output_path = cast(Path, args.output)
# Load settings from the configuration:
settings: Settings = Settings(register=MOBASE_REGISTER)
if args.config is not None:
settings = Settings(MOBASE_REGISTER, args.config)
# Parse mobase:
# Load mobase (cannot simply do "import mobase"):
mobase = load_mobase(Path(args.install_dir))
# List of objects:
objects = []
for name in dir(mobase):
if name.startswith("__"):
continue
if name in settings.ignore_names:
continue
# we do not want the real MoVariant
if name == "MoVariant":
continue
# ignore the private module
if name == "private":
continue
# for now, ignore this since it is a submodule and we
# not handle them
if name == "widgets":
continue
# IPlugin is not the real object
if name == "IPlugin":
continue
objects.append((name, getattr(mobase, name)))
# enum first, and then alphabetical, should be fine with the __future__ import
objects = sorted(
objects, key=lambda e: (isinstance(e[1], type), not is_enum(e[1]), e[0])
)
for n, o in objects:
MOBASE_REGISTER.add_object(n, o)
# Process everything:
for n, o in objects:
# Create the corresponding object:
c = MOBASE_REGISTER.make_object(n, o)
if isinstance(c, Class):
# Clean the class (e.g., remove duplicates methods due to wrappers):
clean_class(c, settings)
# Path the class using the configuration:
settings.patch_class(c)
elif isinstance(c, list) and isinstance(c[0], Function):
settings.patch_functions(c)
else:
LOGGER.critical(
"Cannot generated stubs for {}, unsupported object type.".format(n)
)
# Write everything:
with open(args.output, "w") as output:
writer = Writer(output, settings)
# the __future__ import must be at the beginning
writer.print_imports([("__future__", ["annotations"])])
writer.print_version(settings.mobase["__version__"]) # type: ignore
writer.print_imports(
[
"abc",
("enum", ["Enum"]),
("pathlib", ["Path"]),
(
"typing",
[
"Dict",
"Iterator",
"List",
"Tuple",
"Union",
"Any",
"Optional",
"Callable",
"overload",
"Sequence",
"Set",
"TypeVar",
"Type",
],
),
"PyQt6.QtCore",
"PyQt6.QtGui",
"PyQt6.QtWidgets",
]
)
# Needs to define the MVariant and GameFeatureType type:
writer._print(f"MoVariant = {PyType.MO_VARIANT}")
writer._print(f"FileWrapper = {PyType.FILE_WRAPPER}")
writer._print(f"DirectoryWrapper = {PyType.DIRECTORY_WRAPPER}")
writer._print('GameFeatureType = TypeVar("GameFeatureType")')
writer._print()
# This is a class to represent interface not implemented:
writer.print_class(Class("InterfaceNotImplemented", [], []))
writer._print()
for n, o in objects:
# Get the corresponding object:
c = MOBASE_REGISTER.get_object(n)
if isinstance(c, Class):
writer.print_class(c)
elif isinstance(c, list) and isinstance(c[0], Function):
for fn in c:
writer.print_function(fn)
black.format_file_in_place(
output_path,
fast=False,
mode=black.Mode(is_pyi=args.output.name.endswith("pyi")),
write_back=black.WriteBack.YES,
)
isort.api.sort_file(output_path)
+4 -4
View File
@@ -34,7 +34,7 @@ deps =
types-PyYAML
isort
commands =
black generator main.py --check --diff
flake8 generator main.py
mypy generator main.py
isort -c generator main.py
black src --check --diff
flake8 src
mypy src
isort -c src
+35
View File
@@ -0,0 +1,35 @@
# -*- encoding: utf-8 -*-
from setuptools import find_namespace_packages, setup
install_requires = ["black", "isort"]
dev_requires = [
"black",
"flake8-black",
"flake8",
"types-chardet",
]
setup(
name="mo2-stubs-generator",
version="1.0.0",
package_dir={"": "src"},
packages=find_namespace_packages(where="src", include=["mo2.*"]),
author="Holt59",
author_email="capelle.mikael@gmail",
description="Python stubs generator for mobase (MO2 Python API).",
long_description=open("README.md").read(),
url="https://github.com/ModOrganizer2/pystubs-generation",
classifiers=[
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
],
license="MIT",
install_requires=install_requires,
extras_require={"dev": dev_requires},
entry_points={
"console_scripts": ["mo2-stubs-generator=mo2.stubs.generator.__main__:main"],
},
)
+200
View File
@@ -0,0 +1,200 @@
# -*- encoding: utf-8 -*-
import argparse
import logging
from pathlib import Path
from typing import cast
import black
import isort
from . import LOGGER
from .loader import load_mobase
from .mtypes import Class, Function, PyType
from .parser import is_enum
from .register import MOBASE_REGISTER
from .utils import Settings, clean_class
from .writer import Writer
def main():
parser = argparse.ArgumentParser("Stubs generator for the MO2 python interface")
parser.add_argument(
"install_dir",
metavar="INSTALL_DIR",
type=Path,
default=None,
help="installation directory of Mod Organizer 2",
)
parser.add_argument(
"-o",
"--output",
type=Path,
default="stubs/setup/mobase-stubs/__init__.pyi",
help="output file (default stubs/setup/mobase-stubs/__init__.pyi)",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="verbose mode (all logs go to stderr)",
)
parser.add_argument(
"-c",
"--config",
type=argparse.FileType("r"),
default=None,
help="configuration file",
)
args = parser.parse_args()
logging.basicConfig()
LOGGER.setLevel(logging.WARNING)
if args.verbose:
LOGGER.setLevel(logging.INFO)
output_path = cast(Path, args.output)
# Load settings from the configuration:
settings: Settings = Settings(register=MOBASE_REGISTER)
if args.config is not None:
settings = Settings(MOBASE_REGISTER, args.config)
# Parse mobase:
# Load mobase (cannot simply do "import mobase"):
mobase = load_mobase(Path(args.install_dir))
# List of objects:
objects = []
for name in dir(mobase):
if name.startswith("__"):
continue
if name in settings.ignore_names:
continue
# we do not want the real MoVariant
if name == "MoVariant":
continue
# ignore the private module
if name == "private":
continue
# for now, ignore this since it is a submodule and we
# not handle them
if name == "widgets":
continue
# IPlugin is not the real object
if name == "IPlugin":
continue
objects.append((name, getattr(mobase, name)))
# enum first, and then alphabetical, should be fine with the __future__ import
objects = sorted(
objects, key=lambda e: (isinstance(e[1], type), not is_enum(e[1]), e[0])
)
for n, o in objects:
MOBASE_REGISTER.add_object(n, o)
# Process everything:
for n, o in objects:
# Create the corresponding object:
c = MOBASE_REGISTER.make_object(n, o)
if isinstance(c, Class):
# Clean the class (e.g., remove duplicates methods due to wrappers):
clean_class(c, settings)
# Path the class using the configuration:
settings.patch_class(c)
elif isinstance(c, list) and isinstance(c[0], Function):
settings.patch_functions(c)
else:
LOGGER.critical(
"Cannot generated stubs for {}, unsupported object type.".format(n)
)
# Write everything:
with open(args.output, "w") as output:
writer = Writer(output, settings)
# the __future__ import must be at the beginning
writer.print_imports([("__future__", ["annotations"])])
writer.print_version(settings.mobase["__version__"]) # type: ignore
writer.print_imports(
[
"abc",
("enum", ["Enum"]),
("pathlib", ["Path"]),
(
"typing",
[
"Dict",
"Iterator",
"List",
"Tuple",
"Union",
"Any",
"Optional",
"Callable",
"overload",
"Sequence",
"Set",
"TypeVar",
"Type",
],
),
"PyQt6.QtCore",
"PyQt6.QtGui",
"PyQt6.QtWidgets",
]
)
# Needs to define the MVariant and GameFeatureType type:
writer._print(f"MoVariant = {PyType.MO_VARIANT}")
writer._print(f"FileWrapper = {PyType.FILE_WRAPPER}")
writer._print(f"DirectoryWrapper = {PyType.DIRECTORY_WRAPPER}")
writer._print('GameFeatureType = TypeVar("GameFeatureType")')
writer._print()
# This is a class to represent interface not implemented:
writer.print_class(Class("InterfaceNotImplemented", [], []))
writer._print()
for n, o in objects:
# Get the corresponding object:
c = MOBASE_REGISTER.get_object(n)
if isinstance(c, Class):
writer.print_class(c)
elif isinstance(c, list) and isinstance(c[0], Function):
for fn in c:
writer.print_function(fn)
black.format_file_in_place(
output_path,
fast=False,
mode=black.Mode(is_pyi=args.output.name.endswith("pyi")),
write_back=black.WriteBack.YES,
)
isort.api.sort_file(output_path)
if __name__ == "__main__":
main()
@@ -33,7 +33,7 @@ def load_mobase(path: Path):
# We need to add plugins/data to sys.path, mainly for PyQt6
sys.path.insert(1, path.joinpath("plugins", "plugin_python", "libs").as_posix())
import mobase
import mobase # type: ignore
return mobase