diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 075e900..3626196 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,30 +5,23 @@ on:
branches: [ master ]
pull_request:
branches: [ master ]
+ workflow_dispatch:
jobs:
- test:
+ testing:
runs-on: ubuntu-latest
- strategy:
- matrix:
- python-version: ["3.6"]
steps:
- - uses: actions/checkout@v4
-
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
- with:
- python-version: ${{ matrix.python-version }}
-
- - name: Install package
- run: pip install .
-
- - name: Install black
- run: pip install black
-
- - name: Check code formatting with black
- run: black --check stubs/
-
- - name: Type check with mypy
- run: mypy stubs/
\ No newline at end of file
+ - uses: actions/checkout@v4
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v5
+
+ - name: Install the project
+ run: uv sync --locked --all-extras --dev
+
+ - name: Check code formatting with ruff
+ run: uv run ruff check
+
+ - name: Type check with mypy
+ run: uv run mypy
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..a145fb5
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,31 @@
+name: Publish
+
+on:
+ release:
+ types: [ published ]
+ workflow_dispatch:
+
+jobs:
+
+ publish:
+ runs-on: ubuntu-latest
+ environment:
+ name: pypi
+ permissions:
+ id-token: write
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v5
+
+ - name: Install the project
+ run: uv sync --locked --all-extras --dev
+
+ - name: Build
+ run: uv build
+
+ - name: Publish
+ if: github.event_name == 'release'
+ run: uv publish --trusted-publishing always
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index a2e120d..c54cb59 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE
old mode 100755
new mode 100644
similarity index 100%
rename from LICENSE.txt
rename to LICENSE
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..2c9a355
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include pythonista_stubs/*.pyi
+include VERSION
\ No newline at end of file
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..fbcbf73
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+3.4.0
\ No newline at end of file
diff --git a/mypy.ini b/mypy.ini
deleted file mode 100644
index 0d69f24..0000000
--- a/mypy.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[mypy]
-python_version = 3.6
-
-[mypy-PIL.*]
-ignore_missing_imports = True
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..c5ac17a
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,76 @@
+[build-system]
+requires = ["setuptools>=80.1.0"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "pythonista-stubs"
+authors = [
+ { name = "Harold Martin" },
+ { name = "Dmytro Yaroshenko" }
+]
+description = "A collection of Pythonista stub files"
+readme = "README.md"
+requires-python = ">=3.10"
+dependencies = [
+ "types-pillow>=10.2.0.20240822",
+ "typing-extensions>=4.14.1",
+]
+license = { file = "LICENSE" }
+keywords = ["pythonista", "stubs", "ios", "ide"]
+classifiers = [
+ "Intended Audience :: Developers",
+ "Natural Language :: English",
+ "Programming Language :: Python",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: Implementation :: CPython",
+]
+dynamic = ['version']
+
+[project.urls]
+"Homepage" = "https://github.com/hbmartin/pythonista-stubs"
+"Bug Reports" = "https://github.com/hbmartin/pythonista-stubs/issues"
+"Source" = "https://github.com/hbmartin/pythonista-stubs"
+
+[project.optional-dependencies]
+dev = [
+ "mypy>=1.17.1",
+ "ruff>=0.12.8",
+]
+
+[tool.setuptools.package-dir]
+"pythonista_stubs" = "stubs"
+
+[tool.setuptools.packages.find]
+where = ["stubs"]
+include = [
+ "pythonista_stubs*",
+]
+
+[tool.setuptools.package-data]
+"*" = ["**/*"] # to include root-places .pyi stubs
+
+[tool.setuptools.dynamic]
+version = { file = "VERSION" }
+
+[dependency-groups]
+dev = [
+ "mypy>=1.17.1", # as a dev dependency, Pythonista does not support mypy
+ "ruff>=0.12.8",
+]
+
+[tool.mypy]
+packages = ["stubs"]
+ignore_missing_imports = true
+
+[tool.ruff]
+include = [
+ "pyproject.toml",
+ "stubs/**/*.py",
+ "stubs/**/*.pyi",
+]
diff --git a/setup.py b/setup.py
index 1c221a7..6068493 100755
--- a/setup.py
+++ b/setup.py
@@ -1,39 +1,3 @@
from setuptools import setup
-from os import path
-here = path.abspath(path.dirname(__file__))
-
-with open(path.join(here, "README.md"), encoding="utf-8") as f:
- long_description = f.read()
-
-packages = ["appex", "clipboard", "console", "editor", "reminders", "sound", "speech", "ui"]
-packages_stubs = [p + "-stubs" for p in packages]
-package_dir = {p + "-stubs": path.join("stubs", p) for p in packages}
-package_data = {ps: ["__init__.pyi"] for ps in packages_stubs}
-
-setup(
- name="pythonista-stubs",
- version="0.0.3",
- description="A collection of Pythonista stub files",
- long_description=long_description,
- long_description_content_type="text/markdown",
- url="https://github.com/hbmartin/pythonista-stubs",
- package_dir=package_dir,
- packages=packages_stubs,
- package_data=package_data,
- install_requires=["typing_extensions", "mypy", "Pillow"],
- classifiers=[
- "Development Status :: 3 - Alpha",
- "Intended Audience :: Developers",
- "Intended Audience :: Information Technology",
- "License :: OSI Approved :: Apache Software License",
- "Operating System :: iOS",
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.6",
- "Topic :: Software Development :: Libraries :: Python Modules",
- "Typing :: Typed",
- ],
- license="Apache License 2.0",
- keywords="pythonista stubs ios ide",
- project_urls={"Bug Reports": "https://github.com/hbmartin/pythonista-stubs/issues"},
-)
+setup()
diff --git a/stubs/_cb.pyi b/stubs/_cb.pyi
new file mode 100644
index 0000000..0d8240f
--- /dev/null
+++ b/stubs/_cb.pyi
@@ -0,0 +1,103 @@
+# Created on July, 07 2025 by o-murphy
+"""
+pythonista `_cb` module type annotations
+according to [pythonista.cb docs](https://omz-software.com/pythonista/docs/ios/cb.html)
+and references to pythonista built-in `_cb` module help
+>>> import _cb
+>>> help(_cb)
+"""
+
+from typing import Optional, List
+
+__all__ = (
+ "CM_STATE_UNKNOWN",
+ "CM_STATE_RESETTING",
+ "CM_STATE_UNSUPPORTED",
+ "CM_STATE_UNAUTHORIZED",
+ "CM_STATE_POWERED_OFF",
+ "CM_STATE_POWERED_ON",
+ "CH_PROP_BROADCAST",
+ "CH_PROP_READ",
+ "CH_PROP_WRITE_WITHOUT_RESPONSE",
+ "CH_PROP_WRITE",
+ "CH_PROP_NOTIFY",
+ "CH_PROP_INDICATE",
+ "CH_PROP_AUTHENTICATED_SIGNED_WRITES",
+ "CH_PROP_EXTENDED_PROPERTIES",
+ "CH_PROP_NOTIFY_ENCRYPTION_REQUIRED",
+ "CH_PROP_INDICATE_ENCRYPTION_REQUIRED",
+ "Characteristic",
+ "Service",
+ "Peripheral",
+ "CentralManager",
+)
+
+CM_STATE_UNKNOWN: int = 0
+CM_STATE_RESETTING: int = 1
+CM_STATE_UNSUPPORTED: int = 2
+CM_STATE_UNAUTHORIZED: int = 3
+CM_STATE_POWERED_OFF: int = 4
+CM_STATE_POWERED_ON: int = 5
+
+CH_PROP_BROADCAST: int = 1
+CH_PROP_READ: int = 2
+CH_PROP_WRITE_WITHOUT_RESPONSE: int = 4
+CH_PROP_WRITE: int = 8
+CH_PROP_NOTIFY: int = 16
+CH_PROP_INDICATE: int = 32
+CH_PROP_AUTHENTICATED_SIGNED_WRITES: int = 64
+CH_PROP_EXTENDED_PROPERTIES: int = 128
+CH_PROP_NOTIFY_ENCRYPTION_REQUIRED: int = 256
+CH_PROP_INDICATE_ENCRYPTION_REQUIRED: int = 512
+
+class Characteristic:
+ properties: int
+ value: Optional[bytes]
+ uuid: str # hex
+ notifying: bool
+
+class Service:
+ characteristics: List[Characteristic]
+ primary: bool
+ uuid: str # hex
+
+class Peripheral:
+ manufacturer_data: bytes
+ name: Optional[str]
+ uuid: str # hex
+ state: int
+ services: List[Service]
+
+ def discover_services(self) -> None: ...
+ def discover_characteristics(self, service: Service) -> None: ...
+ def set_notify_value(
+ self, characteristic: Characteristic, flag: bool = True
+ ) -> None: ...
+ def write_characteristic_value(
+ self, characteristic, data, with_response
+ ) -> None: ...
+ def read_characteristic_value(self, characteristic: Characteristic) -> None: ...
+
+class CentralManager:
+ state: int
+
+ def __init__(self) -> None: ...
+ def scan_for_peripherals(self) -> None: ...
+ def stop_scan(self) -> None: ...
+ def connect_peripheral(self, p: Peripheral) -> None: ...
+ def cancel_peripheral_connection(self, p: Peripheral) -> None: ...
+ def did_discover_peripheral(self, p: Peripheral) -> None: ...
+ def did_connect_peripheral(self, p: Peripheral) -> None: ...
+ def did_fail_to_connect_peripheral(
+ self, p: Peripheral, error: Optional[str]
+ ) -> None: ...
+ def did_disconnect_peripheral(
+ self, p: Peripheral, error: Optional[str]
+ ) -> None: ...
+ def did_discover_services(self, p: Peripheral, error: Optional[str]) -> None: ...
+ def did_discover_characteristics(
+ self, s: Service, error: Optional[str]
+ ) -> None: ...
+ def did_write_value(self, c: Characteristic, error: Optional[str]) -> None: ...
+ def did_update_value(self, c: Characteristic, error: Optional[str]) -> None: ...
+ def did_update_state(self) -> None: ...
diff --git a/stubs/appex/__init__.pyi b/stubs/appex/__init__.pyi
deleted file mode 100644
index 7354014..0000000
--- a/stubs/appex/__init__.pyi
+++ /dev/null
@@ -1,16 +0,0 @@
-from typing import List, Any, ByteString, Optional
-from typing_extensions import Literal
-
-def is_running_extension() -> bool: ...
-def get_attachments(uti: str = "public.data") -> List[Any]: ...
-def get_images(image_type: Literal["ui", "pil"] = "pil") -> List[Any]: ...
-def get_image(image_type: Literal["ui", "pil"] = "pil") -> Any: ...
-def get_image_data() -> Optional[ByteString]: ...
-def get_images_data() -> List[ByteString]: ...
-def get_text() -> str: ...
-def get_urls() -> List[str]: ...
-def get_url() -> Optional[str]: ...
-def get_file_paths() -> List[str]: ...
-def get_file_path() -> Optional[str]: ...
-def get_vcard() -> Optional[Any]: ...
-def get_vcards() -> List[Any]: ...
diff --git a/stubs/clipboard/__init__.pyi b/stubs/clipboard/__init__.pyi
deleted file mode 100644
index b745de1..0000000
--- a/stubs/clipboard/__init__.pyi
+++ /dev/null
@@ -1,9 +0,0 @@
-from PIL import Image
-from typing_extensions import Literal
-
-def get() -> str: ...
-def set(string: str) -> None: ...
-def get_image(idx: int = 0) -> Image: ...
-def set_image(
- image: Image, format: Literal["jpeg", "png"] = "png", jpeg_quality: float = 0.75
-) -> None: ...
diff --git a/stubs/console/__init__.pyi b/stubs/console/__init__.pyi
deleted file mode 100644
index 116d393..0000000
--- a/stubs/console/__init__.pyi
+++ /dev/null
@@ -1,48 +0,0 @@
-from typing import Optional, Tuple
-from typing_extensions import Literal
-
-def clear() -> None: ...
-def set_font(name: Optional[str], size: Optional[float]) -> None: ...
-def set_color(r: float, g: float, b: float) -> None: ...
-def secure_input(prompt: Optional[str]) -> None: ...
-def show_image(image_path: str) -> None: ...
-def alert(
- title: str,
- message: Optional[str],
- button1: Optional[str],
- button2: Optional[str],
- button3: Optional[str],
- hide_cancel_button: bool = False,
-) -> int: ...
-def input_alert(
- title: str,
- message: Optional[str],
- input: Optional[str],
- ok_button_title: Optional[str],
- hide_cancel_button: bool = False,
-) -> str: ...
-def password_alert(
- title: str,
- message: Optional[str],
- password: Optional[str],
- ok_button_title: Optional[str],
- hide_cancel_button: bool = False,
-) -> str: ...
-def login_alert(
- title: str,
- message: Optional[str],
- login: Optional[str],
- password: Optional[str],
- ok_button_title: Optional[str],
-) -> Tuple[str, str]: ...
-def show_activity() -> None: ...
-def hide_activity() -> None: ...
-def hud_alert(
- message: str, icon: Literal["success", "error"] = "success", duration: float = 1.8
-) -> None: ...
-def write_link(title: str, link_url: str) -> None: ...
-def hide_output() -> None: ...
-def quicklook(file_path: str) -> None: ...
-def open_in(file_path) -> None: ...
-def set_idle_timer_disabled(flag: bool) -> None: ...
-def is_in_background() -> bool: ...
diff --git a/stubs/editor/__init__.py b/stubs/editor/__init__.py
deleted file mode 100644
index ef41acb..0000000
--- a/stubs/editor/__init__.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from typing import Optional, Tuple
-from typing_extensions import Literal
-
-from stubs import ui
-
-
-def get_path() -> Optional[str]:
- ...
-
-
-def get_text() -> str:
- ...
-
-
-def get_selection() -> Optional[Tuple[int, int]]:
- ...
-
-
-def get_line_selection() -> Optional[Tuple[int, int]]:
- ...
-
-
-def set_selection(start: int, end: Optional[int] = None, scroll: bool = False) -> None:
- ...
-
-
-def replace_text(start: int, end: int, replacement: str) -> None:
- ...
-
-
-def make_new_file(name: str, content: Optional[str], new_tab: Optional[bool]) -> None:
- ...
-
-
-def open_file(name: str, new_tab: Optional[bool] = False) -> None:
- ...
-
-
-def reload_files() -> None:
- ... # undocumented
-
-
-def apply_ui_theme(ui_view: ui.View, theme_name: Optional[str] = None) -> None:
- ...
-
-
-def present_themed(
- ui_view: ui.View, theme_name: Optional[str] = None, **kwargs
-) -> None:
- ...
-
-
-def annotate_line(
- lineno: int,
- text: str = "",
- style: Literal["success", "warning", "error"] = "warning",
- expanded: bool = True,
- filename: Optional[str] = None,
- scroll: bool = False,
-) -> None:
- ...
-
-
-def clear_annotations(filename: Optional[str] = None) -> None:
- ...
diff --git a/stubs/py.typed b/stubs/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/stubs/pythonista_stubs/__init__.pyi b/stubs/pythonista_stubs/__init__.pyi
new file mode 100644
index 0000000..bda5b5a
--- /dev/null
+++ b/stubs/pythonista_stubs/__init__.pyi
@@ -0,0 +1 @@
+__version__: str
diff --git a/stubs/pythonista_stubs/appex.pyi b/stubs/pythonista_stubs/appex.pyi
new file mode 100644
index 0000000..6714ba0
--- /dev/null
+++ b/stubs/pythonista_stubs/appex.pyi
@@ -0,0 +1,161 @@
+"""
+This is a stub file for the `appex` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import Any, Dict, List, Optional, Literal, Union
+
+# These are imported from the `ui` module, which is part of Pythonista.
+class View: ...
+class Image: ...
+
+# Assuming 'PIL' is from the Pillow library.
+try:
+ from PIL.Image import Image as PilImage
+except ImportError:
+ PilImage: Any # type: ignore[no-redef]
+
+# -----------------------------------------------------------------------------
+# General Functions
+# -----------------------------------------------------------------------------
+def is_running_extension() -> bool:
+ """Return True if the script is running within an app extension (share
+ extension or widget), False otherwise.
+ """
+ ...
+
+def is_widget() -> bool:
+ """Return True if the script is running within the Today widget, False
+ otherwise.
+ """
+ ...
+
+def finish(js: Optional[str] = None) -> None:
+ """Close the share sheet extension.
+ Args:
+ js (str, optional): A piece of JavaScript code to be evaluated in the
+ context of the current web page (only relevant in Safari).
+ """
+ ...
+
+# -----------------------------------------------------------------------------
+# Share Extension Functions
+# -----------------------------------------------------------------------------
+def get_attachments(uti: str = "public.data") -> List[Any]:
+ """Return a list of attachments that match the given type identifier.
+ Args:
+ uti (str, optional): The type identifier to match. Defaults to 'public.data'.
+ Returns:
+ List[Any]: A list of attachments.
+ """
+ ...
+
+_ImageType = Literal["ui", "pil"]
+
+def get_images(image_type: _ImageType = "pil") -> List[Union[Image, PilImage]]:
+ """Return a list of images in the input of the share sheet.
+ Args:
+ image_type (Literal['ui', 'pil'], optional): The desired image type.
+ Defaults to 'pil'.
+ Returns:
+ List[Union[ui.Image, PIL.Image.Image]]: A list of images.
+ """
+ ...
+
+def get_image(image_type: _ImageType = "pil") -> Optional[Union[Image, PilImage]]:
+ """Return the first image in the input of the share sheet.
+ Args:
+ image_type (Literal['ui', 'pil'], optional): The desired image type.
+ Defaults to 'pil'.
+ Returns:
+ Optional[Union[ui.Image, PIL.Image.Image]]: The first image, or None.
+ """
+ ...
+
+def get_image_data() -> Optional[bytes]:
+ """Return raw image data for the first image in the share sheet’s input.
+ Returns:
+ Optional[bytes]: The raw image data as a byte string, or None.
+ """
+ ...
+
+def get_images_data() -> List[bytes]:
+ """Return raw image data for all images in the share sheet’s input.
+ Returns:
+ List[bytes]: A list of byte strings, or an empty list.
+ """
+ ...
+
+def get_text() -> Optional[str]:
+ """Return text input of the share sheet.
+ Returns:
+ Optional[str]: The text as a unicode string, or None.
+ """
+ ...
+
+def get_urls() -> List[str]:
+ """Return a list of URLs in the share sheet’s input.
+ Returns:
+ List[str]: A list of URLs, or an empty list.
+ """
+ ...
+
+def get_url() -> Optional[str]:
+ """Return the first URL in the share sheet’s input.
+ Returns:
+ Optional[str]: The first URL, or None.
+ """
+ ...
+
+def get_file_paths() -> List[str]:
+ """Return a list of file paths in the share sheet’s input.
+ Returns:
+ List[str]: A list of file paths, or an empty list.
+ """
+ ...
+
+def get_file_path() -> Optional[str]:
+ """Return the first file path in the share sheet’s input.
+ Returns:
+ Optional[str]: The first file path, or None.
+ """
+ ...
+
+def get_vcards() -> List[str]:
+ """Return a list of VCard records in the share sheet’s input.
+ Returns:
+ List[str]: A list of VCard records as strings, or an empty list.
+ """
+ ...
+
+def get_vcard() -> Optional[str]:
+ """Return the first VCard record in the share sheet’s input.
+ Returns:
+ Optional[str]: The first VCard record as a string, or None.
+ """
+ ...
+
+def get_web_page_info() -> Dict[str, str]:
+ """When the share sheet is shown in Safari, return information about the
+ currently loaded page.
+ Returns:
+ Dict[str, str]: A dictionary with page information, or an empty dict.
+ """
+ ...
+
+# -----------------------------------------------------------------------------
+# Today Widget Functions
+# -----------------------------------------------------------------------------
+def get_widget_view() -> Optional[View]:
+ """Return the view that is currently shown in the Today widget.
+ Returns:
+ Optional[ui.View]: The current view, or None.
+ """
+ ...
+
+def set_widget_view(view: Optional[View]) -> None:
+ """Set the widget’s view to a ui.View object.
+ Args:
+ view (Optional[ui.View]): The view to set, or None to remove the current view.
+ """
+ ...
diff --git a/stubs/pythonista_stubs/canvas.pyi b/stubs/pythonista_stubs/canvas.pyi
new file mode 100644
index 0000000..3b32039
--- /dev/null
+++ b/stubs/pythonista_stubs/canvas.pyi
@@ -0,0 +1,221 @@
+"""
+This is a stub file for the `canvas` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import Optional, Tuple
+
+# -----------------------------------------------------------------------------
+# Blend Modes (Constants)
+# -----------------------------------------------------------------------------
+BLEND_NORMAL: int = ...
+BLEND_MULTIPLY: int = ...
+BLEND_SCREEN: int = ...
+BLEND_OVERLAY: int = ...
+BLEND_DARKEN: int = ...
+BLEND_LIGHTEN: int = ...
+BLEND_COLOR_DODGE: int = ...
+BLEND_COLOR_BURN: int = ...
+BLEND_SOFT_LIGHT: int = ...
+BLEND_HARD_LIGHT: int = ...
+BLEND_DIFFERENCE: int = ...
+BLEND_EXCLUSION: int = ...
+BLEND_HUE: int = ...
+BLEND_SATURATION: int = ...
+BLEND_COLOR: int = ...
+BLEND_LUMINOSITY: int = ...
+BLEND_CLEAR: int = ...
+BLEND_COPY: int = ...
+BLEND_SOURCE_IN: int = ...
+BLEND_SOURCE_OUT: int = ...
+BLEND_SOURCE_ATOP: int = ...
+BLEND_DESTINATION_OVER: int = ...
+BLEND_DESTINATION_ATOP: int = ...
+BLEND_XOR: int = ...
+BLEND_PLUS_DARKER: int = ...
+BLEND_PLUS_LIGHTER: int = ...
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+# Configuration
+def clear() -> None:
+ """Clears the canvas."""
+ ...
+
+def get_size() -> Tuple[float, float]:
+ """Return the size of the canvas as a tuple of width and height."""
+ ...
+
+def set_size(width: float, height: float) -> None:
+ """Set the size of the canvas and reset all graphics state."""
+ ...
+
+def begin_updates() -> None:
+ """Begins a group of drawing operations to improve performance."""
+ ...
+
+def end_updates() -> None:
+ """Forces all buffered drawing to be displayed on the screen."""
+ ...
+
+def save_png(filename: str) -> None:
+ """Save the current content of the canvas to a PNG file."""
+ ...
+
+# Setting Drawing Parameters
+def set_aa_enabled(flag: bool) -> None:
+ """Enables or disables antialiasing."""
+ ...
+
+def set_alpha(alpha: float) -> None:
+ """Sets the global alpha value."""
+ ...
+
+def set_blend_mode(mode: int) -> None:
+ """Sets the active blend mode."""
+ ...
+
+def set_fill_color(r: float, g: float, b: float, a: float = 1.0) -> None:
+ """Sets the fill color (RGB or RGBA)."""
+ ...
+
+def set_line_width(width: float) -> None:
+ """Sets the line width."""
+ ...
+
+def set_stroke_color(r: float, g: float, b: float, a: float = 1.0) -> None:
+ """Sets the active stroke color (RGB or RGBA)."""
+ ...
+
+# Vector Drawing Functions
+def add_curve(
+ cp1x: float, cp1y: float, cp2x: float, cp2y: float, x: float, y: float
+) -> None:
+ """Adds a cubic bezier curve to the current path."""
+ ...
+
+def add_ellipse(x: float, y: float, width: float, height: float) -> None:
+ """Adds an ellipse to the current path."""
+ ...
+
+def add_line(x: float, y: float) -> None:
+ """Adds a straight line to the current path."""
+ ...
+
+def add_quad_curve(cpx: float, cpy: float, x: float, y: float) -> None:
+ """Adds a quadratic bezier curve to the current path."""
+ ...
+
+def add_rect(x: float, y: float, width: float, height: float) -> None:
+ """Adds a rectangle to the current path."""
+ ...
+
+def begin_path() -> None:
+ """Begins a new path."""
+ ...
+
+def clip() -> None:
+ """Clips subsequent drawing to the current path."""
+ ...
+
+def close_path() -> None:
+ """Closes the current path."""
+ ...
+
+def draw_ellipse(x: float, y: float, width: float, height: float) -> None:
+ """Draws an ellipse in a rectangle."""
+ ...
+
+def draw_line(x1: float, y1: float, x2: float, y2: float) -> None:
+ """Draws a line between two points."""
+ ...
+
+def draw_path() -> None:
+ """Draws the outline of the current path."""
+ ...
+
+def draw_rect(x: float, y: float, width: float, height: float) -> None:
+ """Draws the outline of a rectangle."""
+ ...
+
+def fill_ellipse(x: float, y: float, width: float, height: float) -> None:
+ """Fills an ellipse in a rectangle."""
+ ...
+
+def fill_path() -> None:
+ """Fills the current path."""
+ ...
+
+def fill_pixel(x: float, y: float) -> None:
+ """Fills a pixel with the current fill color."""
+ ...
+
+def fill_rect(x: float, y: float, width: float, height: float) -> None:
+ """Fills a rectangle."""
+ ...
+
+def move_to(x: float, y: float) -> None:
+ """Moves the 'pen' to a given point."""
+ ...
+
+# Transforms
+def restore_gstate() -> None:
+ """Restores the graphics state from the stack."""
+ ...
+
+def rotate(angle: float) -> None:
+ """Rotates the current transformation matrix."""
+ ...
+
+def save_gstate() -> None:
+ """Pushes a copy of the current graphics state onto the stack."""
+ ...
+
+def scale(sx: float, sy: float) -> None:
+ """Scales the current transformation matrix."""
+ ...
+
+def translate(tx: float, ty: float) -> None:
+ """Translates the current transformation matrix."""
+ ...
+
+# Drawing Bitmap Images
+def draw_image(
+ image_name: str,
+ x: float,
+ y: float,
+ width: Optional[float] = None,
+ height: Optional[float] = None,
+) -> None:
+ """Draws the image with the given name in a rectangle."""
+ ...
+
+def draw_clipboard(x: float, y: float, width: float, height: float) -> None:
+ """Draw the image in the clipboard in a given rectangle."""
+ ...
+
+def get_clipboard_size() -> Tuple[float, float]:
+ """Return the size of the image in the clipboard in points."""
+ ...
+
+def get_image_size(image_name: str) -> Tuple[float, float]:
+ """Returns the size of the image with the given name in points."""
+ ...
+
+# Drawing Text
+def draw_text(
+ text: str,
+ x: float,
+ y: float,
+ font_name: str = "Helvetica",
+ font_size: float = 16.0,
+) -> None:
+ """Draw a single line of text at a given point."""
+ ...
+
+def get_text_size(
+ text: str, font_name: str = "Helvetica", font_size: float = 16.0
+) -> Tuple[float, float]:
+ """Get the size of a line of text as a tuple of (width, height)."""
+ ...
diff --git a/stubs/pythonista_stubs/cb.pyi b/stubs/pythonista_stubs/cb.pyi
new file mode 100644
index 0000000..7b85722
--- /dev/null
+++ b/stubs/pythonista_stubs/cb.pyi
@@ -0,0 +1,94 @@
+"""
+`pythonista.cb` module type annotations
+according to [pythonista.cb docs](https://omz-software.com/pythonista/docs/ios/cb.html)
+"""
+
+from typing import Optional, Protocol
+from _cb import (
+ CM_STATE_UNKNOWN,
+ CM_STATE_RESETTING,
+ CM_STATE_UNSUPPORTED,
+ CM_STATE_UNAUTHORIZED,
+ CM_STATE_POWERED_OFF,
+ CM_STATE_POWERED_ON,
+ CH_PROP_BROADCAST,
+ CH_PROP_READ,
+ CH_PROP_WRITE_WITHOUT_RESPONSE,
+ CH_PROP_WRITE,
+ CH_PROP_NOTIFY,
+ CH_PROP_INDICATE,
+ CH_PROP_AUTHENTICATED_SIGNED_WRITES,
+ CH_PROP_EXTENDED_PROPERTIES,
+ CH_PROP_NOTIFY_ENCRYPTION_REQUIRED,
+ CH_PROP_INDICATE_ENCRYPTION_REQUIRED,
+ Characteristic,
+ Service,
+ Peripheral,
+ CentralManager,
+)
+
+__all__ = (
+ "CM_STATE_UNKNOWN",
+ "CM_STATE_RESETTING",
+ "CM_STATE_UNSUPPORTED",
+ "CM_STATE_UNAUTHORIZED",
+ "CM_STATE_POWERED_OFF",
+ "CM_STATE_POWERED_ON",
+ "CH_PROP_BROADCAST",
+ "CH_PROP_READ",
+ "CH_PROP_WRITE_WITHOUT_RESPONSE",
+ "CH_PROP_WRITE",
+ "CH_PROP_NOTIFY",
+ "CH_PROP_INDICATE",
+ "CH_PROP_AUTHENTICATED_SIGNED_WRITES",
+ "CH_PROP_EXTENDED_PROPERTIES",
+ "CH_PROP_NOTIFY_ENCRYPTION_REQUIRED",
+ "CH_PROP_INDICATE_ENCRYPTION_REQUIRED",
+ "Characteristic",
+ "Service",
+ "Peripheral",
+ "CentralManager",
+ "SharedCentralManager",
+ "shared_manager",
+ "set_central_delegate",
+ "set_verbose",
+ "scan_for_peripherals",
+ "stop_scan",
+ "connect_peripheral",
+ "cancel_peripheral_connection",
+ "get_state",
+ "reset",
+)
+
+class _CentralManagerDelegate(Protocol):
+ def did_discover_peripheral(self, p: Peripheral) -> None: ...
+ def did_connect_peripheral(self, p: Peripheral) -> None: ...
+ def did_fail_to_connect_peripheral(
+ self, p: Peripheral, error: Optional[str]
+ ) -> None: ...
+ def did_disconnect_peripheral(
+ self, p: Peripheral, error: Optional[str]
+ ) -> None: ...
+ def did_discover_services(self, p: Peripheral, error: Optional[str]) -> None: ...
+ def did_discover_characteristics(
+ self, s: Service, error: Optional[str]
+ ) -> None: ...
+ def did_write_value(self, c: Characteristic, error: Optional[str]) -> None: ...
+ def did_update_value(self, c: Characteristic, error: Optional[str]) -> None: ...
+ def did_update_state(self) -> None: ...
+
+class SharedCentralManager(CentralManager):
+ delegate: Optional[_CentralManagerDelegate] = None
+ verbose: bool = False
+ def verbose_log(self): ...
+
+shared_manager: Optional[SharedCentralManager] = SharedCentralManager()
+
+def set_central_delegate(delegate: _CentralManagerDelegate) -> None: ...
+def set_verbose(flag: bool) -> None: ...
+def scan_for_peripherals() -> None: ...
+def stop_scan() -> None: ...
+def connect_peripheral(p: Peripheral) -> None: ...
+def cancel_peripheral_connection(p: Peripheral) -> None: ...
+def get_state() -> int: ...
+def reset() -> None: ...
diff --git a/stubs/pythonista_stubs/clipboard.pyi b/stubs/pythonista_stubs/clipboard.pyi
new file mode 100644
index 0000000..00f120f
--- /dev/null
+++ b/stubs/pythonista_stubs/clipboard.pyi
@@ -0,0 +1,63 @@
+"""
+This is a stub file for the `clipboard` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import Optional, Literal, Any
+
+# Assuming 'PIL' is from the Pillow library, which is not part of the standard library
+# and might not be available in all Pythonista environments.
+# We'll use `Any` as a fallback or assume a type alias exists.
+try:
+ from PIL.Image import Image
+except ImportError:
+ Image: Any # type: ignore[no-redef]
+
+def get() -> str:
+ """Returns the clipboard’s content as a Unicode string.
+
+ Returns:
+ str: The content of the clipboard.
+ """
+ ...
+
+def set(string: str) -> None:
+ """Sets the clipboard’s content to a new string.
+
+ Args:
+ string (str): The new content for the clipboard.
+ """
+ ...
+
+def get_image(idx: int = 0) -> Optional[Image]:
+ """Returns an image from the clipboard.
+
+ If there are multiple images in the clipboard, the `idx` parameter can be
+ used to get an image at a given index. If the index is >= the number of
+ images in the clipboard, `None` is returned.
+
+ Args:
+ idx (int, optional): The index of the image to retrieve. Defaults to 0.
+
+ Returns:
+ Optional[Image]: The image from the clipboard, or None if no image
+ was found at the given index.
+ """
+ ...
+
+_ImageFormat = Literal["png", "jpeg"]
+
+def set_image(
+ image: Image, format: _ImageFormat = "png", jpeg_quality: float = 0.75
+) -> None:
+ """Stores a given PIL Image in the clipboard.
+
+ Args:
+ image (PIL.Image.Image): The image to store in the clipboard.
+ format (str, optional): The format to store the image in. Can be
+ 'png' or 'jpeg'. Defaults to 'png'.
+ jpeg_quality (float, optional): The quality for JPEG format. Should be
+ a float between 0.0 and 1.0. This is ignored if `format` is 'png'.
+ Defaults to 0.75.
+ """
+ ...
diff --git a/stubs/pythonista_stubs/console.pyi b/stubs/pythonista_stubs/console.pyi
new file mode 100644
index 0000000..1c0cd7d
--- /dev/null
+++ b/stubs/pythonista_stubs/console.pyi
@@ -0,0 +1,244 @@
+"""
+This is a stub file for the `console` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import Optional, Union, Tuple, Sequence, Literal
+
+# These are simple utility functions.
+def clear() -> None:
+ """Clears the console output."""
+ ...
+
+def set_font(name: Optional[str] = None, size: Optional[int] = None) -> None:
+ """Sets the font and font size for the following output.
+
+ Args:
+ name (str, optional): The font name (e.g. "Menlo"). If None, reset to default.
+ size (int, optional): The font size. If None, reset to default.
+ """
+ ...
+
+def set_color(r: float, g: float, b: float) -> None:
+ """Sets the RGB colour for the following output.
+
+ The components are floats between 0.0 and 1.0.
+
+ Args:
+ r (float): The red component.
+ g (float): The green component.
+ b (float): The blue component.
+ """
+ ...
+
+def secure_input(prompt: Optional[str] = None) -> str:
+ """Gets user input with hidden characters.
+
+ This function is similar to the built-in raw_input function, but the user’s
+ input is hidden, so that it’s suitable to request passwords and other
+ sensitive information.
+
+ Args:
+ prompt (str, optional): A prompt to display to the user.
+
+ Returns:
+ str: The string entered by the user.
+ """
+ ...
+
+def show_image(image_path: Union[str]) -> None:
+ """Shows an image in the console output area.
+
+ Args:
+ image_path (str or Path): The path to the image file.
+ """
+ ...
+
+def alert(
+ title: str,
+ message: str = "",
+ button1: str = "OK",
+ button2: Optional[str] = None,
+ button3: Optional[str] = None,
+ hide_cancel_button: bool = False,
+) -> int:
+ """Shows an alert dialog with up to three custom buttons.
+
+ The selected button is returned as an integer (button1 => 1, etc.).
+ Unless `hide_cancel_button` is True, all alert dialogs contain a ‘Cancel’
+ button that sends a KeyboardInterrupt.
+
+ Args:
+ title (str): The title of the alert.
+ message (str, optional): The message to display. Defaults to "".
+ button1 (str, optional): The title of the first button. Defaults to "OK".
+ button2 (str, optional): The title of the second button.
+ button3 (str, optional): The title of the third button.
+ hide_cancel_button (bool, optional): If True, hides the cancel button.
+ Defaults to False.
+
+ Returns:
+ int: The integer corresponding to the selected button (1, 2, or 3).
+ """
+ ...
+
+def input_alert(
+ title: str,
+ message: str = "",
+ input: str = "",
+ ok_button_title: str = "OK",
+ hide_cancel_button: bool = False,
+) -> str:
+ """Shows a dialog with a single text field.
+
+ The text field can be pre-filled with the `input` parameter. The text
+ that was entered by the user is returned. The ‘Cancel’ button sends a
+ KeyboardInterrupt.
+
+ Args:
+ title (str): The title of the alert.
+ message (str, optional): The message to display. Defaults to "".
+ input (str, optional): Text to pre-fill the input field with.
+ Defaults to "".
+ ok_button_title (str, optional): The title of the OK button.
+ Defaults to "OK".
+ hide_cancel_button (bool, optional): If True, hides the cancel button.
+ Defaults to False.
+
+ Returns:
+ str: The text entered by the user.
+ """
+ ...
+
+def password_alert(
+ title: str,
+ message: str = "",
+ password: str = "",
+ ok_button_title: str = "OK",
+ hide_cancel_button: bool = False,
+) -> str:
+ """Shows a dialog with a password entry text field.
+
+ The password field can be pre-filled with the `password` parameter.
+ The password that was entered by the user is returned. The ‘Cancel’ button
+ sends a KeyboardInterrupt.
+
+ Args:
+ title (str): The title of the alert.
+ message (str, optional): The message to display. Defaults to "".
+ password (str, optional): Text to pre-fill the password field with.
+ Defaults to "".
+ ok_button_title (str, optional): The title of the OK button.
+ Defaults to "OK".
+ hide_cancel_button (bool, optional): If True, hides the cancel button.
+ Defaults to False.
+
+ Returns:
+ str: The password entered by the user.
+ """
+ ...
+
+def login_alert(
+ title: str,
+ message: str = "",
+ login: str = "",
+ password: str = "",
+ ok_button_title: str = "OK",
+) -> Tuple[str, str]:
+ """Shows a dialog with two text fields, one for login and one for a password.
+
+ The text fields can be pre-filled with the `login` and `password` parameters.
+ Returns a tuple of the entered text as `(login, password)`. The ‘Cancel’
+ button sends a KeyboardInterrupt.
+
+ Args:
+ title (str): The title of the alert.
+ message (str, optional): The message to display. Defaults to "".
+ login (str, optional): Text to pre-fill the login field with.
+ Defaults to "".
+ password (str, optional): Text to pre-fill the password field with.
+ Defaults to "".
+ ok_button_title (str, optional): The title of the OK button.
+ Defaults to "OK".
+
+ Returns:
+ Tuple[str, str]: A tuple containing the entered login and password.
+ """
+ ...
+
+def show_activity() -> None:
+ """Shows the animated “network activity indicator” in the status bar."""
+ ...
+
+def hide_activity() -> None:
+ """Hides the animated “network activity indicator” in the status bar."""
+ ...
+
+_HudIcon = Literal["success", "error"]
+
+def hud_alert(message: str, icon: _HudIcon = "success", duration: float = 1.8) -> None:
+ """Shows a HUD-style alert with the given message.
+
+ The function blocks until the alert is dismissed.
+
+ Args:
+ message (str): The message to display.
+ icon (str, optional): The icon to show. It Can be 'success' (a checkmark
+ symbol) or 'error' (a cross symbol). Defaults to 'success'.
+ duration (float, optional): How long the alert is shown. It Can be
+ between 0.25 and 5.0 seconds. Defaults to 1.8 seconds.
+ """
+ ...
+
+def write_link(title: str, link_url: str) -> None:
+ """Prints a tappable link to the console.
+
+ Args:
+ title (str): The title of the link to display.
+ link_url (str): The URL link should open.
+ """
+ ...
+
+def hide_output() -> None:
+ """Hides the console output area with a sliding animation."""
+ ...
+
+def quicklook(file_path: Union[str, Sequence[str]]) -> None:
+ """Shows a full-screen preview of local files.
+
+ The function returns when the preview is dismissed.
+
+ Args:
+ file_path (str or Path or Sequence): The path to a single file, or
+ a sequence of paths to preview multiple files.
+ """
+ ...
+
+def open_in(file_path: str) -> Optional[str]:
+ """Shows the iOS “Open in...” menu for the specified file.
+
+ Args:
+ file_path (str or Path): The path to the file.
+
+ Returns:
+ Optional[str]: The bundle identifier of the selected app, or None
+ if the menu was cancelled or no app can open the file.
+ """
+ ...
+
+def set_idle_timer_disabled(flag: bool) -> None:
+ """Disables or enables the idle timer.
+
+ Args:
+ flag (bool): If True, the idle timer is disabled (a device won't go to
+ sleep). If False, the idle timer is re-enabled.
+ """
+ ...
+
+def is_in_background() -> bool:
+ """Returns whether the app is currently running in the background.
+
+ Returns:
+ bool: True if the app is in the background, False otherwise.
+ """
+ ...
diff --git a/stubs/pythonista_stubs/contacts.pyi b/stubs/pythonista_stubs/contacts.pyi
new file mode 100644
index 0000000..e9b8d96
--- /dev/null
+++ b/stubs/pythonista_stubs/contacts.pyi
@@ -0,0 +1,172 @@
+"""
+This is a stub file for the `contacts` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+import datetime
+from typing import Dict, List, Optional, Tuple
+
+# -----------------------------------------------------------------------------
+# Constants for multi-value attributes
+# -----------------------------------------------------------------------------
+HOME: str = ...
+WORK: str = ...
+OTHER: str = ...
+IPHONE: str = ...
+MAIN_PHONE: str = ...
+HOME_FAX: str = ...
+WORK_FAX: str = ...
+OTHER_FAX: str = ...
+PAGER: str = ...
+FATHER: str = ...
+MOTHER: str = ...
+PARENT: str = ...
+BROTHER: str = ...
+SISTER: str = ...
+CHILD: str = ...
+FRIEND: str = ...
+SPOUSE: str = ...
+PARTNER: str = ...
+ASSISTANT: str = ...
+MANAGER: str = ...
+HOMEPAGE: str = ...
+STREET: str = ...
+CITY: str = ...
+STATE: str = ...
+ZIP: str = ...
+COUNTRY: str = ...
+COUNTRY_CODE: str = ...
+
+# -----------------------------------------------------------------------------
+# Person Objects
+# -----------------------------------------------------------------------------
+class Person:
+ """Person objects represent people in the address book."""
+
+ address: List[Tuple[str, Dict[str, str]]]
+ """Street address(es). The inner dictionary uses keys from the constants
+ section (e.g., STREET, CITY).
+ """
+ birthday: Optional[datetime.datetime]
+ """Birthday as a datetime object."""
+ creation_date: datetime.datetime
+ """When the person was added (readonly)."""
+ department: str
+ """Department name."""
+ email: List[Tuple[str, str]]
+ """Email address(es)."""
+ first_name: str
+ """First name."""
+ first_name_phonetic: str
+ """Phonetic first name."""
+ full_name: str
+ """The person’s full name (readonly)."""
+ id: int
+ """The persistent identifier of the person record (readonly)."""
+ image_data: Optional[bytes]
+ """The person’s image data (e.g., PNG or JPEG) or None."""
+ instant_message: List[Tuple[str, Dict[str, str]]]
+ """Instant message accounts."""
+ job_title: str
+ """Job title."""
+ kind: int
+ """The kind of address book record (0 for person, 1 for organization)."""
+ last_name: str
+ """Last name."""
+ last_name_phonetic: str
+ """Phonetic last name."""
+ middle_name: str
+ """Middle name."""
+ middle_name_phonetic: str
+ """Phonetic middle name."""
+ modification_date: datetime.datetime
+ """When the person was last modified (readonly)."""
+ nickname: str
+ """Nickname."""
+ note: str
+ """Additional notes."""
+ organization: str
+ """Organization name."""
+ phone: List[Tuple[str, str]]
+ """Phone number(s)."""
+ prefix: str
+ """Prefix (e.g., 'Sir')."""
+ related_names: List[Tuple[str, str]]
+ """Related names."""
+ social_profile: List[Tuple[str, Dict[str, str]]]
+ """Social profile(s)."""
+ suffix: str
+ """Suffix (e.g., 'Jr.')."""
+ url: List[Tuple[str, str]]
+ """URL(s)."""
+ vcard: str
+ """VCard representation of the person's data (readonly)."""
+
+# -----------------------------------------------------------------------------
+# Group Objects
+# -----------------------------------------------------------------------------
+class Group:
+ """A Group object represents a group in the address book."""
+
+ name: str
+ """The group’s name."""
+ id: int
+ """The persistent identifier of the group (readonly)."""
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+def get_group(group_id: int) -> Optional[Group]:
+ """Return the Group with the given id."""
+ ...
+
+def get_all_groups() -> List[Group]:
+ """Return a list of all Group objects in the address book."""
+ ...
+
+def add_group() -> Group:
+ """Add a new Group to the address book.
+ Returns:
+ Group: The newly created Group object.
+ """
+ ...
+
+def remove_group(group: Group) -> None:
+ """Remove a Group from the address book."""
+ ...
+
+def add_person(person: Person) -> None:
+ """Add a Person to the address book."""
+ ...
+
+def remove_person(person: Person) -> None:
+ """Remove a Person from the address book."""
+ ...
+
+def find(name: str) -> List[Person]:
+ """Do a prefix search for the given name and return a list of matches."""
+ ...
+
+def get_all_people() -> List[Person]:
+ """Return a list of all people in the address book."""
+ ...
+
+def get_person(person_id: int) -> Optional[Person]:
+ """Return the Person with the given id."""
+ ...
+
+def save() -> None:
+ """Save all pending changes in the contacts database."""
+ ...
+
+def revert() -> None:
+ """Revert all pending changes in the contacts database."""
+ ...
+
+def localized_label(label: str) -> str:
+ """Return a localized version of a label."""
+ ...
+
+def is_authorized() -> bool:
+ """Returns True if access to the address book is currently allowed."""
+ ...
diff --git a/stubs/pythonista_stubs/dialogs.pyi b/stubs/pythonista_stubs/dialogs.pyi
new file mode 100644
index 0000000..95c7711
--- /dev/null
+++ b/stubs/pythonista_stubs/dialogs.pyi
@@ -0,0 +1,280 @@
+"""
+This is a stub file for the `dialogs` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+import datetime
+from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, Literal
+
+# These are imported from the `console` module for convenience.
+# We'll alias the types for clarity.
+from .console import _HudIcon
+
+# These are imported from the `ui` module, which is part of Pythonista.
+# We'll provide a minimal stub for the types used.
+class Image: ...
+
+class TextField:
+ AUTOCAPITALIZE_SENTENCES: int = ...
+ # ... other autocapitalization types
+
+class ListDataSource:
+ items: List[Dict[str, Any]] = ...
+
+def alert(
+ title: str,
+ message: str = "",
+ button1: str = "OK",
+ button2: Optional[str] = None,
+ button3: Optional[str] = None,
+ hide_cancel_button: bool = False,
+) -> int:
+ """See console.alert()"""
+ ...
+
+def input_alert(
+ title: str,
+ message: str = "",
+ input: str = "",
+ ok_button_title: str = "OK",
+ hide_cancel_button: bool = False,
+) -> str:
+ """See console.input_alert()"""
+ ...
+
+def password_alert(
+ title: str,
+ message: str = "",
+ password: str = "",
+ ok_button_title: str = "OK",
+ hide_cancel_button: bool = False,
+) -> str:
+ """See console.password_alert()"""
+ ...
+
+def login_alert(
+ title: str,
+ message: str = "",
+ login: str = "",
+ password: str = "",
+ ok_button_title: str = "OK",
+) -> Tuple[str, str]:
+ """See console.login_alert()"""
+ ...
+
+def hud_alert(message: str, icon: _HudIcon = "success", duration: float = 1.8) -> None:
+ """See console.hud_alert()"""
+ ...
+
+# -----------------------------------------------------------------------------
+# Dialog Functions
+# -----------------------------------------------------------------------------
+
+def list_dialog(
+ title: str = "",
+ items: Optional[Union[Sequence[Any], List[Dict[str, Any]]]] = None,
+ multiple: bool = False,
+) -> Optional[Union[Any, List[Any]]]:
+ """Presents a list of items and returns the one(s) that were selected.
+
+ When the dialog is cancelled, None is returned. The `items` list can
+ contain any kind of object that can be converted to a string. To get more
+ control over how each item is displayed in the list, you can also use a
+ list of dictionaries (see ui.ListDataSource.items for details).
+
+ Args:
+ title (str, optional): The title of the dialog. Defaults to "".
+ items (Union[Sequence[Any], List[Dict[str, Any]]], optional):
+ The list of items to display. Defaults to None.
+ multiple (bool, optional): If True, allows multiple selections.
+ Defaults to False.
+
+ Returns:
+ Optional[Union[Any, List[Any]]]: The selected item(s), or None if the
+ dialog was canceled.
+ """
+ ...
+
+def edit_list_dialog(
+ title: str = "",
+ items: Optional[Sequence[Any]] = None,
+ move: bool = True,
+ delete: bool = True,
+) -> Optional[List[Any]]:
+ """Presents a list of items that can be edited by the user.
+
+ By default, the user can both rearrange the list and remove items; this
+ behavior can be controlled with the `move` and `delete` parameters.
+
+ Args:
+ title (str, optional): The title of the dialog. Defaults to "".
+ items (Sequence[Any], optional): The list of items to display.
+ Defaults to None.
+ move (bool, optional): If True, allows items to be rearranged.
+ Defaults to True.
+ delete (bool, optional): If True, allows items to be deleted.
+ Defaults to True.
+
+ Returns:
+ Optional[List[Any]]: The modified list of items, or None if the
+ dialog was cancelled.
+ """
+ ...
+
+# Field dictionaries are complex, so we'll type-hint them with a specific type alias.
+_FieldType = Literal[
+ "switch",
+ "text",
+ "url",
+ "email",
+ "password",
+ "number",
+ "check",
+ "datetime",
+ "date",
+ "time",
+]
+_FieldDict = Dict[str, Any]
+_SectionTuple = Tuple[str, List[_FieldDict], Optional[str]]
+
+def form_dialog(
+ title: str = "",
+ fields: Optional[List[_FieldDict]] = None,
+ sections: Optional[List[_SectionTuple]] = None,
+) -> Optional[Dict[str, Any]]:
+ """Presents a form dialog with customizable data input fields.
+
+ Args:
+ title (str, optional): The title of the dialog. Defaults to "".
+ fields (List[Dict[str, Any]], optional): A list of field dictionaries for
+ a single-section form. Use `sections` for multiple sections.
+ Defaults to None.
+ sections (List[Tuple[str, List[Dict[str, Any]], Optional[str]]], optional):
+ A list of tuples, where each tuple represents a section.
+ Defaults to None.
+
+ Returns:
+ Optional[Dict[str, Any]]: A dictionary of values for each field,
+ or None if the dialog was cancelled.
+ """
+ ...
+
+def text_dialog(
+ title: str = "",
+ text: str = "",
+ font: Union[Tuple[str, int], Tuple[str], str] = ("", 16),
+ autocorrection: Optional[bool] = None,
+ autocapitalization: int = TextField.AUTOCAPITALIZE_SENTENCES,
+ spellchecking: Optional[bool] = None,
+) -> Optional[str]:
+ """Shows a multi-line text editor sheet.
+
+ Args:
+ title (str, optional): The title of the dialog. Defaults to "".
+ text (str, optional): The initial text in the editor. Defaults to "".
+ font (Union[Tuple[str, int], Tuple[str], str], optional):
+ The font and size. Defaults to ('', 16).
+ autocorrection (Optional[bool], optional): Whether auto-correction
+ should be enabled. Defaults to None.
+ autocapitalization (int, optional): The auto-capitalization behavior.
+ Defaults to ui.AUTOCAPITALIZE_SENTENCES.
+ spellchecking (Optional[bool], optional): Whether spell checking
+ should be enabled. Defaults to None.
+
+ Returns:
+ Optional[str]: The edited text, or None if the dialog was cancelled.
+ """
+ ...
+
+def date_dialog(title: str = "") -> Optional[datetime.datetime]:
+ """Shows a date picker dialog.
+
+ Args:
+ title (str, optional): The title of the dialog. Defaults to "".
+
+ Returns:
+ Optional[datetime.datetime]: A datetime.datetime object with the selected
+ date, or None if the dialog was cancelled.
+ """
+ ...
+
+def time_dialog(title: str = "") -> Optional[datetime.datetime]:
+ """Shows a time picker dialog.
+
+ Args:
+ title (str, optional): The title of the dialog. Defaults to "".
+
+ Returns:
+ Optional[datetime.datetime]: A datetime.datetime object with the selected
+ time, or None if the dialog was cancelled.
+ """
+ ...
+
+def datetime_dialog(title: str = "") -> Optional[datetime.datetime]:
+ """Shows a date and time picker dialog.
+
+ Args:
+ title (str, optional): The title of the dialog. Defaults to "".
+
+ Returns:
+ Optional[datetime.datetime]: A datetime.datetime object with the selected
+ date and time, or None if the dialog was cancelled.
+ """
+ ...
+
+def duration_dialog(title: str = "") -> Optional[float]:
+ """Shows a duration picker dialog (e.g. for a countdown timer).
+
+ Args:
+ title (str, optional): The title of the dialog. Defaults to "".
+
+ Returns:
+ Optional[float]: The selected duration in seconds, or None if the
+ dialog was cancelled.
+ """
+ ...
+
+# -----------------------------------------------------------------------------
+# Sharing Functions
+# -----------------------------------------------------------------------------
+
+def share_image(img: Union[Image, Any]) -> None:
+ """Shows the system sharing dialog for a given image.
+
+ Args:
+ img (Union[ui.Image, PIL.Image.Image]): The image to share.
+ """
+ ...
+
+def share_text(text: str) -> None:
+ """Shows the system sharing dialog for a given string.
+
+ Args:
+ text (str): The text to share.
+ """
+ ...
+
+def share_url(url: str) -> None:
+ """Shows the system sharing dialog for a given URL.
+
+ Args:
+ url (str): The URL to share.
+ """
+ ...
+
+# -----------------------------------------------------------------------------
+# Importing Files
+# -----------------------------------------------------------------------------
+
+def pick_document(types: List[str] = ["public.data"]) -> Optional[str]:
+ """Shows the system’s document picker for importing a file.
+
+ Args:
+ types (List[str], optional): Universal Type Identifiers (UTIs) for
+ file types that should be selectable. Defaults to ['public.data'].
+
+ Returns:
+ Optional[str]: The path to the selected temporary file, or None if
+ the dialog was cancelled.
+ """
+ ...
diff --git a/stubs/pythonista_stubs/editor.pyi b/stubs/pythonista_stubs/editor.pyi
new file mode 100644
index 0000000..952506e
--- /dev/null
+++ b/stubs/pythonista_stubs/editor.pyi
@@ -0,0 +1,156 @@
+"""
+This is a stub file for the `editor` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import Optional, Tuple, Literal
+
+# We'll use a minimal stub for the ui.View type from the ui module.
+class View: ...
+
+def get_path() -> Optional[str]:
+ """Returns the absolute file path of the script that is currently open in the editor.
+
+ Returns:
+ Optional[str]: The absolute file path, or None if no script is open.
+ """
+ ...
+
+def get_text() -> str:
+ """Returns the entire text of the script that is currently being edited.
+
+ Returns:
+ str: The full text content of the editor.
+ """
+ ...
+
+def get_selection() -> Optional[Tuple[int, int]]:
+ """Returns the selected range as a tuple of the form (start, end).
+
+ The `start` and `end` values are character indices.
+
+ Returns:
+ Optional[Tuple[int, int]]: The start and end indices of the selection,
+ or None if no file is currently open.
+ """
+ ...
+
+def get_line_selection() -> Optional[Tuple[int, int]]:
+ """Returns the range of all lines that are part of the current selection.
+
+ Returns:
+ Optional[Tuple[int, int]]: The start and end indices of the line selection,
+ or None if no file is currently open.
+ """
+ ...
+
+def set_selection(start: int, end: Optional[int] = None, scroll: bool = False) -> None:
+ """Sets the selected range in the editor.
+
+ Args:
+ start (int): The starting character index of the selection.
+ end (Optional[int], optional): The ending character index of the selection.
+ If None, the caret is positioned at `start` with no text selected.
+ scroll (bool, optional): If True, scrolls the view to make the selection
+ visible. Defaults to False.
+ """
+ ...
+
+def replace_text(start: int, end: int, replacement: str) -> None:
+ """Replaces the text in the given range with a new string.
+
+ To insert/append text, a zero-length range can be used. All changes can be
+ undone by the user (using the regular undo key).
+
+ Args:
+ start (int): The starting character index of the range to replace.
+ end (int): The ending character index of the range to replace.
+ replacement (str): The new text to insert.
+ """
+ ...
+
+def make_new_file(name: Optional[str] = None, content: Optional[str] = None) -> None:
+ """Creates a new file and opens it in the editor.
+
+ If a file with the given name already exists, a numeric suffix is
+ automatically appended.
+
+ Args:
+ name (Optional[str], optional): The desired name for the new file.
+ Defaults to None.
+ content (Optional[str], optional): The initial content of the new file.
+ If omitted, an empty file is created. Defaults to None.
+ """
+ ...
+
+def open_file(name: str, new_tab: bool = False) -> None:
+ """Opens the file with the given name in the editor.
+
+ Args:
+ name (str): The path to the file. It can be relative to the script
+ library’s root directory or an absolute path. The .py extension
+ can be omitted.
+ new_tab (bool, optional): If True, the file is opened in a new tab.
+ Defaults to False.
+ """
+ ...
+
+def apply_ui_theme(ui_view: View, theme_name: Optional[str] = None) -> None:
+ """Styles a ui.View (and its descendants) with the given UI theme.
+
+ Args:
+ ui_view (ui.View): The view to be styled.
+ theme_name (Optional[str], optional): The name of the theme. If None,
+ the currently selected theme is used. Defaults to None.
+ """
+ ...
+
+def present_themed(ui_view: View, theme_name: Optional[str] = None, **kwargs) -> None:
+ """Styles a ui.View and presents it.
+
+ This function combines `apply_ui_theme()` and `ui.View.present()`.
+ Keyword arguments are passed on to `ui.View.present()`.
+
+ Args:
+ ui_view (ui.View): The view to be styled and presented.
+ theme_name (Optional[str], optional): The name of the theme. If None,
+ the currently selected theme is used. Defaults to None.
+ """
+ ...
+
+_AnnotationStyle = Literal["success", "warning", "error"]
+
+def annotate_line(
+ lineno: int,
+ text: str = "",
+ style: _AnnotationStyle = "warning",
+ expanded: bool = True,
+ filename: Optional[str] = None,
+ scroll: bool = False,
+) -> None:
+ """Annotates a line of code in the editor with a label.
+
+ Args:
+ lineno (int): The 1-based line number to annotate.
+ text (str, optional): The text of the annotation. Defaults to ''.
+ style (Literal['success', 'warning', 'error'], optional): The style of
+ the annotation. Defaults to 'warning'.
+ expanded (bool, optional): If False, only an icon is shown; tapping
+ shows the text. Defaults to True.
+ filename (Optional[str], optional): The path to the file to annotate.
+ If None, the file currently open in the editor is used.
+ Defaults to None.
+ scroll (bool, optional): If True, scrolls to the annotated line.
+ Defaults to False.
+ """
+ ...
+
+def clear_annotations(filename: Optional[str] = None) -> None:
+ """Removes all annotations that were added via `annotate_line()`.
+
+ Args:
+ filename (Optional[str], optional): The path to the file from which to
+ clear annotations. If None, the file currently open is used.
+ Defaults to None.
+ """
+ ...
diff --git a/stubs/pythonista_stubs/keyboard.pyi b/stubs/pythonista_stubs/keyboard.pyi
new file mode 100644
index 0000000..c3d4cf8
--- /dev/null
+++ b/stubs/pythonista_stubs/keyboard.pyi
@@ -0,0 +1,82 @@
+"""
+This is a stub file for the `keyboard` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import List, Literal, Optional, Tuple
+
+# These are imported from the `ui` module, which is part of Pythonista.
+class View:
+ def __init__(self, *args, **kwargs): ...
+ def add_subview(self, view: "View") -> None: ...
+ def remove_subview(self, view: "View") -> None: ...
+ def present(self, style: str = "sheet", animated: bool = True) -> None: ...
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+_Appearance = Literal["dark", "light"]
+_Mode = Literal["current", "minimized", "expanded"]
+
+def backspace(times: int = 1) -> None:
+ """Delete backwards in the current document.
+ Args:
+ times (int, optional): The number of characters to delete. Defaults to 1.
+ """
+ ...
+
+def get_appearance() -> _Appearance:
+ """Return the current appearance of the keyboard ('dark' or 'light')."""
+ ...
+
+def get_document_id() -> str:
+ """Return a unique identifier (UUID) for the current document."""
+ ...
+
+def get_input_context() -> Tuple[str, str]:
+ """Return a 2-tuple with the text immediately before and after the cursor."""
+ ...
+
+def get_selected_text() -> str:
+ """Return the currently selected text or an empty string."""
+ ...
+
+def get_text_replacements() -> Optional[List[Tuple[str, str]]]:
+ """Return a list of text replacements.
+ Returns:
+ Optional[List[Tuple[str, str]]]: A list of (phrase, shortcut) tuples,
+ or None if not running in the keyboard.
+ """
+ ...
+
+def has_full_access() -> bool:
+ """Return True if 'Full Access' is enabled for the keyboard."""
+ ...
+
+def has_text() -> bool:
+ """Return True if the document being edited contains any text."""
+ ...
+
+def insert_text(text: str) -> None:
+ """Insert text in the current document."""
+ ...
+
+def is_keyboard() -> bool:
+ """Return True if the script is running in the custom Pythonista keyboard."""
+ ...
+
+def move_cursor(offset: int) -> None:
+ """Move the cursor by a specified offset."""
+ ...
+
+def play_input_click() -> None:
+ """Play an input click sound."""
+ ...
+
+def set_view(view: Optional[View] = None, mode: _Mode = "current") -> None:
+ """Sets a custom ui.View as the keyboard's UI.
+ Args:
+ view (ui.View, optional): The view to display. Pass None to close.
+ mode (str, optional): The presentation mode ('minimized', 'expanded', or 'current').
+ """
+ ...
diff --git a/stubs/pythonista_stubs/keychain.pyi b/stubs/pythonista_stubs/keychain.pyi
new file mode 100644
index 0000000..bbee4fc
--- /dev/null
+++ b/stubs/pythonista_stubs/keychain.pyi
@@ -0,0 +1,57 @@
+"""Secure Password Storage.
+
+This module provides simple access to secure password storage.
+
+Note:
+ The keychain is not shared between apps, so you cannot use this to
+ access passwords stored in Safari's keychain, for example.
+"""
+
+from typing import Optional, List, Tuple, Any
+
+def get_password(service: str, account: str) -> Optional[str]:
+ """Get a password from the keychain.
+
+ Args:
+ service: The name of the service associated with the password.
+ account: The name of the user account associated with the password.
+
+ Returns:
+ The password as a string, or `None` if no password is found
+ for the given service and account.
+ """
+ ...
+
+def set_password(service: str, account: str, password: str) -> None:
+ """Save a password to the keychain.
+
+ This saves a password for the given service and account. If a password
+ already exists for this service/account pair, it will be overwritten.
+
+ Args:
+ service: The name of the service to associate with the password.
+ account: The name of the user account.
+ password: The password to be stored.
+ """
+ ...
+
+def delete_password(service: str, account: str) -> None:
+ """Delete a password from the keychain.
+
+ This deletes the password for the given service and account.
+
+ Args:
+ service: The name of the service.
+ account: The name of the user account.
+ """
+ ...
+
+def reset_keychain() -> None:
+ """Delete all data from the keychain.
+
+ This is a destructive operation that removes all passwords
+ stored by the current application.
+ """
+ ...
+
+def get_services() -> List[Tuple[Any, Any]]: ...
diff --git a/stubs/pythonista_stubs/linguistictagger.pyi b/stubs/pythonista_stubs/linguistictagger.pyi
new file mode 100644
index 0000000..5393eac
--- /dev/null
+++ b/stubs/pythonista_stubs/linguistictagger.pyi
@@ -0,0 +1,45 @@
+"""
+This is a stub file for the `linguistictagger` module, providing type hints for
+its functions and their parameters, to be used for static analysis and
+autocompletion.
+"""
+
+from typing import List, Literal, Tuple
+
+# -----------------------------------------------------------------------------
+# Constants
+# -----------------------------------------------------------------------------
+SCHEME_TOKEN_TYPE: str = ...
+SCHEME_LEXICAL_CLASS: str = ...
+SCHEME_NAME_TYPE: str = ...
+SCHEME_NAME_TYPE_OR_LEXICAL_CLASS: str = ...
+SCHEME_LEMMA: str = ...
+SCHEME_LANGUAGE: str = ...
+SCHEME_SCRIPT: str = ...
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+_Scheme = Literal[
+ "Token Type",
+ "Lexical Class",
+ "Name Type",
+ "Name Type or Lexical Class",
+ "Lemma",
+ "Language",
+ "Script",
+]
+
+def tag_string(
+ string: str,
+ scheme: _Scheme,
+) -> List[Tuple[str, str, Tuple[int, int]]]:
+ """Tag a given string according to the scheme.
+ Args:
+ string (str): The text to be tagged.
+ scheme (str): The tagging scheme to use.
+ Returns:
+ List[Tuple[str, str, Tuple[int, int]]]: A list of (tag, substring, range)
+ tuples.
+ """
+ ...
diff --git a/stubs/pythonista_stubs/location.pyi b/stubs/pythonista_stubs/location.pyi
new file mode 100644
index 0000000..50594b5
--- /dev/null
+++ b/stubs/pythonista_stubs/location.pyi
@@ -0,0 +1,82 @@
+"""
+This is a stub file for the `location` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import Dict, List, Optional, Literal
+
+# These are imported from the `ui` module, which is part of Pythonista.
+class Image: ...
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+_MapType = Literal["standard", "satellite", "hybrid"]
+
+def get_location() -> Optional[Dict[str, float]]:
+ """Return the most recently obtained location data.
+ Returns:
+ Optional[Dict[str, float]]: A dictionary with 'longitude', 'latitude',
+ and 'timestamp' keys, or None.
+ """
+ ...
+
+def start_updates() -> None:
+ """Start updating location data."""
+ ...
+
+def stop_updates() -> None:
+ """Stop updating location data."""
+ ...
+
+def geocode(address: Dict[str, str]) -> List[Dict[str, float]]:
+ """Convert an address dictionary to geo-coordinates.
+ Args:
+ address (Dict[str, str]): A dictionary with address components (e.g.,
+ 'Street', 'City', 'Country').
+ Returns:
+ List[Dict[str, float]]: A list of dictionaries with 'longitude' and
+ 'latitude' keys.
+ """
+ ...
+
+def render_map_snapshot(
+ lat: float,
+ lng: float,
+ width: float = 1000.0,
+ height: float = 1000.0,
+ map_type: _MapType = "standard",
+ show_poi: bool = True,
+ img_width: int = 240,
+ img_height: int = 240,
+ img_scale: int = 0,
+) -> Image:
+ """Render a snapshot image of the given map region.
+ Args:
+ lat (float): Latitude of the map's center.
+ lng (float): Longitude of the map's center.
+ width (float, optional): Width of the map region in meters.
+ height (float, optional): Height of the map region in meters.
+ map_type (str, optional): Map type ('standard', 'satellite', or 'hybrid').
+ show_poi (bool, optional): Whether to show points of interest.
+ img_width (int, optional): Width of the returned image in points.
+ img_height (int, optional): Height of the returned image in points.
+ img_scale (int, optional): Scale factor of the image (0 for device scale).
+ Returns:
+ ui.Image: The rendered map snapshot.
+ """
+ ...
+
+def reverse_geocode(location: Dict[str, float]) -> List[Dict[str, str]]:
+ """Convert geo-coordinates to a human-readable address.
+ Args:
+ location (Dict[str, float]): A dictionary with 'longitude' and
+ 'latitude' keys.
+ Returns:
+ List[Dict[str, str]]: A list of possible address dictionaries.
+ """
+ ...
+
+def is_authorized() -> bool:
+ """Returns True if access to location data is currently allowed."""
+ ...
diff --git a/stubs/pythonista_stubs/motion.pyi b/stubs/pythonista_stubs/motion.pyi
new file mode 100644
index 0000000..883b058
--- /dev/null
+++ b/stubs/pythonista_stubs/motion.pyi
@@ -0,0 +1,33 @@
+"""
+This is a stub file for the `motion` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import Tuple
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+def start_updates() -> None:
+ """Start monitoring the device's motion sensors."""
+ ...
+
+def stop_updates() -> None:
+ """Stop monitoring the device's motion sensors."""
+ ...
+
+def get_gravity() -> Tuple[float, float, float]:
+ """Return the gravity vector (x, y, z)."""
+ ...
+
+def get_user_acceleration() -> Tuple[float, float, float]:
+ """Return the acceleration the user is giving to the device."""
+ ...
+
+def get_attitude() -> Tuple[float, float, float]:
+ """Return the attitude of the device (roll, pitch, yaw)."""
+ ...
+
+def get_magnetic_field() -> Tuple[float, float, float, float]:
+ """Return the magnetic field vector with respect to the device (x, y, z, accuracy)."""
+ ...
diff --git a/stubs/pythonista_stubs/notification.pyi b/stubs/pythonista_stubs/notification.pyi
new file mode 100644
index 0000000..07a6a55
--- /dev/null
+++ b/stubs/pythonista_stubs/notification.pyi
@@ -0,0 +1,68 @@
+"""
+This is a stub file for the `notification` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import Dict, List, Optional, Union
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+_Action = Dict[str, Union[str, bool]]
+_Trigger = Dict[str, Union[int, float, bool]]
+
+def schedule(
+ message: Optional[str] = None,
+ delay: float = 0,
+ sound_name: Optional[str] = None,
+ action_url: Optional[str] = None,
+ title: Optional[str] = None,
+ subtitle: Optional[str] = None,
+ attachments: Optional[List[str]] = None,
+ trigger: Optional[_Trigger] = None,
+ actions: Optional[List[_Action]] = None,
+ identifier: Optional[str] = None,
+) -> str:
+ """Schedule a notification.
+ Args:
+ message (str, optional): The main text of the notification.
+ delay (float, optional): The time in seconds until delivery. Defaults to 0.
+ sound_name (str, optional): The sound to play. Use 'default' for the
+ default sound or None for silent.
+ action_url (str, optional): The URL to launch when the notification is tapped.
+ title (str, optional): The title of the notification.
+ subtitle (str, optional): The subtitle of the notification.
+ attachments (List[str], optional): A list of file paths to attach.
+ trigger (dict, optional): A dictionary for more complex triggers.
+ actions (List[dict], optional): Definitions for custom action buttons.
+ identifier (str, optional): An optional identifier for the notification.
+ Returns:
+ str: The identifier of the scheduled notification.
+ """
+ ...
+
+def cancel(identifier: str) -> None:
+ """Cancel a previously scheduled notification.
+ Args:
+ identifier (str): The identifier of the notification to cancel.
+ """
+ ...
+
+def cancel_all() -> None:
+ """Cancel all previously scheduled notifications."""
+ ...
+
+def get_scheduled() -> List[str]:
+ """Return a list of scheduled notification identifiers."""
+ ...
+
+def remove_delivered(identifier: str) -> None:
+ """Remove a specific delivered notification from Notification Center.
+ Args:
+ identifier (str): The identifier of the notification to remove.
+ """
+ ...
+
+def remove_all_delivered() -> None:
+ """Remove all delivered notifications from Notification Center."""
+ ...
diff --git a/stubs/pythonista_stubs/objc_util.pyi b/stubs/pythonista_stubs/objc_util.pyi
new file mode 100644
index 0000000..c8daa00
--- /dev/null
+++ b/stubs/pythonista_stubs/objc_util.pyi
@@ -0,0 +1,220 @@
+"""Utilities for bridging Objective-C APIs.
+
+This module provides a "bridge" for using Objective-C APIs from Python.
+Based on ctypes and the Objective-C runtime library, objc_util allows
+you to "wrap" existing Objective-C classes in a way that automatically
+converts Python method calls to corresponding Objective-C messages.
+"""
+
+from typing import (
+ Any,
+ Callable,
+ List,
+ Optional,
+ TypeVar,
+)
+
+# A type variable for the decorator to preserve function signatures.
+F = TypeVar("F", bound=Callable)
+
+class ObjCClass:
+ """Wrapper for an Objective-C class.
+
+ Acts as a proxy for calling Objective-C class methods. Method calls are
+ converted to Objective-C messages on-the-fly. This is done by replacing
+ underscores in the method name with colons in the selector name, and
+ using the selector and arguments for a call to the low-level objc_msgSend()
+ function.
+
+ Example:
+ Calling `NSDictionary.dictionaryWithObject_forKey_(obj, key)` in Python is
+ translated to `[NSDictionary dictionaryWithObject:obj forKey:key]`
+ in Objective-C.
+
+ Args:
+ name: The name of the Objective-C class as a string.
+ """
+
+ def __init__(self, name: str) -> None:
+ pass
+
+class ObjCInstance:
+ """Wrapper for a pointer to an Objective-C object.
+
+ Acts as a proxy for sending messages to the object. Method calls are converted
+ to Objective-C messages on-the-fly.
+
+ Example:
+ Calling `obj.setFoo_withBar_(foo, bar)` in Python is translated to
+ `[obj setFoo:foo withBar:bar]` in Objective-C.
+
+ Args:
+ ptr: A pointer to the Objective-C object.
+ """
+
+ def __init__(self, ptr: Any) -> None:
+ pass
+
+class ObjCBlock:
+ """Wrapper for Objective-C blocks (closures).
+
+ Note:
+ Block support is experimental.
+
+ Args:
+ func: The Python function to wrap as a block.
+ restype: The return type of the block (e.g., `NSInteger`).
+ argtypes: A list of argument types for the block.
+ """
+
+ def __init__(
+ self, func: Callable, restype: Any = None, argtypes: Optional[List[Any]] = None
+ ) -> None:
+ pass
+
+def autoreleasepool() -> Any:
+ """A context manager for NSAutoreleasePool.
+
+ This acts as a wrapper for `NSAutoreleasePool` (similar to
+ `@autoreleasepool {...}` in Objective-C).
+
+ Usage:
+ with objc_util.autoreleasepool():
+ # do stuff...
+
+ Returns:
+ A context manager for an autorelease pool.
+ """
+ ...
+
+def create_objc_class(
+ name: str,
+ superclass: ObjCClass = ...,
+ methods: Optional[List[Callable]] = None,
+ classmethods: Optional[List[Callable]] = None,
+ protocols: Optional[List[str]] = None,
+ debug: bool = True,
+) -> ObjCClass:
+ """Create and return a new ObjCClass.
+
+ The selector name is derived from the name of the function. The return and
+ argument types are inferred automatically from the superclass or protocols
+ if possible.
+
+ Args:
+ name: The name of the class to create.
+ superclass: The ObjCClass object from which the new class inherits.
+ methods: A list of functions for instance methods.
+ classmethods: A list of functions for class methods.
+ protocols: A list of protocol names (strings) for type hinting.
+ debug: If `True`, a new name will be chosen automatically if a class
+ with `name` already exists.
+
+ Returns:
+ A new ObjCClass object.
+ """
+ ...
+
+def load_framework(name: str) -> None:
+ """Load the system framework with the given name.
+
+ Args:
+ name: The name of the framework (e.g., 'SceneKit').
+ """
+ ...
+
+def ns(obj: Any) -> Any:
+ """Convert a Python object to its Objective-C equivalent.
+
+ Converts `str` to `NSString`, `list` to `NSMutableArray`, `dict` to
+ `NSMutableDictionary`, etc. Nested structures are supported.
+
+ Args:
+ obj: The Python object to convert.
+
+ Returns:
+ The Objective-C equivalent of the input object, wrapped in an
+ ObjCInstance if applicable.
+ """
+ ...
+
+def nsurl(url_or_path: str) -> ObjCInstance:
+ """Convert a Python string to an NSURL object.
+
+ Args:
+ url_or_path: A string representing a URL or a file path.
+
+ Returns:
+ An `NSURL` object wrapped in an `ObjCInstance`.
+ """
+ ...
+
+def nsdata_to_bytes(data: ObjCInstance) -> bytes:
+ """Convert an NSData object to a Python byte string.
+
+ Args:
+ data: An `NSData` object wrapped in an `ObjCInstance`.
+
+ Returns:
+ A Python byte string.
+ """
+ ...
+
+def uiimage_to_png(img: ObjCInstance) -> bytes:
+ """Convert a UIImage object to a Python byte string with PNG data.
+
+ Args:
+ img: A `UIImage` object wrapped in an `ObjCInstance`.
+
+ Returns:
+ A Python byte string containing PNG data.
+ """
+ ...
+
+def on_main_thread(func: F) -> F:
+ """Decorator to call a function on the UIKit main thread.
+
+ This is typically used to decorate another function, but can also be used
+ ad-hoc for dispatching a function call to the main thread.
+
+ Args:
+ func: The function to be executed on the main thread.
+
+ Returns:
+ The decorated function.
+ """
+ ...
+
+def sel(name: str) -> Any:
+ """Convert a Python string to an Objective-C selector.
+
+ Args:
+ name: The name of the selector as a string.
+
+ Returns:
+ An Objective-C selector object.
+ """
+ ...
+
+# Convenience class wrappers for common Objective-C types
+# These are included as module-level objects for convenience.
+class CGPoint: ...
+class CGSize: ...
+class CGVector: ...
+class CGRect: ...
+class CGAffineTransform: ...
+class UIEdgeInsets: ...
+class NSRange: ...
+class NSDictionary(ObjCClass): ...
+class NSMutableDictionary(ObjCClass): ...
+class NSArray(ObjCClass): ...
+class NSMutableArray(ObjCClass): ...
+class NSSet(ObjCClass): ...
+class NSMutableSet(ObjCClass): ...
+class NSString(ObjCClass): ...
+class NSMutableString(ObjCClass): ...
+class NSData(ObjCClass): ...
+class NSMutableData(ObjCClass): ...
+class NSNumber(ObjCClass): ...
+class NSURL(ObjCClass): ...
+class NSEnumerator(ObjCClass): ...
diff --git a/stubs/pythonista_stubs/photos.pyi b/stubs/pythonista_stubs/photos.pyi
new file mode 100644
index 0000000..a9c6e62
--- /dev/null
+++ b/stubs/pythonista_stubs/photos.pyi
@@ -0,0 +1,200 @@
+"""
+This is a stub file for the `photos` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+import datetime
+import io
+from typing import List, Optional, Tuple, Literal, Union
+
+# These are imported from the `PIL` and `ui` modules.
+class Image: ...
+class ui_Image: ...
+
+# -----------------------------------------------------------------------------
+# Asset Class
+# -----------------------------------------------------------------------------
+class Asset:
+ """Represents a single media item in the photo library."""
+
+ def get_image(self, original: bool = False) -> Image:
+ """Fetch the asset's image data as a PIL.Image object."""
+ ...
+
+ def get_image_data(self, original: bool = False) -> io.BytesIO:
+ """Fetch the asset's image data as an io.BytesIO object."""
+ ...
+
+ def get_ui_image(
+ self, size: Optional[Tuple[int, int]] = None, crop: bool = False
+ ) -> ui_Image:
+ """
+ Fetch the asset's image data as a ui.Image object.
+
+ Args:
+ size (Optional[Tuple[int, int]]): The desired size of the returned image,
+ specified as a tuple of (width, height). If None, the original
+ image dimensions are used.
+ crop (bool): If True, the image will be cropped to fit the specified
+ size while maintaining its aspect ratio. If False, the image will
+ be resized and may be distorted. Defaults to False.
+
+ Returns:
+ ui_Image: The asset's image data as a ui.Image object.
+ """
+ ...
+
+ def edit_content(self, jpeg_path: str) -> None:
+ """Replace the asset's image content with the given JPEG file."""
+ ...
+
+ def delete(self) -> None:
+ """Delete the asset from the photo library."""
+ ...
+
+ def revert(self) -> None:
+ """Revert the asset to its original state."""
+ ...
+
+ local_id: str
+ """A unique identifier of this asset (read-only)."""
+ pixel_width: int
+ """The width of the asset in pixels (read-only)."""
+ pixel_height: int
+ """The height of the asset in pixels (read-only)."""
+ media_type: Literal["image", "video"]
+ """The asset's media type (read-only)."""
+ media_subtypes: List[str]
+ """The asset's media subtypes (read-only)."""
+ creation_date: datetime.datetime
+ """The asset's creation date (read-write)."""
+ modification_date: datetime.datetime
+ """The asset's modification date (read-write)."""
+ hidden: bool
+ """Whether the asset is hidden (read-write)."""
+ favorite: bool
+ """Whether the asset is a favorite (read-write)."""
+ duration: float
+ """The duration of a video asset in seconds (read-only)."""
+ location: Optional[dict]
+ """The geo-location where the media was taken (read-write)."""
+ can_edit_content: bool
+ """Whether the asset's content can be modified (read-only)."""
+ can_edit_properties: bool
+ """Whether the asset's metadata can be modified (read-only)."""
+ can_delete: bool
+ """Whether the asset can be deleted (read-only)."""
+
+# -----------------------------------------------------------------------------
+# AssetCollection Class
+# -----------------------------------------------------------------------------
+class AssetCollection:
+ """Represents a collection in the photo library (album or smart album)."""
+
+ def delete(self) -> None:
+ """Delete the asset collection from the photo library."""
+ ...
+
+ def add_assets(self, assets: List[Asset]) -> None:
+ """Add a list of Asset objects to the album."""
+ ...
+
+ def remove_assets(self, assets: List[Asset]) -> None:
+ """Remove a list of Asset objects from the album."""
+ ...
+
+ assets: List[Asset]
+ """The assets that the collection contains (read-only)."""
+ local_id: str
+ """A unique identifier of this asset collection (read-only)."""
+ title: str
+ """The title of the asset collection (read-write)."""
+ type: Literal["album", "smart_album", "moment"]
+ """The type of the asset collection (read-only)."""
+ subtype: str
+ """The subtype of the asset collection (read-only)."""
+ start_date: datetime.datetime
+ """The earliest creation date of an asset in the collection (read-only)."""
+ end_date: datetime.datetime
+ """The latest creation date of an asset in the collection (read-only)."""
+ can_delete: bool
+ """Whether the collection can be deleted (read-only)."""
+ can_add_assets: bool
+ """Whether the collection allows adding assets (read-only)."""
+ can_remove_assets: bool
+ """Whether the collection allows removing assets (read-only)."""
+ can_rename: bool
+ """Whether the collection's title can be changed (read-only)."""
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+_MediaType = Literal["image", "video"]
+_CameraType = Literal["rear", "front"]
+_MapType = Literal["standard", "satellite", "hybrid"]
+
+def capture_image(camera: _CameraType = "rear") -> Optional[Image]:
+ """Show a standard camera interface and return the captured image."""
+ ...
+
+def get_assets(
+ media_type: _MediaType = "image", include_hidden: bool = False
+) -> List[Asset]:
+ """Fetch and return a list of all assets in the library."""
+ ...
+
+def get_asset_with_local_id(local_id: str) -> Asset:
+ """Fetch and return the asset with the given local identifier."""
+ ...
+
+def get_albums() -> List[AssetCollection]:
+ """Return a list of all regular albums in the photo library."""
+ ...
+
+def get_smart_albums() -> List[AssetCollection]:
+ """Return a list of all smart albums in the photo library."""
+ ...
+
+def get_moments() -> List[AssetCollection]:
+ """Return a list of all 'moments' in the photo library."""
+ ...
+
+def get_favorites_album() -> AssetCollection:
+ """Return the smart album containing all favorite assets."""
+ ...
+
+def get_recently_added_album() -> AssetCollection:
+ """Return the smart album containing recently added assets."""
+ ...
+
+def get_selfies_album() -> AssetCollection:
+ """Return the smart album containing all assets taken with the front camera."""
+ ...
+
+def get_screenshots_album() -> AssetCollection:
+ """Return the smart album containing all screenshots."""
+ ...
+
+def batch_delete(assets: List[Asset]) -> None:
+ """Delete multiple assets from the photo library."""
+ ...
+
+def batch_revert(assets: List[Asset]) -> None:
+ """Revert multiple assets to their original state."""
+ ...
+
+def create_album(title: str) -> AssetCollection:
+ """Create and return a new album in the photo library."""
+ ...
+
+def create_image_asset(image_path: str) -> Asset:
+ """Create and return a new image asset from a file."""
+ ...
+
+def pick_asset(
+ assets: Optional[Union[List[Asset], AssetCollection]] = None,
+ title: str = "",
+ multi: bool = False,
+) -> Union[Optional[Asset], Optional[List[Asset]]]:
+ """Show a dialog with a grid of thumbnails for the given assets."""
+ ...
diff --git a/stubs/pythonista_stubs/py.typed b/stubs/pythonista_stubs/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/stubs/pythonista_stubs/reminders.pyi b/stubs/pythonista_stubs/reminders.pyi
new file mode 100644
index 0000000..db9a0cd
--- /dev/null
+++ b/stubs/pythonista_stubs/reminders.pyi
@@ -0,0 +1,121 @@
+"""
+This is a stub file for the `reminders` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import List, Optional, Tuple, Literal, Union
+import datetime
+
+# -----------------------------------------------------------------------------
+# Alarm Objects
+# -----------------------------------------------------------------------------
+class Alarm:
+ """Alarm objects represent an alarm associated with a reminder."""
+
+ date: Optional[datetime.datetime]
+ """The absolute date when the alarm is triggered."""
+ location: Optional[Union[Tuple[str, float, float], Tuple[str, float, float, float]]]
+ """The title, coordinates, and radius for a geo-location-based alarm.
+ Represented as a 3- or 4-tuple: (title, latitude, longitude[, radius]).
+ """
+ proximity: Literal["enter", "leave", "none"]
+ """Determines if a location-based alarm is triggered when entering or
+ leaving the specified area.
+ """
+
+# -----------------------------------------------------------------------------
+# Reminder Objects
+# -----------------------------------------------------------------------------
+class Reminder:
+ """Reminder objects represent a single reminder in a list."""
+
+ def __init__(self, calendar: Optional["Calendar"] = None): ...
+
+ alarms: List[Alarm]
+ """A list of Alarm objects associated with this reminder."""
+ completed: bool
+ """Whether the reminder has been completed (checked off) yet."""
+ completion_date: Optional[datetime.datetime]
+ """The date when the reminder was completed, or None if not completed."""
+ due_date: Optional[datetime.datetime]
+ """The due date of the reminder."""
+ notes: str
+ """Additional notes for the reminder."""
+ priority: int
+ """Priority of the reminder (0 = no priority, 1 = highest, 9 = lowest)."""
+ title: str
+ """The title of the reminder."""
+ url: str
+ """A URL associated with the reminder."""
+
+ def save(self) -> None:
+ """Save changes to the database."""
+ ...
+
+# -----------------------------------------------------------------------------
+# Calendar Objects
+# -----------------------------------------------------------------------------
+class Calendar:
+ """Calendar objects represent lists of reminders."""
+
+ title: str
+ """The title of the list of reminders."""
+ identifier: str
+ """The unique identifier of the calendar (readonly)."""
+
+ def save(self) -> None:
+ """Save changes to the database."""
+ ...
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+def get_reminders(
+ calendar: Optional[Calendar] = None,
+ completed: Optional[bool] = None,
+) -> List[Reminder]:
+ """Return all reminders in the given Calendar (or all calendars).
+ Args:
+ calendar (Calendar, optional): The calendar to get reminders from.
+ Defaults to None.
+ completed (bool, optional): Filters reminders by completion status.
+ Defaults to None (all reminders).
+ Returns:
+ List[Reminder]: A list of Reminder objects.
+ """
+ ...
+
+def get_all_calendars() -> List[Calendar]:
+ """Return a list of all available Calendar objects."""
+ ...
+
+def get_default_calendar() -> Calendar:
+ """Return the Calendar that is used for new reminders by default."""
+ ...
+
+def get_calendar(calendar_id: str) -> Optional[Calendar]:
+ """Return a specific Calendar by its unique identifier.
+ Args:
+ calendar_id (str): The unique identifier of the calendar.
+ Returns:
+ Optional[Calendar]: The Calendar object, or None if not found.
+ """
+ ...
+
+def delete_reminder(reminder: Reminder) -> bool:
+ """Remove a Reminder from the database.
+ Args:
+ reminder (Reminder): The Reminder object to remove.
+ Returns:
+ bool: True if the removal was successful, False otherwise.
+ """
+ ...
+
+def delete_calendar(calendar: Calendar) -> bool:
+ """Remove a Calendar from the database.
+ Args:
+ calendar (Calendar): The Calendar object to remove.
+ Returns:
+ bool: True if the removal was successful, False otherwise.
+ """
+ ...
diff --git a/stubs/pythonista_stubs/shortcuts.pyi b/stubs/pythonista_stubs/shortcuts.pyi
new file mode 100644
index 0000000..e939662
--- /dev/null
+++ b/stubs/pythonista_stubs/shortcuts.pyi
@@ -0,0 +1,44 @@
+"""
+This is a stub file for the `shortcuts` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import List, Literal, Optional
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+_Action = Literal["run", "open", "exec"]
+
+def open_url(url: str) -> None:
+ """Open a given URL using the system's default app.
+ Args:
+ url (str): The URL to open.
+ """
+ ...
+
+def pythonista_url(
+ path: str = "",
+ action: _Action = "run",
+ args: Optional[str] = None,
+ argv: Optional[List[str]] = None,
+) -> str:
+ """Generates a pythonista3://... URL from a file name/path.
+ Args:
+ path (str, optional): Path to the script. Defaults to ''.
+ action (_Action, optional): The action to perform ('run', 'open', 'exec').
+ Defaults to 'run'.
+ args (str, optional): A string of arguments to pass to the script.
+ argv (List[str], optional): A list of arguments to pass to the script.
+ Returns:
+ str: The generated Pythonista URL.
+ """
+ ...
+
+def open_shortcuts_app(name: Optional[str] = None, shortcut_input: str = "") -> None:
+ """Open the Apple Shortcuts app and optionally run a named shortcut.
+ Args:
+ name (str, optional): The name of the shortcut to run.
+ shortcut_input (str, optional): Text input to pass to the shortcut.
+ """
+ ...
diff --git a/stubs/pythonista_stubs/sound.pyi b/stubs/pythonista_stubs/sound.pyi
new file mode 100644
index 0000000..94d7ddc
--- /dev/null
+++ b/stubs/pythonista_stubs/sound.pyi
@@ -0,0 +1,158 @@
+"""
+This is a stub file for the `sound` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import Callable, Optional, Tuple, Mapping
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+def play_effect(
+ name: str,
+ volume: float = 1.0,
+ pitch: float = 1.0,
+ pan: float = 0.0,
+ looping: bool = False,
+) -> Optional["Effect"]:
+ """Play the sound effect with the given name.
+ Args:
+ name (str): The name of the sound effect or a file path.
+ volume (float, optional): The volume of the effect (0.0-1.0).
+ pitch (float, optional): The pitch of the effect. Defaults to 1.0.
+ pan (float, optional): The stereo position (-1.0 to 1.0). Defaults to 0.0.
+ looping (bool, optional): Whether the effect should loop. Defaults to False.
+ Returns:
+ Optional[Effect]: An Effect object, or None if too many effects are playing.
+ """
+ ...
+
+def stop_all_effects() -> None:
+ """Stop all sound effects that are currently playing."""
+ ...
+
+def stop_effect(effect: "Effect") -> None:
+ """Stop playback of the given sound effect."""
+ ...
+
+def set_volume(vol: float) -> None:
+ """Sets the default volume for all sound effects (0.0 to 1.0)."""
+ ...
+
+def set_honors_silent_switch(flag: bool) -> None:
+ """Determines whether the silent switch is honored when playing sounds."""
+ ...
+
+# -----------------------------------------------------------------------------
+# Effect Class
+# -----------------------------------------------------------------------------
+class Effect:
+ """Represents a sound effect that is currently playing.
+ Effect objects are returned from `play_effect()`.
+ """
+ def stop(self) -> None:
+ """Stop playback of the sound effect."""
+ ...
+
+ @property
+ def looping(self) -> bool: ...
+ @looping.setter
+ def looping(self, value: bool) -> None: ...
+ @property
+ def pan(self) -> float: ...
+ @pan.setter
+ def pan(self, value: float) -> None: ...
+ @property
+ def pitch(self) -> float: ...
+ @pitch.setter
+ def pitch(self, value: float) -> None: ...
+ @property
+ def position(self) -> Tuple[float, float, float]: ...
+ @position.setter
+ def position(self, value: Tuple[float, float, float]) -> None: ...
+ @property
+ def volume(self) -> float: ...
+ @volume.setter
+ def volume(self, value: float) -> None: ...
+
+# -----------------------------------------------------------------------------
+# Player Class
+# -----------------------------------------------------------------------------
+class Player:
+ """Provides an interface for playing audio files from disk."""
+ def __init__(self, file_path: str): ...
+ def play(self) -> None:
+ """Start playing audio."""
+ ...
+
+ def stop(self) -> None:
+ """Stop playing audio and reset the playback position."""
+ ...
+
+ def pause(self) -> None:
+ """Stop playing audio, but keep the current playback position."""
+ ...
+
+ current_time: float
+ """The current playback position in seconds."""
+ duration: float
+ """The duration of the audio track (read-only)."""
+ finished_handler: Optional[Callable[[], None]]
+ """A function that is called when the player finishes playing."""
+ number_of_loops: int
+ """The number of times the audio track should be repeated."""
+ playing: bool
+ """A boolean indicating whether the audio player is playing."""
+ pan: float
+ """The stereo positioning of the played sound (-1.0 to 1.0)."""
+
+# -----------------------------------------------------------------------------
+# Recorder Class
+# -----------------------------------------------------------------------------
+class Recorder:
+ """High-level methods for recording audio files from the microphone."""
+ def __init__(self, file_path: str): ...
+ def record(self, duration: Optional[float] = None) -> None:
+ """Start recording audio from the microphone.
+ Args:
+ duration (float, optional): The number of seconds to record.
+ """
+ ...
+
+ def stop(self) -> None:
+ """Stop recording audio."""
+ ...
+
+ def pause(self) -> None:
+ """Pause recording audio."""
+ ...
+
+ current_time: float
+ """The current duration of the active recording."""
+ recording: bool
+ """Whether the recorder is currently recording."""
+ meters: Mapping[str, Tuple[float, float]]
+ """The current average and peak power (read-only).
+ Example: {'average': (-35.3, -30.1), 'peak': (-5.2, -8.2)}
+ """
+
+# -----------------------------------------------------------------------------
+# MIDIPlayer Class
+# -----------------------------------------------------------------------------
+class MIDIPlayer:
+ """Simple playback functions for MIDI (.mid) files."""
+ def __init__(self, file_path: str, sound_bank_path: Optional[str] = None): ...
+ def play(self) -> None:
+ """Start playback."""
+ ...
+
+ def stop(self) -> None:
+ """Stop playback."""
+ ...
+
+ current_time: float
+ """The current playback position."""
+ duration: float
+ """The duration of the loaded MIDI file."""
+ rate: float
+ """The playback rate."""
diff --git a/stubs/pythonista_stubs/speech.pyi b/stubs/pythonista_stubs/speech.pyi
new file mode 100644
index 0000000..9721d4f
--- /dev/null
+++ b/stubs/pythonista_stubs/speech.pyi
@@ -0,0 +1,54 @@
+"""
+This is a stub file for the `speech` module, providing type hints for its
+functions and their parameters, to be used for static analysis and autocompletion.
+"""
+
+from typing import Dict, List, Optional, Tuple
+
+# -----------------------------------------------------------------------------
+# Functions
+# -----------------------------------------------------------------------------
+def get_synthesis_languages() -> List[str]:
+ """Return a list of all language/locale identifiers available for
+ speech synthesis.
+ """
+ ...
+
+def get_recognition_languages() -> List[str]:
+ """Return a list of all language/locale identifiers available for
+ speech recognition.
+ """
+ ...
+
+def say(text: str, language: Optional[str] = None, rate: float = 0.5) -> None:
+ """Speak the given text.
+ Args:
+ text (str): The text to be spoken.
+ language (str, optional): The language as a BCP-47 code (e.g. 'en-US').
+ rate (float, optional): The speech rate (0.0 slowest, 1.0 fastest).
+ """
+ ...
+
+def stop() -> None:
+ """Stop any speech synthesis currently in progress."""
+ ...
+
+def is_speaking() -> bool:
+ """Return True if the synthesizer is currently speaking, False otherwise."""
+ ...
+
+def recognize(
+ file_path: str, language: Optional[str] = None
+) -> List[Tuple[str, List[Dict]]]:
+ """Transcribe spoken text in the given audio file.
+ Args:
+ file_path (str): The path to the audio file.
+ language (str, optional): The locale identifier (e.g. 'en-US').
+ Returns:
+ List[Tuple[str, List[Dict]]]: A list of possible transcriptions.
+ Raises:
+ RuntimeError: If speech recognition fails.
+ ValueError: If the language parameter is invalid.
+ IOError: If the audio file cannot be read.
+ """
+ ...
diff --git a/stubs/ui/__init__.py b/stubs/pythonista_stubs/ui.py
similarity index 92%
rename from stubs/ui/__init__.py
rename to stubs/pythonista_stubs/ui.py
index 3e43aa8..6ea6658 100644
--- a/stubs/ui/__init__.py
+++ b/stubs/pythonista_stubs/ui.py
@@ -10,5 +10,4 @@ def __init__(
Union[str, Tuple[int, int, int], Tuple[int, int, int, int], float]
] = None,
name: Optional[str] = None,
- ) -> None:
- ...
+ ) -> None: ...
diff --git a/stubs/reminders/__init__.pyi b/stubs/reminders/__init__.pyi
deleted file mode 100644
index 47ac951..0000000
--- a/stubs/reminders/__init__.pyi
+++ /dev/null
@@ -1,31 +0,0 @@
-from typing import Optional, List, Tuple
-from datetime import datetime
-
-def get_reminders(
- calendar: Optional[Calendar], completed=Optional[bool]
-) -> List[Reminder]: ...
-def get_all_calendars() -> List[Calendar]: ...
-def get_default_calendar() -> Calendar: ...
-def get_calendar(calendar_id: str) -> Optional[Calendar]: ...
-def delete_reminder(reminder: Reminder) -> bool: ...
-def delete_calendar(calendar: Calendar) -> bool: ...
-
-class Alarm:
- date: datetime
- location: Tuple[str, float, float, Optional[float]]
- proximity: str
-
-class Calendar:
- title: str
- identifier: str
- def save(self) -> None: ...
-
-class Reminder:
- title: str
- notes: str
- completed: bool
- completion_date: Optional[datetime]
- due_date: Optional[datetime]
- alarms: List[Alarm]
- def __init__(self, calendar: Optional[Calendar]) -> None: ...
- def save(self) -> None: ...
diff --git a/stubs/sound/__init__.pyi b/stubs/sound/__init__.pyi
deleted file mode 100644
index e70126f..0000000
--- a/stubs/sound/__init__.pyi
+++ /dev/null
@@ -1,50 +0,0 @@
-from typing import Dict, Optional, Tuple, Callable
-
-def play_effect(
- name: str,
- volume: Optional[float] = None,
- pitch: Optional[float] = 1.0,
- pan: Optional[float] = 0.0,
- looping: Optional[bool] = False,
-) -> Optional[Effect]: ...
-def stop_all_effects() -> None: ...
-def stop_effect(effect: Effect) -> None: ...
-def set_volume(vol: float) -> None: ...
-def set_honors_silent_switch(flag: bool) -> None: ...
-
-class Effect:
- looping: bool
- pan: float
- pitch: float = 1.0
- position: Tuple[float, float, float]
- volume: float
- def stop(self) -> None: ...
-
-class Player:
- current_time: float
- duration: float
- finished_handler: Callable
- number_of_loops: int
- playing: bool
- pan: float
- def __init__(self, file_path: str) -> None: ...
- def play(self): ...
- def stop(self): ...
- def pause(self): ...
-
-class Recorder:
- current_time: float
- recording: bool
- meters: Dict[str, Tuple[float, float]]
- def __init__(self, file_path: str) -> None: ...
- def record(self, duration: Optional[int] = None) -> None: ...
- def stop(self) -> None: ...
- def pause(self) -> None: ...
-
-class MIDIPlayer:
- current_time: float
- duration: float
- rate: float
- def __init__(self, file_path: str, sound_bank_path: Optional[str]) -> None: ...
- def play(self) -> None: ...
- def stop(self) -> None: ...
diff --git a/stubs/speech/__init__.pyi b/stubs/speech/__init__.pyi
deleted file mode 100644
index 641ee13..0000000
--- a/stubs/speech/__init__.pyi
+++ /dev/null
@@ -1,13 +0,0 @@
-from typing import Optional, List, Tuple, Dict
-
-def get_languages() -> List[str]: ...
-def get_synthesis_languages() -> List[str]: ...
-def get_recognition_languages() -> List[str]: ...
-def say(
- text: str, language: Optional[str] = None, rate: Optional[float] = 0.5
-) -> None: ...
-def stop() -> None: ...
-def is_speaking() -> bool: ...
-def recognize(
- file_path: str, language: Optional[str] = None
-) -> List[Tuple[str, List[Dict]]]: ...
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..6900664
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,183 @@
+version = 1
+revision = 1
+requires-python = ">=3.10"
+
+[[package]]
+name = "mypy"
+version = "1.17.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mypy-extensions" },
+ { name = "pathspec" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/77/a9/3d7aa83955617cdf02f94e50aab5c830d205cfa4320cf124ff64acce3a8e/mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972", size = 11003299 },
+ { url = "https://files.pythonhosted.org/packages/83/e8/72e62ff837dd5caaac2b4a5c07ce769c8e808a00a65e5d8f94ea9c6f20ab/mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7", size = 10125451 },
+ { url = "https://files.pythonhosted.org/packages/7d/10/f3f3543f6448db11881776f26a0ed079865926b0c841818ee22de2c6bbab/mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df", size = 11916211 },
+ { url = "https://files.pythonhosted.org/packages/06/bf/63e83ed551282d67bb3f7fea2cd5561b08d2bb6eb287c096539feb5ddbc5/mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390", size = 12652687 },
+ { url = "https://files.pythonhosted.org/packages/69/66/68f2eeef11facf597143e85b694a161868b3b006a5fbad50e09ea117ef24/mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94", size = 12896322 },
+ { url = "https://files.pythonhosted.org/packages/a3/87/8e3e9c2c8bd0d7e071a89c71be28ad088aaecbadf0454f46a540bda7bca6/mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b", size = 9507962 },
+ { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009 },
+ { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482 },
+ { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883 },
+ { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215 },
+ { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956 },
+ { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307 },
+ { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295 },
+ { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355 },
+ { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285 },
+ { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895 },
+ { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025 },
+ { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664 },
+ { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338 },
+ { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066 },
+ { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473 },
+ { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296 },
+ { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657 },
+ { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320 },
+ { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037 },
+ { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550 },
+ { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963 },
+ { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189 },
+ { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322 },
+ { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879 },
+ { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411 },
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
+]
+
+[[package]]
+name = "pythonista-stubs"
+source = { editable = "." }
+dependencies = [
+ { name = "types-pillow" },
+ { name = "typing-extensions" },
+]
+
+[package.optional-dependencies]
+dev = [
+ { name = "mypy" },
+ { name = "ruff" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "mypy" },
+ { name = "ruff" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.17.1" },
+ { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.12.8" },
+ { name = "types-pillow", specifier = ">=10.2.0.20240822" },
+ { name = "typing-extensions", specifier = ">=4.14.1" },
+]
+provides-extras = ["dev"]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "mypy", specifier = ">=1.17.1" },
+ { name = "ruff", specifier = ">=0.12.8" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.12.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4b/da/5bd7565be729e86e1442dad2c9a364ceeff82227c2dece7c29697a9795eb/ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033", size = 5242373 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c9/1e/c843bfa8ad1114fab3eb2b78235dda76acd66384c663a4e0415ecc13aa1e/ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513", size = 11675315 },
+ { url = "https://files.pythonhosted.org/packages/24/ee/af6e5c2a8ca3a81676d5480a1025494fd104b8896266502bb4de2a0e8388/ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc", size = 12456653 },
+ { url = "https://files.pythonhosted.org/packages/99/9d/e91f84dfe3866fa648c10512904991ecc326fd0b66578b324ee6ecb8f725/ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec", size = 11659690 },
+ { url = "https://files.pythonhosted.org/packages/fe/ac/a363d25ec53040408ebdd4efcee929d48547665858ede0505d1d8041b2e5/ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53", size = 11896923 },
+ { url = "https://files.pythonhosted.org/packages/58/9f/ea356cd87c395f6ade9bb81365bd909ff60860975ca1bc39f0e59de3da37/ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f", size = 11477612 },
+ { url = "https://files.pythonhosted.org/packages/1a/46/92e8fa3c9dcfd49175225c09053916cb97bb7204f9f899c2f2baca69e450/ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f", size = 13182745 },
+ { url = "https://files.pythonhosted.org/packages/5e/c4/f2176a310f26e6160deaf661ef60db6c3bb62b7a35e57ae28f27a09a7d63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609", size = 14206885 },
+ { url = "https://files.pythonhosted.org/packages/87/9d/98e162f3eeeb6689acbedbae5050b4b3220754554526c50c292b611d3a63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a", size = 13639381 },
+ { url = "https://files.pythonhosted.org/packages/81/4e/1b7478b072fcde5161b48f64774d6edd59d6d198e4ba8918d9f4702b8043/ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb", size = 12613271 },
+ { url = "https://files.pythonhosted.org/packages/e8/67/0c3c9179a3ad19791ef1b8f7138aa27d4578c78700551c60d9260b2c660d/ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818", size = 12847783 },
+ { url = "https://files.pythonhosted.org/packages/4e/2a/0b6ac3dd045acf8aa229b12c9c17bb35508191b71a14904baf99573a21bd/ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea", size = 11702672 },
+ { url = "https://files.pythonhosted.org/packages/9d/ee/f9fdc9f341b0430110de8b39a6ee5fa68c5706dc7c0aa940817947d6937e/ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3", size = 11440626 },
+ { url = "https://files.pythonhosted.org/packages/89/fb/b3aa2d482d05f44e4d197d1de5e3863feb13067b22c571b9561085c999dc/ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161", size = 12462162 },
+ { url = "https://files.pythonhosted.org/packages/18/9f/5c5d93e1d00d854d5013c96e1a92c33b703a0332707a7cdbd0a4880a84fb/ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46", size = 12913212 },
+ { url = "https://files.pythonhosted.org/packages/71/13/ab9120add1c0e4604c71bfc2e4ef7d63bebece0cfe617013da289539cef8/ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3", size = 11694382 },
+ { url = "https://files.pythonhosted.org/packages/f6/dc/a2873b7c5001c62f46266685863bee2888caf469d1edac84bf3242074be2/ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e", size = 12740482 },
+ { url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718 },
+]
+
+[[package]]
+name = "tomli"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 },
+ { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 },
+ { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 },
+ { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 },
+ { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 },
+ { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 },
+ { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 },
+ { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 },
+ { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 },
+ { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 },
+ { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 },
+ { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 },
+ { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 },
+ { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 },
+ { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 },
+ { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 },
+ { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 },
+ { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 },
+ { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 },
+ { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 },
+ { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 },
+ { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 },
+ { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 },
+ { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 },
+ { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 },
+ { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 },
+ { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 },
+ { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 },
+ { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
+ { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
+ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
+]
+
+[[package]]
+name = "types-pillow"
+version = "10.2.0.20240822"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/4a/4495264dddaa600d65d68bcedb64dcccf9d9da61adff51f7d2ffd8e4c9ce/types-Pillow-10.2.0.20240822.tar.gz", hash = "sha256:559fb52a2ef991c326e4a0d20accb3bb63a7ba8d40eb493e0ecb0310ba52f0d3", size = 35389 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/66/23/e81a5354859831fcf54d488d33b80ba6133ea84f874a9c0ec40a4881e133/types_Pillow-10.2.0.20240822-py3-none-any.whl", hash = "sha256:d9dab025aba07aeb12fd50a6799d4eac52a9603488eca09d7662543983f16c5d", size = 54354 },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.14.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 },
+]