python-script-launcher 0.0.10__py3-none-any.whl → 0.0.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -66,14 +66,20 @@ def cmd_run(args):
66
66
  LAUNCHER_FILES = ["app.py", "client.py", "tasks.py"]
67
67
  STATIC_DIR = "static"
68
68
  SHIM_PKG = "python_script_launcher"
69
+ # Files placed inside the staged `python_script_launcher/` package so the
70
+ # frozen exe supports both `from python_script_launcher import task` and
71
+ # `from python_script_launcher.tasks import task`.
72
+ SHIM_SUBMODULES = ("tasks.py",)
69
73
  SHIM_INIT = (
70
- '"'*3 + "Compatibility shim generated by launcher build.\n\n"
71
- "Allows user scripts to write ``from python_script_launcher import task``\n"
72
- "even after being packaged into a standalone exe. All names are forwarded\n"
73
- "from the top-level ``tasks`` module that ships alongside the launcher.\n"
74
+ '"'*3 + "Runtime shim generated by launcher build.\n\n"
75
+ "Re-exports the public API from ``python_script_launcher.tasks`` so both\n"
76
+ " from python_script_launcher import task\n"
77
+ " from python_script_launcher.tasks import task\n"
78
+ "work inside a frozen exe. The submodule is copied verbatim from the\n"
79
+ "installed launcher package.\n"
74
80
  + '"'*3 + "\n"
75
- "from tasks import * # noqa: F401,F403\n"
76
- "from tasks import task, run_cli, collect_tasks, TaskSpec, TaskParam # noqa: F401\n"
81
+ "from .tasks import * # noqa: F401,F403\n"
82
+ "from .tasks import task, run_cli, collect_tasks, TaskSpec, TaskParam # noqa: F401\n"
77
83
  )
78
84
 
79
85
 
@@ -89,12 +95,17 @@ def _stage_project(scripts_src: Path) -> Path:
89
95
  static_src = PACKAGE_DIR / STATIC_DIR
90
96
  if static_src.exists():
91
97
  shutil.copytree(static_src, tmp / STATIC_DIR)
92
- # Generate a lightweight `python_script_launcher` package shim so user
93
- # scripts using `from python_script_launcher import task` keep working
94
- # inside the frozen exe. The exe ships `tasks.py` at its root, and the
95
- # shim just re-exports its public names.
98
+ # Stage a real `python_script_launcher/` package next to the top-level
99
+ # runtime files. Copying `tasks.py` in as a submodule (plus a small
100
+ # re-export shim in __init__.py) makes both `from python_script_launcher
101
+ # import task` and `from python_script_launcher.tasks import task` work
102
+ # inside the frozen exe, matching what the wheel install exposes.
96
103
  shim_dir = tmp / SHIM_PKG
97
104
  shim_dir.mkdir(parents=True, exist_ok=True)
105
+ for name in SHIM_SUBMODULES:
106
+ src = PACKAGE_DIR / name
107
+ if src.exists():
108
+ shutil.copy2(src, shim_dir / name)
98
109
  (shim_dir / "__init__.py").write_text(SHIM_INIT, encoding="utf-8", newline="\n")
99
110
  # Copy user scripts into <tmp>/scripts so both scan_scripts and
100
111
  # PyInstaller's --add-data pick them up.
@@ -42,13 +42,35 @@ def _default_root():
42
42
  return p.parent if p.name == "scripts" else p
43
43
 
44
44
 
45
+ # The launcher package always ships a `static/` folder next to app.py
46
+ # (repo root during development, python_script_launcher/ after wheel
47
+ # install). Remember it so `run --scripts <dir>` and `build --scripts
48
+ # <dir>` can serve the frontend even when <dir> has no static/ of its own.
49
+ _LAUNCHER_DIR = Path(__file__).parent.resolve()
50
+
51
+
45
52
  def _default_static(root: Path):
