async-expo-push-notifications


Nameasync-expo-push-notifications JSON
Version 2.3.1 PyPI version JSON
download
home_pageNone
SummaryExpo Server SDK for Python with async/await support, Pydantic models, and full type hints
upload_time2025-10-14 07:21:05
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseNone
keywords expo push notifications async pydantic type-hints
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Async Expo Push Notifications SDK for Python

**Modern Python SDK for Expo Push Notifications with async/await support, Pydantic models, and full type hints.**

> **Note:** This is an **independent project** and is **not officially maintained by Expo**. It's a modern reimplementation with async support.

If you have problems or suggestions, please feel free to [open an issue](https://github.com/tmdgusya/async-expo-notification-sdk/issues) or submit a PR. Contributions welcome! 🙏

## Installation

```bash
pip install async-expo-push-notifications
```

> **Note:** Package name is `async-expo-push-notifications` but you still import as `exponent_server_sdk`

## Requirements

- Python 3.8+
- Type-safe with Pydantic models
- Async support with httpx
- Synchronous support with requests (backward compatible)

## Usage

Use to send push notifications to Exponent Experiences from a Python server.

[Full documentation](https://docs.expo.dev/push-notifications/sending-notifications/#http2-api) on the API is available if you want to dive into the details.

### Async/Await Usage (Recommended for modern applications)

```python
import asyncio
from exponent_server_sdk import AsyncPushClient, PushMessage

async def send_notification():
    async with AsyncPushClient() as client:
        message = PushMessage(
            to="ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
            title="Hello",
            body="World!",
            data={"extra": "data"}
        )
        ticket = await client.publish(message)
        ticket.validate_response()

asyncio.run(send_notification())
```

### Synchronous Usage (Backward Compatible)

Here's an example on how to use this with retries and reporting via [pyrollbar](https://github.com/rollbar/pyrollbar).

```python
from exponent_server_sdk import (
    DeviceNotRegisteredError,
    PushClient,
    PushMessage,
    PushServerError,
    PushTicketError,
)
import os
import requests
from requests.exceptions import ConnectionError, HTTPError

# Optionally providing an access token within a session if you have enabled push security
session = requests.Session()
session.headers.update(
    {
        "Authorization": f"Bearer {os.getenv('EXPO_TOKEN')}",
        "accept": "application/json",
        "accept-encoding": "gzip, deflate",
        "content-type": "application/json",
    }
)

# Basic arguments. You should extend this function with the push features you
# want to use, or simply pass in a `PushMessage` object.
def send_push_message(token, message, extra=None):
    try:
        response = PushClient(session=session).publish(
            PushMessage(to=token,
                        body=message,
                        data=extra))
    except PushServerError as exc:
        # Encountered some likely formatting/validation error.
        rollbar.report_exc_info(
            extra_data={
                'token': token,
                'message': message,
                'extra': extra,
                'errors': exc.errors,
                'response_data': exc.response_data,
            })
        raise
    except (ConnectionError, HTTPError) as exc:
        # Encountered some Connection or HTTP error - retry a few times in
        # case it is transient.
        rollbar.report_exc_info(
            extra_data={'token': token, 'message': message, 'extra': extra})
        raise self.retry(exc=exc)

    try:
        # We got a response back, but we don't know whether it's an error yet.
        # This call raises errors so we can handle them with normal exception
        # flows.
        response.validate_response()
    except DeviceNotRegisteredError:
        # Mark the push token as inactive
        from notifications.models import PushToken
        PushToken.objects.filter(token=token).update(active=False)
    except PushTicketError as exc:
        # Encountered some other per-notification error.
        rollbar.report_exc_info(
            extra_data={
                'token': token,
                'message': message,
                'extra': extra,
                'push_response': exc.push_response._asdict(),
            })
        raise self.retry(exc=exc)
```

## Features

### 🚀 Async/Await Support

Modern async/await syntax for high-performance applications:

```python
async with AsyncPushClient() as client:
    tickets = await client.publish_multiple(messages)
```

### 🔒 Type Safety with Pydantic

All models use Pydantic for validation and type safety:

```python
# Automatic validation
message = PushMessage(
    to="ExponentPushToken[xxx]",
    priority="high",  # Validates against allowed values
    richContent={"image": "https://example.com/img.png"}  # NEW!
)
```

### 💉 Dependency Injection

Inject custom HTTP clients for testing or custom behavior:

```python
import httpx

custom_client = httpx.AsyncClient(
    headers={"Authorization": f"Bearer {token}"}
)
push_client = AsyncPushClient(http_client=custom_client)
```

### 📸 Rich Content Support

Now includes the `richContent` field for image notifications:

```python
message = PushMessage(
    to=token,
    title="Check this out!",
    body="Beautiful image notification",
    richContent={"image": "https://example.com/image.jpg"}
)
```

## Examples

Check out the [examples/](examples/) directory for more:

- [async_example.py](examples/async_example.py) - Async/await usage
- [sync_example.py](examples/sync_example.py) - Synchronous usage
- [dependency_injection_example.py](examples/dependency_injection_example.py) - DI patterns

## Migration Guide

### From v2.1.x to v2.2.0

The library is **fully backward compatible**. Existing code will continue to work without changes:

```python
# Old code still works!
from exponent_server_sdk import PushClient, PushMessage

client = PushClient()
ticket = client.publish(PushMessage(to=token, body="Hello"))
```

### New Features You Can Adopt

1. **Use Pydantic models** - Your `PushMessage` objects now have validation
2. **Try async/await** - Use `AsyncPushClient` for better performance
3. **Add richContent** - Include images in your notifications
4. **Inject dependencies** - Pass custom HTTP clients for testing

## Type Hints

All classes and methods now include full type hints:

```python
from exponent_server_sdk import AsyncPushClient, PushMessage, PushTicket
from typing import List

async def send_many(messages: List[PushMessage]) -> List[PushTicket]:
    async with AsyncPushClient() as client:
        return await client.publish_multiple(messages)
```

## 📜 License

MIT License - see [LICENSE](LICENSE) file for details.

## 🙏 Acknowledgments

This project is a modern reimplementation inspired by:
- [Expo Community Server SDK](https://github.com/expo-community/expo-server-sdk-python) - The original community-maintained SDK
- [Expo Push Notification Service](https://docs.expo.dev/push-notifications/overview/) - Official Expo documentation and API

## ⚠️ Disclaimer

This is an **independent project** and is **not officially affiliated with or endorsed by Expo**.

### Comparison with Official SDK

| Feature | This SDK (`async-expo-push-notifications`) | [Official Community SDK](https://github.com/expo-community/expo-server-sdk-python) |
|---------|-------------------------------------------|----------------------------------------------------------------------------------|
| **Async/Await** | ✅ Full async support | ❌ Sync only |
| **Type Hints** | ✅ Complete type hints | ⚠️ Partial |
| **Pydantic** | ✅ Type-safe models | ❌ Named tuples |
| **Dependency Injection** | ✅ Supported | ❌ No |
| **Rich Content** | ✅ Image support | ❌ Not included |
| **Python Version** | 3.8+ | 3.6+ |
| **Backward Compatible** | ✅ Yes | - |

**When to use this SDK:**
- You need async/await support for high-performance applications
- You want type safety with Pydantic models
- You need dependency injection for testing
- You want modern Python features (type hints, async)

**When to use the official community SDK:**
- You need a battle-tested, widely-used solution
- You're using Python 3.6-3.7
- You prefer simpler, synchronous code

For official Expo resources and the original community SDK, please visit:
- Official SDK: https://github.com/expo-community/expo-server-sdk-python
- Expo: https://expo.dev

## 🤝 Contributing

Contributions are welcome! Please feel free to submit issues or pull requests on [GitHub](https://github.com/tmdgusya/async-expo-notification-sdk).

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "async-expo-push-notifications",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "Roach <tmdgusya@gmail.com>",
    "keywords": "expo, push, notifications, async, pydantic, type-hints",
    "author": null,
    "author_email": "Roach <tmdgusya@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/48/39/fe100c8c583342d908d257146ce90120ab993a9eb0269e5111c4d5ff82d1/async_expo_push_notifications-2.3.1.tar.gz",
    "platform": null,
    "description": "# Async Expo Push Notifications SDK for Python\n\n**Modern Python SDK for Expo Push Notifications with async/await support, Pydantic models, and full type hints.**\n\n> **Note:** This is an **independent project** and is **not officially maintained by Expo**. It's a modern reimplementation with async support.\n\nIf you have problems or suggestions, please feel free to [open an issue](https://github.com/tmdgusya/async-expo-notification-sdk/issues) or submit a PR. Contributions welcome! \ud83d\ude4f\n\n## Installation\n\n```bash\npip install async-expo-push-notifications\n```\n\n> **Note:** Package name is `async-expo-push-notifications` but you still import as `exponent_server_sdk`\n\n## Requirements\n\n- Python 3.8+\n- Type-safe with Pydantic models\n- Async support with httpx\n- Synchronous support with requests (backward compatible)\n\n## Usage\n\nUse to send push notifications to Exponent Experiences from a Python server.\n\n[Full documentation](https://docs.expo.dev/push-notifications/sending-notifications/#http2-api) on the API is available if you want to dive into the details.\n\n### Async/Await Usage (Recommended for modern applications)\n\n```python\nimport asyncio\nfrom exponent_server_sdk import AsyncPushClient, PushMessage\n\nasync def send_notification():\n    async with AsyncPushClient() as client:\n        message = PushMessage(\n            to=\"ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]\",\n            title=\"Hello\",\n            body=\"World!\",\n            data={\"extra\": \"data\"}\n        )\n        ticket = await client.publish(message)\n        ticket.validate_response()\n\nasyncio.run(send_notification())\n```\n\n### Synchronous Usage (Backward Compatible)\n\nHere's an example on how to use this with retries and reporting via [pyrollbar](https://github.com/rollbar/pyrollbar).\n\n```python\nfrom exponent_server_sdk import (\n    DeviceNotRegisteredError,\n    PushClient,\n    PushMessage,\n    PushServerError,\n    PushTicketError,\n)\nimport os\nimport requests\nfrom requests.exceptions import ConnectionError, HTTPError\n\n# Optionally providing an access token within a session if you have enabled push security\nsession = requests.Session()\nsession.headers.update(\n    {\n        \"Authorization\": f\"Bearer {os.getenv('EXPO_TOKEN')}\",\n        \"accept\": \"application/json\",\n        \"accept-encoding\": \"gzip, deflate\",\n        \"content-type\": \"application/json\",\n    }\n)\n\n# Basic arguments. You should extend this function with the push features you\n# want to use, or simply pass in a `PushMessage` object.\ndef send_push_message(token, message, extra=None):\n    try:\n        response = PushClient(session=session).publish(\n            PushMessage(to=token,\n                        body=message,\n                        data=extra))\n    except PushServerError as exc:\n        # Encountered some likely formatting/validation error.\n        rollbar.report_exc_info(\n            extra_data={\n                'token': token,\n                'message': message,\n                'extra': extra,\n                'errors': exc.errors,\n                'response_data': exc.response_data,\n            })\n        raise\n    except (ConnectionError, HTTPError) as exc:\n        # Encountered some Connection or HTTP error - retry a few times in\n        # case it is transient.\n        rollbar.report_exc_info(\n            extra_data={'token': token, 'message': message, 'extra': extra})\n        raise self.retry(exc=exc)\n\n    try:\n        # We got a response back, but we don't know whether it's an error yet.\n        # This call raises errors so we can handle them with normal exception\n        # flows.\n        response.validate_response()\n    except DeviceNotRegisteredError:\n        # Mark the push token as inactive\n        from notifications.models import PushToken\n        PushToken.objects.filter(token=token).update(active=False)\n    except PushTicketError as exc:\n        # Encountered some other per-notification error.\n        rollbar.report_exc_info(\n            extra_data={\n                'token': token,\n                'message': message,\n                'extra': extra,\n                'push_response': exc.push_response._asdict(),\n            })\n        raise self.retry(exc=exc)\n```\n\n## Features\n\n### \ud83d\ude80 Async/Await Support\n\nModern async/await syntax for high-performance applications:\n\n```python\nasync with AsyncPushClient() as client:\n    tickets = await client.publish_multiple(messages)\n```\n\n### \ud83d\udd12 Type Safety with Pydantic\n\nAll models use Pydantic for validation and type safety:\n\n```python\n# Automatic validation\nmessage = PushMessage(\n    to=\"ExponentPushToken[xxx]\",\n    priority=\"high\",  # Validates against allowed values\n    richContent={\"image\": \"https://example.com/img.png\"}  # NEW!\n)\n```\n\n### \ud83d\udc89 Dependency Injection\n\nInject custom HTTP clients for testing or custom behavior:\n\n```python\nimport httpx\n\ncustom_client = httpx.AsyncClient(\n    headers={\"Authorization\": f\"Bearer {token}\"}\n)\npush_client = AsyncPushClient(http_client=custom_client)\n```\n\n### \ud83d\udcf8 Rich Content Support\n\nNow includes the `richContent` field for image notifications:\n\n```python\nmessage = PushMessage(\n    to=token,\n    title=\"Check this out!\",\n    body=\"Beautiful image notification\",\n    richContent={\"image\": \"https://example.com/image.jpg\"}\n)\n```\n\n## Examples\n\nCheck out the [examples/](examples/) directory for more:\n\n- [async_example.py](examples/async_example.py) - Async/await usage\n- [sync_example.py](examples/sync_example.py) - Synchronous usage\n- [dependency_injection_example.py](examples/dependency_injection_example.py) - DI patterns\n\n## Migration Guide\n\n### From v2.1.x to v2.2.0\n\nThe library is **fully backward compatible**. Existing code will continue to work without changes:\n\n```python\n# Old code still works!\nfrom exponent_server_sdk import PushClient, PushMessage\n\nclient = PushClient()\nticket = client.publish(PushMessage(to=token, body=\"Hello\"))\n```\n\n### New Features You Can Adopt\n\n1. **Use Pydantic models** - Your `PushMessage` objects now have validation\n2. **Try async/await** - Use `AsyncPushClient` for better performance\n3. **Add richContent** - Include images in your notifications\n4. **Inject dependencies** - Pass custom HTTP clients for testing\n\n## Type Hints\n\nAll classes and methods now include full type hints:\n\n```python\nfrom exponent_server_sdk import AsyncPushClient, PushMessage, PushTicket\nfrom typing import List\n\nasync def send_many(messages: List[PushMessage]) -> List[PushTicket]:\n    async with AsyncPushClient() as client:\n        return await client.publish_multiple(messages)\n```\n\n## \ud83d\udcdc License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n\n## \ud83d\ude4f Acknowledgments\n\nThis project is a modern reimplementation inspired by:\n- [Expo Community Server SDK](https://github.com/expo-community/expo-server-sdk-python) - The original community-maintained SDK\n- [Expo Push Notification Service](https://docs.expo.dev/push-notifications/overview/) - Official Expo documentation and API\n\n## \u26a0\ufe0f Disclaimer\n\nThis is an **independent project** and is **not officially affiliated with or endorsed by Expo**.\n\n### Comparison with Official SDK\n\n| Feature | This SDK (`async-expo-push-notifications`) | [Official Community SDK](https://github.com/expo-community/expo-server-sdk-python) |\n|---------|-------------------------------------------|----------------------------------------------------------------------------------|\n| **Async/Await** | \u2705 Full async support | \u274c Sync only |\n| **Type Hints** | \u2705 Complete type hints | \u26a0\ufe0f Partial |\n| **Pydantic** | \u2705 Type-safe models | \u274c Named tuples |\n| **Dependency Injection** | \u2705 Supported | \u274c No |\n| **Rich Content** | \u2705 Image support | \u274c Not included |\n| **Python Version** | 3.8+ | 3.6+ |\n| **Backward Compatible** | \u2705 Yes | - |\n\n**When to use this SDK:**\n- You need async/await support for high-performance applications\n- You want type safety with Pydantic models\n- You need dependency injection for testing\n- You want modern Python features (type hints, async)\n\n**When to use the official community SDK:**\n- You need a battle-tested, widely-used solution\n- You're using Python 3.6-3.7\n- You prefer simpler, synchronous code\n\nFor official Expo resources and the original community SDK, please visit:\n- Official SDK: https://github.com/expo-community/expo-server-sdk-python\n- Expo: https://expo.dev\n\n## \ud83e\udd1d Contributing\n\nContributions are welcome! Please feel free to submit issues or pull requests on [GitHub](https://github.com/tmdgusya/async-expo-notification-sdk).\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Expo Server SDK for Python with async/await support, Pydantic models, and full type hints",
    "version": "2.3.1",
    "project_urls": {
        "Documentation": "https://github.com/tmdgusya/async-expo-notification-sdk#readme",
        "Homepage": "https://github.com/tmdgusya/async-expo-notification-sdk",
        "Issues": "https://github.com/tmdgusya/async-expo-notification-sdk/issues",
        "Repository": "https://github.com/tmdgusya/async-expo-notification-sdk"
    },
    "split_keywords": [
        "expo",
        " push",
        " notifications",
        " async",
        " pydantic",
        " type-hints"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "7e722d95ee8dda14d535ea3ced363e49fe4fe60556ad705a8906ee4e5aa96096",
                "md5": "5b206986eb2d209b2438f3537449090e",
                "sha256": "0c7871bc42732f20c81aa00ff2c87c06c996a9a85b21dd8f42219c36b82412af"
            },
            "downloads": -1,
            "filename": "async_expo_push_notifications-2.3.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "5b206986eb2d209b2438f3537449090e",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 17129,
            "upload_time": "2025-10-14T07:21:04",
            "upload_time_iso_8601": "2025-10-14T07:21:04.482435Z",
            "url": "https://files.pythonhosted.org/packages/7e/72/2d95ee8dda14d535ea3ced363e49fe4fe60556ad705a8906ee4e5aa96096/async_expo_push_notifications-2.3.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "4839fe100c8c583342d908d257146ce90120ab993a9eb0269e5111c4d5ff82d1",
                "md5": "4c3055e8c73020e063cc3adfa4873cab",
                "sha256": "b4d4a8ac9d2d8921d0acde5a6984a32cff894de04819714eb0e767c389bd383b"
            },
            "downloads": -1,
            "filename": "async_expo_push_notifications-2.3.1.tar.gz",
            "has_sig": false,
            "md5_digest": "4c3055e8c73020e063cc3adfa4873cab",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 16030,
            "upload_time": "2025-10-14T07:21:05",
            "upload_time_iso_8601": "2025-10-14T07:21:05.884596Z",
            "url": "https://files.pythonhosted.org/packages/48/39/fe100c8c583342d908d257146ce90120ab993a9eb0269e5111c4d5ff82d1/async_expo_push_notifications-2.3.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-14 07:21:05",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "tmdgusya",
    "github_project": "async-expo-notification-sdk#readme",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "async-expo-push-notifications"
}
        
Elapsed time: 2.49381s