Coverage for src / lilbee / __main__.py: 100%

44 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-06-28 01:01 +0000

1"""Allow running as `python -m lilbee`.""" 

2 

3from __future__ import annotations 

4 

5import importlib 

6import os 

7import sys 

8from pathlib import Path 

9 

10_FLATPAK_INFO = Path("/.flatpak-info") # present in every Flatpak sandbox 

11 

12 

13def _isolate_vendored_openssl() -> None: 

14 """Default ``OPENSSL_CONF`` to the empty config inside Flatpak sandboxes. 

15 

16 Flatpak's freedesktop runtime ships an openssl.cnf whose engine section 

17 dlopens engine modules built against the runtime's own OpenSSL. Bundled 

18 wheels that vendor a static OpenSSL (pyarrow's libarrow) honor that 

19 config during import-time init, load the ABI-incompatible engine, and 

20 segfault before any lilbee code runs. An empty config keeps every 

21 vendored OpenSSL self-contained; certificate paths are unaffected. 

22 Scoped to Flatpak sandboxes so every other install keeps reading the 

23 host config, and an explicitly set ``OPENSSL_CONF`` always wins. 

24 Deliberately not gated on a frozen-binary check: Nuitka does not set 

25 ``sys.frozen``, and a pip install run inside a sandbox crashes the same 

26 way. 

27 """ 

28 if sys.platform != "linux" or not _FLATPAK_INFO.exists(): 

29 return 

30 os.environ.setdefault("OPENSSL_CONF", os.devnull) 

31 

32 

33def _is_frozen() -> bool: 

34 """True when running from a frozen build. 

35 

36 A deliberate local copy of :func:`lilbee._frozen.is_frozen`: the dispatch 

37 path below must import nothing from the ``lilbee`` package (the splash 

38 subprocess this intercepts is stdlib-only and must stay light), so it 

39 cannot import the shared helper. PyInstaller/cx_Freeze set ``sys.frozen``; 

40 Nuitka never does, but injects a ``__compiled__`` global into every module 

41 it compiles. Both must be checked so the dispatchers fire in the shipped 

42 Nuitka onefile binary, not just under PyInstaller. 

43 """ 

44 return bool(sys.__dict__.get("frozen")) or "__compiled__" in globals() 

45 

46 

47def _multiprocessing_child_code(argv: list[str]) -> str | None: 

48 """Return the ``-c`` payload if *argv* is a multiprocessing child reinvocation. 

49 

50 Python's ``multiprocessing.resource_tracker`` respawns ``sys.executable`` 

51 with interpreter flags followed by ``-c "<code>"`` (e.g. 

52 ``-B -s -E -c "from multiprocessing.resource_tracker import main;main(7)"``). 

53 In a frozen build (Nuitka onefile) ``sys.executable`` is this exe, so those 

54 interpreter flags leak into the typer parser and raise ``No such option: -B`` 

55 (or, once typer has eaten the flags, ``No such command '<N>'`` for the 

56 leftover fd number). The stdlib ``freeze_support()`` hook only covers spawn 

57 children (``--multiprocessing-fork``), not resource_tracker. 

58 

59 Require the payload to mention ``multiprocessing`` or ``spawn_main`` so a 

60 stray ``lilbee -c …`` typed by a human never reaches ``exec``. 

61 """ 

62 try: 

63 idx = argv.index("-c") 

64 except ValueError: 

65 return None 

66 if idx + 1 >= len(argv): 

67 return None 

68 code = argv[idx + 1] 

69 if "multiprocessing" not in code and "spawn_main" not in code: 

70 return None 

71 return code 

72 

73 

74def _dispatch_frozen_child() -> bool: 

75 """Exec a multiprocessing child payload and return True when handled.""" 

76 if not _is_frozen(): 

77 return False 

78 code = _multiprocessing_child_code(sys.argv) 

79 if code is None: 

80 return False 

81 exec( # noqa: S102 payload is emitted by Python's own stdlib into sys.executable 

82 compile(code, "<frozen-mp-child>", "exec"), 

83 {"__name__": "__main__", "__builtins__": __builtins__}, 

84 ) 

85 return True 

86 

87 

88_DASH_M_MIN_ARGV = 3 # [bin, "-m", "lilbee.<module>"] 

89 

90 

91def _dispatch_module_invocation() -> bool: 

92 """Run a `python -m lilbee.<module>` reinvocation and return True when handled. 

93 

94 Internal subprocesses spawned via ``[sys.executable, "-m", "lilbee.X", ...]`` 

95 (e.g. ``splash.start`` launching ``lilbee.runtime._splash_runner``) hit 

96 typer's `--model -m` short-form parser in a frozen build. Detect the 

97 pattern, run the target module's ``main()`` directly, never reach typer. 

98 

99 Imports the module and calls ``main()`` rather than ``runpy.run_module``: 

100 Nuitka's module loader does not implement ``get_code``, so ``runpy`` raises 

101 ``AttributeError: ... has no attribute 'get_code'`` inside the onefile 

102 binary. Every ``lilbee.*`` module reinvoked this way exposes a ``main()`` 

103 entry point. Restricted to the ``lilbee.*`` namespace so a stray 

104 ``lilbee -m foo`` typed by a user can't reach exec. 

105 """ 

106 if not _is_frozen(): 

107 return False 

108 if len(sys.argv) < _DASH_M_MIN_ARGV or sys.argv[1] != "-m": 

109 return False 

110 module_name = sys.argv[2] 

111 if not module_name.startswith("lilbee."): 

112 return False 

113 sys.argv = [module_name, *sys.argv[3:]] 

114 module = importlib.import_module(module_name) 

115 module.main() 

116 return True 

117 

118 

119if __name__ == "__main__": # pragma: no cover - process entry glue; logic is unit-tested above 

120 # Must run before anything that can initialize OpenSSL (pyarrow import, 

121 # ssl), including the multiprocessing child payloads dispatched below. 

122 _isolate_vendored_openssl() 

123 

124 # Make the frozen exe a valid subprocess target for multiprocessing's 

125 # sys.executable reinvocations, BEFORE any import that could pull typer. 

126 if _dispatch_module_invocation(): 

127 sys.exit(0) 

128 if _dispatch_frozen_child(): 

129 sys.exit(0) 

130 

131 import multiprocessing 

132 

133 multiprocessing.freeze_support() 

134 

135 from lilbee.runtime.launcher import main 

136 

137 main()