Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"WebFetch(domain:omz-software.com)"
],
"deny": []
}
}
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ jobs:
run: uv run mypy

- name: Check code formatting with black
run: uv run black --check
run: uv run black --check --diff .
70 changes: 70 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Here are the key best practices for writing Python type stub libraries:

## Structure and Organization

**Follow the source layout**: Mirror the structure of the runtime library you're stubbing. If the original package is `mylib/module.py`, your stub should be `mylib/module.pyi`.

**Use `.pyi` extension**: Type stubs should always use the `.pyi` extension, never `.py`.

**Include `py.typed` marker**: For stub packages, include an empty `py.typed` file in the package root to indicate it contains type information.

## Type Annotations

**Be precise but practical**: Use the most specific types that accurately represent the API without being overly complex. Prefer `list[str]` over `List[str]` (Python 3.9+).

**Use `typing_extensions` when needed**: Import from `typing_extensions` for newer typing features that need to work with older Python versions.

**Leverage generics appropriately**: Use `TypeVar` for generic functions and classes, but don't over-generify simple APIs.

```python
from typing import TypeVar, Generic
T = TypeVar('T')

class Container(Generic[T]):
def get(self) -> T: ...
```

## Stub Content Guidelines

**Omit implementation details**: Stubs should only contain signatures, not implementations. Use `...` (Ellipsis) for function bodies.

**Include all public APIs**: Cover all publicly documented functions, classes, methods, and constants.

**Use `@overload` for complex signatures**: When functions accept different parameter combinations that return different types.

```python
from typing import overload

@overload
def process(data: str) -> str: ...
@overload
def process(data: int) -> int: ...
```

**Handle optional parameters correctly**: Use proper defaults and Optional types.

## Documentation and Metadata

**Include docstrings sparingly**: Only add docstrings if they provide type-relevant information not captured in the signature.

**Version compatibility**: Use `if TYPE_CHECKING:` blocks and version checks when APIs differ across Python versions.

**Mark incomplete stubs**: Use `# type: ignore` or add comments explaining limitations for partially-stubbed modules.

## Distribution and Packaging

**Follow naming conventions**: Stub-only packages should be named `types-{package}` (e.g., `types-requests`).

**Specify supported versions**: Clearly document which versions of the runtime library your stubs support.

**Keep stubs minimal**: Don't include runtime code in stub packages - they should be purely type information.

## Testing and Validation

**Test with mypy**: Run mypy against code that uses your stubs to ensure they work correctly.

**Validate completeness**: Use tools like `stubtest` (part of mypy) to verify stubs match the runtime API.

**Check multiple Python versions**: Ensure stubs work across the Python versions you claim to support.

