# 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"
}