53
+ """Resolve the default static directory.
54
+
55
+ Search order:
56
+ 1. sys._MEIPASS/static (PyInstaller frozen bundle),
57
+ 2. <launcher package>/static (source repo or wheel install),
58
+ 3. <user scripts root>/static (lets users override the UI).
59
+
60
+ Returns the first existing candidate, falling back to the last one
61
+ for backward compatibility when nothing is on disk yet.
62
+ """
63
+ candidates = []
46
64
  if getattr(sys, "frozen", False):
47
- meipass = Path(sys._MEIPASS)
48
- static = meipass / "static"
49
- if static.exists():
50
- return static
51
- return root / "static"
65
+ meipass = getattr(sys, "_MEIPASS", "")
66
+ if meipass:
67
+ candidates.append(Path(meipass) / "static")
68
+ candidates.append(_LAUNCHER_DIR / "static")
69
+ candidates.append(root / "static")
70
+ for c in candidates:
71
+ if c.exists():
72
+ return c
73
+ return candidates[-1]
52
74
 
53
75
 
54
76
  DEFAULT_ROOT = _default_root()
@@ -622,6 +644,7 @@ def create_app(
622
644
  candidates.append(Path(sys._MEIPASS) / "static" / "index.html")
623
645
  candidates.extend([
624
646
  static / "index.html",
647
+ _LAUNCHER_DIR / "static" / "index.html",
625
648
  project_root / "static" / "index.html",
626
649
  ])
627
650
  for f in candidates:
@@ -21,6 +21,7 @@ import os
21
21
  import shutil
22
22
  import subprocess
23
23
  import sys
24
+ import tempfile
24
25
  from importlib.metadata import distributions
25
26
  from pathlib import Path
26
27
 
@@ -37,6 +38,14 @@ SKIP_PACKAGES = {
37
38
  "python-script-launcher",
38
39
  }
39
40
 
41
+ # Files copied into the on-the-fly `python_script_launcher/` package so user
42
+ # scripts can `from python_script_launcher.tasks import task` inside the
43
+ # frozen exe (mirrors publish.py's wheel layout).
44
+ _STAGE_PACKAGE_FILES = (
45
+ "app.py", "client.py", "tasks.py", "__init__.py", "__main__.py",
46
+ )
47
+ _STAGE_PACKAGE_NAME = "python_script_launcher"
48
+
40
49
 
41
50
  def _norm(name: str) -> str:
42
51
  return (name or "").strip().lower().replace("_", "-")
@@ -75,6 +84,23 @@ def _clean_target(name: str, dist_dir: Path, build_dir: Path, project: Path,
75
84
  shutil.rmtree(p, ignore_errors=True)
76
85
 
77
86
 
87
+ def _stage_launcher_package(project: Path) -> Path:
88
+ """Materialize a temporary `python_script_launcher/` package next to the
89
+ top-level source files so PyInstaller can pick it up via `--paths`.
90
+
91
+ Returns the *root* directory to add to PyInstaller search paths; the
92
+ caller is responsible for removing it once the build is done.
93
+ """
94
+ stage_root = Path(tempfile.mkdtemp(prefix="psl-exe-"))
95
+ pkg_dir = stage_root / _STAGE_PACKAGE_NAME
96
+ pkg_dir.mkdir(parents=True, exist_ok=True)
97
+ for name in _STAGE_PACKAGE_FILES:
98
+ src = project / name
99
+ if src.exists():
100
+ shutil.copy2(src, pkg_dir / name)
101
+ return stage_root
102
+
103
+
78
104
  def build_exe(entry, name, *, project=None, dist=None, static=None,
79
105
  noconsole=False, onefile=False, clean=False):
80
106
  """Build a single PyInstaller exe.
@@ -128,31 +154,50 @@ def build_exe(entry, name, *, project=None, dist=None, static=None,
128
154
  if scripts_dir.exists():
129
155
  cmd += ["--add-data", f"{scripts_dir}{os.pathsep}scripts"]
130
156
 
131
- # Make `tasks` and the compatibility `python_script_launcher` shim
132
- # importable from user scripts inside the frozen exe. `--paths` lets
133
- # PyInstaller resolve them at build time; hidden-imports pull them into
134
- # the frozen PYZ.
135
- cmd += ["--paths", str(project)]
136
- if (project / "tasks.py").exists():
137
- cmd += ["--hidden-import", "tasks"]
157
+ # Make both `tasks` (top-level) and `python_script_launcher.tasks`
158
+ # (wheel-style dotted import) usable from user scripts inside the exe.
159
+ # - `--paths <project>` lets `from tasks import task` resolve.
160
+ # - We also stage a temporary `python_script_launcher/` package that
161
+ # mirrors publish.py wheel layout and add its parent to `--paths`, so
162
+ # `from python_script_launcher.tasks import task` resolves the same
163
+ # way it does when installed from the wheel.
164
+ # If the caller already staged a python_script_launcher/ package into
165
+ # `project` (e.g. via __main__.cmd_build), reuse it verbatim so we do
166
+ # not end up with two competing copies of the same package on --paths.
138
167
  if (project / "python_script_launcher" / "__init__.py").exists():
139
- cmd += ["--hidden-import", "python_script_launcher"]
140
-
141
- for pkg in get_installed_packages():
142
- cmd += ["--collect-all", pkg]
143
-
144
- if icon_arg:
145
- cmd += ["--icon", icon_arg]
146
-
147
- entry_path = Path(entry)
148
- if not entry_path.is_absolute():
149
- entry_path = (project / entry_path).resolve()
150
- cmd.append(str(entry_path))
151
-
152
- print(f" entry: {entry_path}")
153
- subprocess.run(cmd, check=True, cwd=str(project))
154
- out = f"{dist_dir}/{name}.exe" if onefile else f"{dist_dir}/{name}/{name}.exe"
155
- print(f" [OK] {name} -> {out}\n")
168
+ stage_root = None
169
+ else:
170
+ stage_root = _stage_launcher_package(project)
171
+ try:
172
+ cmd += ["--paths", str(project)]
173
+ if stage_root is not None:
174
+ cmd += ["--paths", str(stage_root)]
175
+ if (project / "tasks.py").exists():
176
+ cmd += ["--hidden-import", "tasks"]
177
+ cmd += [
178
+ "--hidden-import", _STAGE_PACKAGE_NAME,
179
+ "--hidden-import", f"{_STAGE_PACKAGE_NAME}.tasks",
180
+ "--collect-submodules", _STAGE_PACKAGE_NAME,
181
+ ]
182
+
183
+ for pkg in get_installed_packages():
184
+ cmd += ["--collect-all", pkg]
185
+
186
+ if icon_arg:
187
+ cmd += ["--icon", icon_arg]
188
+
189
+ entry_path = Path(entry)
190
+ if not entry_path.is_absolute():
191
+ entry_path = (project / entry_path).resolve()
192
+ cmd.append(str(entry_path))
193
+
194
+ print(f" entry: {entry_path}")
195
+ subprocess.run(cmd, check=True, cwd=str(project))
196
+ out = f"{dist_dir}/{name}.exe" if onefile else f"{dist_dir}/{name}/{name}.exe"
197
+ print(f" [OK] {name} -> {out}\n")
198
+ finally:
199
+ if stage_root is not None:
200
+ shutil.rmtree(stage_root, ignore_errors=True)
156
201
 
157
202
 
158
203
  def main():
@@ -186,4 +231,4 @@ def main():
186
231
 
187
232
 
188
233
  if __name__ == "__main__":
189
- main()
234
+ main()
@@ -1,4 +1,4 @@
1
- """多任务示例: @task 注册多个可运行函数, 无需 __main__ guard."""
1
+ """Multi-task demo using the wheel-style dotted import."""
2
2
  from tasks import task
3
3
 
4
4
 
@@ -21,4 +21,4 @@ def multiline(times: int = 3):
21
21
  for i in range(times):
22
22
  print(f"line {i}", flush=True)
23
23
  time.sleep(0.05)
24
- print("done")
24
+ print("done")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-script-launcher
3
- Version: 0.0.10
3
+ Version: 0.0.12
4
4
  Summary: Package Python scripts into standalone exe (Web + desktop client).
5
5
  License-Expression: MIT
6
6
  Keywords: pyinstaller,packaging,exe,launcher,scripts
@@ -1,16 +1,16 @@
1
1
  python_script_launcher/__init__.py,sha256=XeqJdWwu4-26nBlxeu2urarg3chsKMYYAWKgbUQnUyo,1750
2
- python_script_launcher/__main__.py,sha256=PiKbURpPYHkB8ZQ7YCSYPKh_YUi6R2FKJEjBDXdgm_c,6824
3
- python_script_launcher/app.py,sha256=3MBCyOtO5pFAcwyrkRkzg5a-GmPOOQ-jiRDEC261YZ4,24252
2
+ python_script_launcher/__main__.py,sha256=siqi1bFNpbuK2ed-fvJ-E1EcLg9KCurFN8dP3fvP7V8,7349
3
+ python_script_launcher/app.py,sha256=Qr5GAhqiHaKs3eZUkJvsyMInDIPGcJb0m8pJ-D1zuUY,25211
4
4
  python_script_launcher/client.py,sha256=pULe9xHv4OuXFAdiFAEHp4uxO8x4D_myyNX6ofqElMg,2964
5
- python_script_launcher/make_exe.py,sha256=zBLgUOOlhCzDOGVmmM0wnjpZK-a9jK0EGlVbgiNy6eI,6849
5
+ python_script_launcher/make_exe.py,sha256=4K8gHnfg7FGgD1WvHaxwp9yk1MhpWGtJOXHYpZgc0Ws,8826
6
6
  python_script_launcher/tasks.py,sha256=0f6P88vlUXscnIxXNL5pEQpmr6x-kavAHvDB9Z1qowE,5755
7
7
  python_script_launcher/scripts/file_status.py,sha256=D22FwHlsNnJkHz7ICyM3L2xK3vhNaMRl-aJEERrEG8s,1063
8
8
  python_script_launcher/scripts/hello.py,sha256=df5gR95rta5J0sDORI0wncCTBS0wGrTX07pEcR9R4_A,517
9
- python_script_launcher/scripts/multi.py,sha256=5TBxkaxF1xXtLhRkdQK1p4H5DD-mxwlptkQOpX8V9WE,617
9
+ python_script_launcher/scripts/multi.py,sha256=2HApGhrdt8Xs1WZ9HY7yHXAlabqc1KRC6CHqTUhjWy0,590
10
10
  python_script_launcher/scripts/test_modules.py,sha256=AFzAAC2NlYSVcPucNgChguLMEj8m1pmCIB3LIDthOf8,618
11
11
  python_script_launcher/static/index.html,sha256=_HTLklrRge6A09l2kb5ucFI4rrm2KMIEgU9xXbPJzT8,15000
12
- python_script_launcher-0.0.10.dist-info/METADATA,sha256=VhJNZXLM-ohC2PCGVgCgF4516pmUOpzVOaRtC-Bgt2M,6378
13
- python_script_launcher-0.0.10.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
14
- python_script_launcher-0.0.10.dist-info/entry_points.txt,sha256=13ENRmw4BsgWGLDCr_y1dKB91ed1j6rEyks6JDFzOns,66
15
- python_script_launcher-0.0.10.dist-info/top_level.txt,sha256=Ap6WryIb1YRnr_QVCmfGjdEVLDNA3dQcTl_-gxX11qk,23
16
- python_script_launcher-0.0.10.dist-info/RECORD,,
12
+ python_script_launcher-0.0.12.dist-info/METADATA,sha256=qBK3LK0GmI_r9v9Qnpxc3_J4KUDMVATVl2KaKjuRHd4,6378
13
+ python_script_launcher-0.0.12.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
14
+ python_script_launcher-0.0.12.dist-info/entry_points.txt,sha256=13ENRmw4BsgWGLDCr_y1dKB91ed1j6rEyks6JDFzOns,66
15
+ python_script_launcher-0.0.12.dist-info/top_level.txt,sha256=Ap6WryIb1YRnr_QVCmfGjdEVLDNA3dQcTl_-gxX11qk,23
16
+ python_script_launcher-0.0.12.dist-info/RECORD,,