The key is balancing precision with usability - your stubs should provide helpful type checking without being so complex that they're hard to maintain or understand.
90 changes: 45 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,47 @@
[![CI](https://github.com/hbmartin/pythonista-stubs/actions/workflows/ci.yml/badge.svg)](https://github.com/hbmartin/pythonista-stubs/actions/workflows/ci.yml)



Stubs for the [Pythonista iOS API](http://omz-software.com/pythonista/docs/ios/). This allows for better error detection and IDE / editor autocomplete.
Stubs for the [Pythonista iOS API](https://omz-software.com/pythonista/lab/). This allows for better error detection and IDE / editor autocomplete.

## Installation and Usage

```
Install using your preferred package manager:

```bash
# Using uv (recommended)
uv add pythonista-stubs

# Using pip
pip install pythonista-stubs
```

You can now develop from your computer editor with proper typing and completions.

## API Coverage

| Module | Status |
| ----------- |--------|
| appex | ✔ |
| canvas | WIP |
| cb | [WIP](https://github.com/hbmartin/pythonista-stubs/issues/7) |
| clipboard | ✔ |
| console | ✔ |
| contacts | ✔ |
| dialogs | ✔ |
| editor | ✔ |
| keychain | ✔ |
| linguistictagger | ✔ |
| location | ✔ |
| motion | ✔ |
| notification | ✔ |
| objc_util | [WIP](https://github.com/hbmartin/pythonista-stubs/issues/7) |
| photos | ✔ |
| reminders | ✔ |
| scene | [✘](https://github.com/hbmartin/pythonista-stubs/issues/9) |
| sound | ✔ |
| speech | ✔ |
| twitter | ✘ |
| ui | [WIP](https://github.com/hbmartin/pythonista-stubs/issues/6) |
| Module | Status | Documentation |
| ----------- |--------|---------------|
| appex | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/appex.html) |
| canvas | WIP | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/canvas.html) |
| cb | [WIP](https://github.com/hbmartin/pythonista-stubs/issues/7) | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/cb.html) |
| clipboard | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/clipboard.html) |
| console | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/console.html) |
| contacts | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/contacts.html) |
| dialogs | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/dialogs.html) |
| editor | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/editor.html) |
| keychain | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/keychain.html) |
| linguistictagger | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/linguistictagger.html) |
| location | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/location.html) |
| motion | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/motion.html) |
| notification | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/notification.html) |
| objc_util | [WIP](https://github.com/hbmartin/pythonista-stubs/issues/7) | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/objc_util.html) |
| photos | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/photos.html) |
| reminders | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/reminders.html) |
| scene | [✘](https://github.com/hbmartin/pythonista-stubs/issues/9) | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/scene.html) |
| sound | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/sound.html) |
| speech | ✔ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/speech.html) |
| twitter | ✘ | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/twitter.html) |
| ui | [WIP](https://github.com/hbmartin/pythonista-stubs/issues/6) | [API Docs](https://omz-software.com/pythonista/docs-3.4/py3/ios/ui.html) |

## Built With

Expand All @@ -53,28 +59,22 @@ You can now develop from your computer editor with proper typing and completions
* [PEP 561 -- Distributing and Packaging Type Information](https://www.python.org/dev/peps/pep-0561/)
* [PEP 3107 -- Function Annotations](https://www.python.org/dev/peps/pep-3107/)

## Troubleshooting

### Type Checker Not Finding Stubs
- **VSCode**: Ensure the Python extension is using the correct interpreter where pythonista-stubs is installed
- **PyCharm**: Check that the stub package appears in your project's external libraries
- **mypy**: Make sure mypy can find the stubs in your Python path

### Import Errors in IDE
- Verify pythonista-stubs is installed in the same environment as your project
- Try restarting your IDE after installation
- Check that your IDE is using the correct Python interpreter

## Contributing

Please [file a bug report](https://github.com/hbmartin/pythonista-stubs/issues) for any issues you find. Even more excellent than a good bug report is a fix for a bug, or the implementation of a much-needed stub. We'd love to have your contributions.

### Conventions

* long functions and methods should be split up with one argument per line
* all function bodies should be empty
* prefer ``...`` over ``pass``
* prefer ``...`` on the same line as the class/function signature
* avoid vertical whitespace between consecutive module-level functions, names, or methods and fields within a single class
* use a single blank line between top-level class definitions
* do not use docstrings
* use variable annotations instead of type comments
* for arguments with a type and a default, use spaces around the `=`
* use `float` instead of `Union[int, float]`
* avoid Union return types: https://github.com/python/mypy/issues/1693
* imports in stubs are considered private unless they use the form ``from library import name as name``
* avoid using the `Any` type when possible
* type variables and aliases for legibility reasons should be prefixed with an underscore to make it obvious to the reader they are not part of the stubbed API.
* these conventions derived from [typeshed](https://github.com/python/typeshed/blob/master/CONTRIBUTING.md#conventions)

### Code of Conduct

Everyone participating in this community is expected to treat other people with respect and more generally to follow the guidelines articulated in the [Python Community Code of Conduct](https://www.python.org/psf/codeofconduct/).
Expand All @@ -91,4 +91,4 @@ This is not an official project and is not associated with omz:software

## License

[Apache License 2.0](LICENSE.txt)
[Apache License 2.0](LICENSE)
70 changes: 39 additions & 31 deletions stubs/_cb.pyi
Original file line number Diff line number Diff line change
@@ -1,35 +1,32 @@
# Created on July, 07 2025 by o-murphy <https://github.com/o-murphy>
"""
pythonista `_cb` module type annotations
"""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_BROADCAST",
"CH_PROP_EXTENDED_PROPERTIES",
"CH_PROP_NOTIFY_ENCRYPTION_REQUIRED",
"CH_PROP_INDICATE",
"CH_PROP_INDICATE_ENCRYPTION_REQUIRED",
"CH_PROP_NOTIFY",
"CH_PROP_NOTIFY_ENCRYPTION_REQUIRED",
"CH_PROP_READ",
"CH_PROP_WRITE",
"CH_PROP_WRITE_WITHOUT_RESPONSE",
"CM_STATE_POWERED_OFF",
"CM_STATE_POWERED_ON",
"CM_STATE_RESETTING",
"CM_STATE_UNAUTHORIZED",
"CM_STATE_UNKNOWN",
"CM_STATE_UNSUPPORTED",
"CentralManager",
"Characteristic",
"Service",
"Peripheral",
"CentralManager",
"Service",
)

CM_STATE_UNKNOWN: int = 0
Expand All @@ -52,29 +49,34 @@ CH_PROP_INDICATE_ENCRYPTION_REQUIRED: int = 512

class Characteristic:
properties: int
value: Optional[bytes]
value: bytes | None
uuid: str # hex
notifying: bool

class Service:
characteristics: List[Characteristic]
characteristics: list[Characteristic]
primary: bool
uuid: str # hex

class Peripheral:
manufacturer_data: bytes
name: Optional[str]
name: str | None
uuid: str # hex
state: int
services: List[Service]
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
self,
characteristic: Characteristic,
flag: bool = True,
) -> None: ...
def write_characteristic_value(
self, characteristic, data, with_response
self,
characteristic: Characteristic,
data: bytes,
with_response: bool,
) -> None: ...
def read_characteristic_value(self, characteristic: Characteristic) -> None: ...

Expand All @@ -89,15 +91,21 @@ class CentralManager:
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]
self,
p: Peripheral,
error: str | None,
) -> None: ...
def did_disconnect_peripheral(
self, p: Peripheral, error: Optional[str]
self,
p: Peripheral,
error: str | None,
) -> None: ...
def did_discover_services(self, p: Peripheral, error: Optional[str]) -> None: ...
def did_discover_services(self, p: Peripheral, error: str | None) -> None: ...
def did_discover_characteristics(
self, s: Service, error: Optional[str]
self,
s: Service,
error: str | None,
) -> 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_write_value(self, c: Characteristic, error: str | None) -> None: ...
def did_update_value(self, c: Characteristic, error: str | None) -> None: ...
def did_update_state(self) -> None: ...
Loading