polytool 0.2.0__tar.gz → 0.2.1__tar.gz

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.
Files changed (32) hide show
  1. {polytool-0.2.0 → polytool-0.2.1}/PKG-INFO +1 -1
  2. {polytool-0.2.0 → polytool-0.2.1}/pyproject.toml +3 -3
  3. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/__init__.py +1 -1
  4. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/__main__.py +2 -2
  5. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/__init__.py +19 -3
  6. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/img.py +2 -2
  7. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/shot.py +1 -1
  8. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/core/errors.py +27 -17
  9. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/core/ffmpeg.py +3 -1
  10. {polytool-0.2.0 → polytool-0.2.1}/.gitignore +0 -0
  11. {polytool-0.2.0 → polytool-0.2.1}/LICENSE +0 -0
  12. {polytool-0.2.0 → polytool-0.2.1}/README.md +0 -0
  13. {polytool-0.2.0 → polytool-0.2.1}/docs/README.md +0 -0
  14. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/clip.py +0 -0
  15. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/color.py +0 -0
  16. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/convert.py +0 -0
  17. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/cron.py +0 -0
  18. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/data.py +0 -0
  19. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/dl.py +0 -0
  20. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/enc.py +0 -0
  21. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/file.py +0 -0
  22. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/gen.py +0 -0
  23. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/net.py +0 -0
  24. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/pdf.py +0 -0
  25. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/qr.py +0 -0
  26. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/text.py +0 -0
  27. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/cli/vid.py +0 -0
  28. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/core/__init__.py +0 -0
  29. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/core/console.py +0 -0
  30. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/core/io.py +0 -0
  31. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/core/lazy.py +0 -0
  32. {polytool-0.2.0 → polytool-0.2.1}/src/polytool/core/progress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polytool
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: One-binary CLI bundling 26 everyday utilities — image/video/PDF conversion, background removal, OCR, QR codes, hashing, downloads, and more
5
5
  Project-URL: Homepage, https://github.com/k6w/polytool
6
6
  Project-URL: Repository, https://github.com/k6w/polytool
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "polytool"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "One-binary CLI bundling 26 everyday utilities — image/video/PDF conversion, background removal, OCR, QR codes, hashing, downloads, and more"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -76,8 +76,8 @@ dev = [
76
76
  ]
77
77
 
78
78
  [project.scripts]
79
- polytool = "polytool.cli:app"
80
- pt = "polytool.cli:app"
79
+ polytool = "polytool.cli:run"
80
+ pt = "polytool.cli:run"
81
81
 
82
82
  [project.urls]
83
83
  Homepage = "https://github.com/k6w/polytool"
@@ -1,4 +1,4 @@
1
1
  """polytool — one-binary CLI bundling 26 everyday utilities."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.1"
4
4
  __all__ = ["__version__"]
@@ -1,6 +1,6 @@
1
1
  """Allow `python -m polytool` invocation."""
2
2
 
3
- from polytool.cli import app
3
+ from polytool.cli import run
4
4
 
5
5
  if __name__ == "__main__":
6
- app()
6
+ run()
@@ -7,6 +7,8 @@ Heavy imports (Pillow, ffmpeg, rembg) happen inside command bodies via
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import sys
11
+
10
12
  import typer
11
13
 
12
14
  from polytool import __version__
@@ -28,9 +30,7 @@ from polytool.cli import (
28
30
  text,
29
31
  vid,
30
32
  )
31
- from polytool.core.errors import install_excepthook
32
-
33
- install_excepthook()
33
+ from polytool.core.errors import PolytoolError, render_panel
34
34
 
35
35
  app = typer.Typer(
36
36
  name="polytool",
@@ -38,9 +38,25 @@ app = typer.Typer(
38
38
  no_args_is_help=True,
39
39
  add_completion=False,
40
40
  rich_markup_mode="rich",
41
+ # We handle PolytoolError ourselves via the `main()` wrapper below; disable
42
+ # Typer's pretty-traceback so it doesn't squash our hints.
43
+ pretty_exceptions_enable=False,
41
44
  )
42
45
 
43
46
 
47
+ def run() -> None:
48
+ """Console-script entry point — runs the Typer app and renders PolytoolError nicely.
49
+
50
+ (Defined with a unique name to avoid clashing with the ``@app.callback()``
51
+ function below — both would otherwise be named ``main`` in this module.)
52
+ """
53
+ try:
54
+ app()
55
+ except PolytoolError as exc:
56
+ render_panel(exc.message, exc.hint)
57
+ sys.exit(1)
58
+
59
+
44
60
  def _version_callback(value: bool) -> None:
45
61
  if value:
46
62
  typer.echo(f"polytool {__version__}")
@@ -41,7 +41,7 @@ def _open_image(source: Path):
41
41
  except ImportError as exc:
42
42
  raise PolytoolError(
43
43
  "HEIC support missing.",
44
- hint="Install: [cyan]uv tool install 'polytool[img]'[/cyan]",
44
+ hint="Install: [cyan]uv tool install 'polytool\\[img]'[/cyan]",
45
45
  ) from exc
46
46
  if suffix == ".avif":
47
47
  try:
@@ -49,7 +49,7 @@ def _open_image(source: Path):
49
49
  except ImportError as exc:
50
50
  raise PolytoolError(
51
51
  "AVIF support missing.",
52
- hint="Install: [cyan]uv tool install 'polytool[img]'[/cyan]",
52
+ hint="Install: [cyan]uv tool install 'polytool\\[img]'[/cyan]",
53
53
  ) from exc
54
54
 
55
55
  try:
@@ -121,7 +121,7 @@ def cmd_install() -> None:
121
121
  except FileNotFoundError as exc:
122
122
  raise PolytoolError(
123
123
  "Playwright not found.",
124
- hint="Install: [cyan]uv tool install 'polytool[shot]'[/cyan]",
124
+ hint="Install: [cyan]uv tool install 'polytool\\[shot]'[/cyan]",
125
125
  ) from exc
126
126
  except subprocess.CalledProcessError as exc:
127
127
  raise PolytoolError(f"playwright install failed (exit {exc.returncode})") from exc
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import sys
6
5
  from typing import NoReturn
7
6
 
8
7
  import typer
@@ -11,8 +10,20 @@ from rich.panel import Panel
11
10
  from polytool.core.console import err_console
12
11
 
13
12
 
13
+ def render_panel(message: str, hint: str | None) -> None:
14
+ """Render the standard red error panel to stderr."""
15
+ body = f"[red bold]{message}[/red bold]"
16
+ if hint:
17
+ body += f"\n\n[dim]Hint:[/dim] {hint}"
18
+ err_console.print(Panel(body, border_style="red", title="Error"))
19
+
20
+
14
21
  class PolytoolError(Exception):
15
- """A user-facing error with an optional fix hint."""
22
+ """A user-facing error with an optional fix hint.
23
+
24
+ The CLI entry point (``polytool.cli.main``) catches this and renders
25
+ ``render_panel`` instead of letting Python or Typer print a stack trace.
26
+ """
16
27
 
17
28
  def __init__(self, message: str, hint: str | None = None) -> None:
18
29
  super().__init__(message)
@@ -24,10 +35,16 @@ class MissingExtraError(PolytoolError):
24
35
  """Raised when an optional dependency group isn't installed."""
