chatapult 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chatapult/__init__.py +28 -0
- chatapult/async_client.py +82 -0
- chatapult/client.py +85 -0
- chatapult/exceptions.py +39 -0
- chatapult/py.typed +0 -0
- chatapult-0.1.0.dist-info/METADATA +115 -0
- chatapult-0.1.0.dist-info/RECORD +9 -0
- chatapult-0.1.0.dist-info/WHEEL +4 -0
- chatapult-0.1.0.dist-info/licenses/LICENSE +21 -0
chatapult/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chatapult: A fast, Pythonic API wrapper for Google Chat webhooks.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Explicitly define the version so developers can check it programmatically
|
|
6
|
+
__version__ = "0.1.0"
|
|
7
|
+
|
|
8
|
+
# Import the core components to expose them at the top level of the package
|
|
9
|
+
from .client import ChatClient
|
|
10
|
+
from .async_client import AsyncChatClient
|
|
11
|
+
from .exceptions import (
|
|
12
|
+
APIError,
|
|
13
|
+
ChatapultError,
|
|
14
|
+
ConfigurationError,
|
|
15
|
+
NetworkError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# The __all__ list strictly defines the public API of your module.
|
|
19
|
+
# It tells IDEs and linters exactly what is safe for end-users to import,
|
|
20
|
+
# and hides internal modules (like 'logging' or 'httpx') from autocomplete.
|
|
21
|
+
__all__ = [
|
|
22
|
+
"APIError",
|
|
23
|
+
"AsyncChatClient",
|
|
24
|
+
"ChatClient",
|
|
25
|
+
"ChatapultError",
|
|
26
|
+
"ConfigurationError",
|
|
27
|
+
"NetworkError",
|
|
28
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from .exceptions import APIError, ConfigurationError, NetworkError
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncChatClient:
|
|
12
|
+
"""
|
|
13
|
+
An asynchronous client for interacting with Google Chat Webhooks.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, webhook_url: str, timeout: float = 10.0) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Initialize the AsyncChatClient.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
webhook_url (str): The full Google Chat webhook URL.
|
|
22
|
+
timeout (float): Connection timeout in seconds. Defaults to 10.0.
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
ConfigurationError: If the webhook_url is empty.
|
|
26
|
+
"""
|
|
27
|
+
if not webhook_url:
|
|
28
|
+
raise ConfigurationError("A valid webhook_url must be provided.")
|
|
29
|
+
|
|
30
|
+
self.webhook_url = webhook_url
|
|
31
|
+
self._client = httpx.AsyncClient(timeout=timeout)
|
|
32
|
+
|
|
33
|
+
async def send_message(
|
|
34
|
+
self, text: str, thread_name: Optional[str] = None
|
|
35
|
+
) -> Dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
Asynchronously send a simple text message to the Google Chat Space.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
text (str): The markdown-formatted text to send.
|
|
41
|
+
thread_name (Optional[str]): The ID of an existing thread to reply to.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Dict[str, Any]: The JSON response from the Google Chat API.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
APIError: If the Google API returns a non-200 status code.
|
|
48
|
+
NetworkError: If the connection times out or fails.
|
|
49
|
+
"""
|
|
50
|
+
payload: Dict[str, Any] = {"text": text}
|
|
51
|
+
|
|
52
|
+
if thread_name:
|
|
53
|
+
payload["thread"] = {"name": thread_name}
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
response = await self._client.post(self.webhook_url, json=payload)
|
|
57
|
+
response.raise_for_status()
|
|
58
|
+
return response.json()
|
|
59
|
+
|
|
60
|
+
except httpx.HTTPStatusError as e:
|
|
61
|
+
logger.error(
|
|
62
|
+
f"Google Chat API error: {e.response.status_code} - {e.response.text}"
|
|
63
|
+
)
|
|
64
|
+
raise APIError(
|
|
65
|
+
message=f"API request failed: {e.response.text}",
|
|
66
|
+
status_code=e.response.status_code,
|
|
67
|
+
) from e
|
|
68
|
+
except httpx.RequestError as e:
|
|
69
|
+
logger.error(f"Network error while connecting to Google Chat: {e}")
|
|
70
|
+
raise NetworkError(f"Network request failed: {str(e)}") from e
|
|
71
|
+
|
|
72
|
+
async def aclose(self) -> None:
|
|
73
|
+
"""Close the underlying asynchronous HTTP client connections."""
|
|
74
|
+
await self._client.aclose()
|
|
75
|
+
|
|
76
|
+
async def __aenter__(self) -> "AsyncChatClient":
|
|
77
|
+
"""Enable usage as an asynchronous context manager."""
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
81
|
+
"""Ensure connections are closed when exiting the context manager."""
|
|
82
|
+
await self.aclose()
|
chatapult/client.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
# Import our new custom exceptions
|
|
7
|
+
from .exceptions import APIError, ConfigurationError, NetworkError
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ChatClient:
|
|
13
|
+
"""
|
|
14
|
+
A synchronous client for interacting with Google Chat Webhooks.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, webhook_url: str, timeout: float = 10.0) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Initialize the ChatClient.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
webhook_url (str): The full Google Chat webhook URL.
|
|
23
|
+
timeout (float): Connection timeout in seconds. Defaults to 10.0.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ConfigurationError: If the webhook_url is empty or invalid.
|
|
27
|
+
"""
|
|
28
|
+
if not webhook_url:
|
|
29
|
+
raise ConfigurationError("A valid webhook_url must be provided.")
|
|
30
|
+
|
|
31
|
+
self.webhook_url = webhook_url
|
|
32
|
+
self._client = httpx.Client(timeout=timeout)
|
|
33
|
+
|
|
34
|
+
def send_message(
|
|
35
|
+
self, text: str, thread_name: Optional[str] = None
|
|
36
|
+
) -> Dict[str, Any]:
|
|
37
|
+
"""
|
|
38
|
+
Send a simple text message to the configured Google Chat Space.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
text (str): The markdown-formatted text to send.
|
|
42
|
+
thread_name (Optional[str]): The ID of an existing thread to reply to
|
|
43
|
+
(e.g., 'spaces/SPACE_ID/threads/THREAD_ID').
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Dict[str, Any]: The JSON response from the Google Chat API representing the message.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
APIError: If the Google API returns a non-200 status code.
|
|
50
|
+
NetworkError: If the network connection times out or fails.
|
|
51
|
+
"""
|
|
52
|
+
payload: Dict[str, Any] = {"text": text}
|
|
53
|
+
|
|
54
|
+
if thread_name:
|
|
55
|
+
payload["thread"] = {"name": thread_name}
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
response = self._client.post(self.webhook_url, json=payload)
|
|
59
|
+
response.raise_for_status()
|
|
60
|
+
return response.json()
|
|
61
|
+
|
|
62
|
+
except httpx.HTTPStatusError as e:
|
|
63
|
+
logger.error(
|
|
64
|
+
f"Google Chat API error: {e.response.status_code} - {e.response.text}"
|
|
65
|
+
)
|
|
66
|
+
raise APIError(
|
|
67
|
+
message=f"API request failed: {e.response.text}",
|
|
68
|
+
status_code=e.response.status_code,
|
|
69
|
+
) from e
|
|
70
|
+
|
|
71
|
+
except httpx.RequestError as e:
|
|
72
|
+
logger.error(f"Network error while connecting to Google Chat: {e}")
|
|
73
|
+
raise NetworkError(f"Network request failed: {str(e)}") from e
|
|
74
|
+
|
|
75
|
+
def close(self) -> None:
|
|
76
|
+
"""Close the underlying HTTP client connections."""
|
|
77
|
+
self._client.close()
|
|
78
|
+
|
|
79
|
+
def __enter__(self) -> "ChatClient":
|
|
80
|
+
"""Enable usage as a context manager."""
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
84
|
+
"""Ensure connections are closed when exiting the context manager."""
|
|
85
|
+
self.close()
|
chatapult/exceptions.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions for the Chatapult library.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ChatapultError(Exception):
|
|
7
|
+
"""
|
|
8
|
+
Base exception class for all Chatapult errors.
|
|
9
|
+
All other custom exceptions in this library inherit from this base class,
|
|
10
|
+
allowing users to catch any Chatapult-related error with a single except block.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigurationError(ChatapultError):
|
|
17
|
+
"""
|
|
18
|
+
Raised when there is a configuration issue, such as an empty or invalid webhook URL.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class APIError(ChatapultError):
|
|
25
|
+
"""
|
|
26
|
+
Raised when the Google Chat API returns an HTTP error response (e.g., 400 or 404).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, message: str, status_code: int) -> None:
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
self.status_code = status_code
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class NetworkError(ChatapultError):
|
|
35
|
+
"""
|
|
36
|
+
Raised when a network connection fails, times out, or the host is unreachable.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
pass
|
chatapult/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chatapult
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A fast, Pythonic API wrapper for Google Chat webhooks.
|
|
5
|
+
Author-email: Demetrios Lambropoulos <d.lambropoulos@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: api,async,google chat,notifications,webhook,wrapper
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Communications :: Chat
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Requires-Dist: httpx>=0.24.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: black>=24.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
25
|
+
Requires-Dist: pre-commit>=3.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-httpx>=0.22.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.3; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
# Chatapult
|
|
36
|
+
|
|
37
|
+
A fast, Pythonic API wrapper for Google Chat webhooks.
|
|
38
|
+
|
|
39
|
+
Chatapult is designed to make sending automated notifications, CI/CD alerts, and rich UI cards to Google Workspace Spaces effortless. Whether you need a simple synchronous alert or a high-throughput async notification integration, Chatapult handles the boilerplate so you can focus on your application.
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
[](LICENSE)
|
|
43
|
+
[](https://github.com/psf/black)
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
* **Zero Boilerplate:** Send a message to a Google Chat Space in three lines of code.
|
|
48
|
+
* **Async Ready:** First-class support for `asyncio`, making it perfect for FastAPI, Discord bots, and high-concurrency event loops.
|
|
49
|
+
* **Rich V2 Cards (Coming Soon):** Construct complex Google Chat Cards and Widgets using clean Python objects instead of nested JSON.
|
|
50
|
+
* **Threaded Replies:** Easily group related alerts by replying to specific message threads.
|
|
51
|
+
* **Fully Typed:** Built with standard Python type hints for excellent IDE autocomplete and static checking.
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
Install via pip:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install chatapult
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
**Security Note:** Never hardcode your webhook URLs. Always load them securely from environment variables or a secrets manager.
|
|
64
|
+
|
|
65
|
+
### Synchronous Usage
|
|
66
|
+
|
|
67
|
+
Perfect for simple scripts, cron jobs, or basic data pipelines.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import os
|
|
71
|
+
from chatapult import ChatClient, APIError
|
|
72
|
+
|
|
73
|
+
webhook_url = os.environ.get("GOOGLE_CHAT_WEBHOOK_URL")
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
with ChatClient(webhook_url) as client:
|
|
77
|
+
response = client.send_message("Hello from Chatapult!")
|
|
78
|
+
print("Message sent successfully!")
|
|
79
|
+
except APIError as e:
|
|
80
|
+
print(f"Failed to send message: {e}")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Asynchronous Usage
|
|
84
|
+
|
|
85
|
+
Ideal for web servers, async task queues, or applications where you cannot block the main thread.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import asyncio
|
|
89
|
+
import os
|
|
90
|
+
from chatapult import AsyncChatClient, NetworkError
|
|
91
|
+
|
|
92
|
+
async def main():
|
|
93
|
+
webhook_url = os.environ.get("GOOGLE_CHAT_WEBHOOK_URL")
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
async with AsyncChatClient(webhook_url) as client:
|
|
97
|
+
await client.send_message("Hello from the async event loop!")
|
|
98
|
+
print("Async message sent successfully!")
|
|
99
|
+
except NetworkError as e:
|
|
100
|
+
print(f"Network issue encountered: {e}")
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
asyncio.run(main())
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Advanced Usage
|
|
107
|
+
|
|
108
|
+
Documentation for sending V2 Cards, threaded replies, and handling rate-limit exceptions will be added here as features are released.
|
|
109
|
+
|
|
110
|
+
## Contributing
|
|
111
|
+
Contributions are welcome! If you'd like to help improve Chatapult, please review our [Contributing Guidelines](CONTRIBUTING.md) and open an issue or pull request.
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
This project is licensed under the MIT License - see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
chatapult/__init__.py,sha256=15l6_leVRYapJfm2Ap4VDMycnG7jvcjoPFo1eez3l7Q,784
|
|
2
|
+
chatapult/async_client.py,sha256=fxRmPOas-nz4LTpPTcEwMaCfi-L9bs4F7LnYrPPsJhI,2777
|
|
3
|
+
chatapult/client.py,sha256=KQZDLOI7iD7Tdyxce6PEtOs4MWSwdkkTFY02ptridnM,2842
|
|
4
|
+
chatapult/exceptions.py,sha256=NEzZq02YvzHtPaHNrenjALrVem_Nk96Lr-9JoZNVir0,908
|
|
5
|
+
chatapult/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
chatapult-0.1.0.dist-info/METADATA,sha256=kQ8NCD1BDQRuUTxkwDKQeLqQZU7OKvccG--psX9oqWk,4273
|
|
7
|
+
chatapult-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
chatapult-0.1.0.dist-info/licenses/LICENSE,sha256=YKs-BeMD4E_SIN9dxk98anfj7ccELurtRG34m7IzvTM,1079
|
|
9
|
+
chatapult-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Demetrios Lambropoulos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|