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 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()
@@ -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
+ ![Chatapult Banner](images/banner.png)
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
+ ![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
43
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.