25
36
 
26
37
  def __init__(self, module: str, extra: str) -> None:
38
+ from rich.markup import escape
39
+
27
40
  message = f"Missing optional dependency: '{module}' (from the '{extra}' extra)."
41
+ # rich.markup.escape only escapes `[` — `]` is left literal because
42
+ # Rich only treats `[` as the markup-tag opener.
43
+ spec = escape(f"polytool[{extra}]")
44
+ full = escape("polytool[full]")
28
45
  hint = (
29
- f"Install with: [cyan]uv tool install 'polytool[{extra}]'[/cyan]\n"
30
- f"Or for everything: [cyan]uv tool install 'polytool[full]'[/cyan]"
46
+ f"Install with: [cyan]uv tool install '{spec}'[/cyan]\n"
47
+ f"Or for everything: [cyan]uv tool install '{full}'[/cyan]"
31
48
  )
32
49
  super().__init__(message, hint)
33
50
  self.module = module
@@ -36,21 +53,14 @@ class MissingExtraError(PolytoolError):
36
53
 
37
54
  def fail(message: str, hint: str | None = None) -> NoReturn:
38
55
  """Print a red error panel and exit with code 1."""
39
- body = f"[red bold]{message}[/red bold]"
40
- if hint:
41
- body += f"\n\n[dim]Hint:[/dim] {hint}"
42
- err_console.print(Panel(body, border_style="red", title="Error"))
56
+ render_panel(message, hint)
43
57
  raise typer.Exit(code=1)
44
58
 
45
59
 
46
60
  def install_excepthook() -> None:
47
- """Install a sys.excepthook that renders PolytoolError nicely."""
48
- original = sys.excepthook
49
-
50
- def hook(exc_type, exc_value, tb):
51
- if isinstance(exc_value, PolytoolError):
52
- fail(exc_value.message, exc_value.hint)
53
- return
54
- original(exc_type, exc_value, tb)
61
+ """No-op kept for backwards compatibility.
55
62
 
56
- sys.excepthook = hook
63
+ Earlier versions installed a ``sys.excepthook``. The CLI entry-point now
64
+ handles ``PolytoolError`` directly via a try/except wrapper.
65
+ """
66
+ return
@@ -28,8 +28,10 @@ def ffmpeg_path() -> str:
28
28
 
29
29
  from polytool.core.errors import PolytoolError
30
30
 
31
+ from rich.markup import escape
32
+
31
33
  raise PolytoolError(
32
34
  "ffmpeg not found.",
33
35
  hint="Install ffmpeg system-wide, or install the 'vid' extra: "
34
- "[cyan]uv tool install 'polytool[vid]'[/cyan]",
36
+ f"[cyan]uv tool install '{escape('polytool[vid]')}'[/cyan]",
35
37
  )
File without changes
File without changes
File without changes
File without changes