Skip to content

Core: refactor some loading mechanisms #1753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 19, 2023
26 changes: 19 additions & 7 deletions Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import argparse
import itertools
import logging
import shlex
import subprocess
import sys
Expand Down Expand Up @@ -54,7 +55,7 @@ def open_patch():
except Exception as e:
messagebox('Error', str(e), error=True)
else:
file, _, component = identify(filename)
file, component = identify(filename)
if file and component:
launch([*get_exe(component), file], component.cli)

Expand Down Expand Up @@ -95,11 +96,13 @@ def open_folder(folder_path):

def identify(path: Union[None, str]):
if path is None:
return None, None, None
return None, None
for component in components:
if component.handles_file(path):
return path, component.script_name, component
return (None, None, None) if '/' in path or '\\' in path else (None, path, None)
return path, component
elif path == component.display_name or path == component.script_name:
return None, component
return None, None


def get_exe(component: Union[str, Component]) -> Optional[Sequence[str]]:
Expand Down Expand Up @@ -221,23 +224,32 @@ def component_action(button):
Launcher().run()


def run_component(component: Component, *args):
if component.func:
component.func(*args)
elif component.script_name:
subprocess.run([*get_exe(component.script_name), *args])
else:
logging.warning(f"Component {component} does not appear to be executable.")


def main(args: Optional[Union[argparse.Namespace, dict]] = None):
if isinstance(args, argparse.Namespace):
args = {k: v for k, v in args._get_kwargs()}
elif not args:
args = {}

if "Patch|Game|Component" in args:
file, component, _ = identify(args["Patch|Game|Component"])
file, component = identify(args["Patch|Game|Component"])
if file:
args['file'] = file
if component:
args['component'] = component

if 'file' in args:
subprocess.run([*get_exe(args['component']), args['file'], *args['args']])
run_component(args["component"], args["file"], *args["args"])
elif 'component' in args:
subprocess.run([*get_exe(args['component']), *args['args']])
run_component(args["component"], *args["args"])
else:
run_gui()

Expand Down
73 changes: 43 additions & 30 deletions worlds/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,50 @@ class DataPackage(typing.TypedDict):
class WorldSource(typing.NamedTuple):
path: str # typically relative path from this module
is_zip: bool = False
relative: bool = True # relative to regular world import folder

def __repr__(self):
return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip})"
return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})"

@property
def resolved_path(self) -> str:
if self.relative:
return os.path.join(folder, self.path)
return self.path

def load(self) -> bool:
try:
if self.is_zip:
importer = zipimport.zipimporter(self.resolved_path)
if hasattr(importer, "find_spec"): # new in Python 3.10
spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
mod = importlib.util.module_from_spec(spec)
else: # TODO: remove with 3.8 support
mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0])

mod.__package__ = f"worlds.{mod.__package__}"
mod.__name__ = f"worlds.{mod.__name__}"
sys.modules[mod.__name__] = mod
with warnings.catch_warnings():
warnings.filterwarnings("ignore", message="__package__ != __spec__.parent")
# Found no equivalent for < 3.10
if hasattr(importer, "exec_module"):
importer.exec_module(mod)
else:
importlib.import_module(f".{self.path}", "worlds")
return True

except Exception as e:
# A single world failing can still mean enough is working for the user, log and carry on
import traceback
import io
file_like = io.StringIO()
print(f"Could not load world {self}:", file=file_like)
traceback.print_exc(file=file_like)
file_like.seek(0)
import logging
logging.exception(file_like.read())
return False


# find potential world containers, currently folders and zip-importable .apworld's
Expand All @@ -58,35 +99,7 @@ def __repr__(self):
# import all submodules to trigger AutoWorldRegister
world_sources.sort()
for world_source in world_sources:
try:
if world_source.is_zip:
importer = zipimport.zipimporter(os.path.join(folder, world_source.path))
if hasattr(importer, "find_spec"): # new in Python 3.10
spec = importer.find_spec(world_source.path.split(".", 1)[0])
mod = importlib.util.module_from_spec(spec)
else: # TODO: remove with 3.8 support
mod = importer.load_module(world_source.path.split(".", 1)[0])

mod.__package__ = f"worlds.{mod.__package__}"
mod.__name__ = f"worlds.{mod.__name__}"
sys.modules[mod.__name__] = mod
with warnings.catch_warnings():
warnings.filterwarnings("ignore", message="__package__ != __spec__.parent")
# Found no equivalent for < 3.10
if hasattr(importer, "exec_module"):
importer.exec_module(mod)
else:
importlib.import_module(f".{world_source.path}", "worlds")
except Exception as e:
# A single world failing can still mean enough is working for the user, log and carry on
import traceback
import io
file_like = io.StringIO()
print(f"Could not load world {world_source}:", file=file_like)
traceback.print_exc(file=file_like)
file_like.seek(0)
import logging
logging.exception(file_like.read())
world_source.load()

lookup_any_item_id_to_name = {}
lookup_any_location_id_to_name = {}
Expand Down