python-yappy 0.1.1__tar.gz → 0.1.2__tar.gz
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.
- {python_yappy-0.1.1 → python_yappy-0.1.2}/PKG-INFO +65 -3
- {python_yappy-0.1.1 → python_yappy-0.1.2}/README.md +63 -2
- {python_yappy-0.1.1 → python_yappy-0.1.2}/pyproject.toml +2 -1
- {python_yappy-0.1.1 → python_yappy-0.1.2}/pyproject.toml.orig +2 -1
- python_yappy-0.1.2/src/yappy/__init__.py +6 -0
- {python_yappy-0.1.1 → python_yappy-0.1.2}/src/yappy/api.py +18 -3
- python_yappy-0.1.2/src/yappy/app.py +527 -0
- {python_yappy-0.1.1 → python_yappy-0.1.2}/src/yappy/core.py +109 -28
- python_yappy-0.1.2/src/yappy/errors.py +79 -0
- python_yappy-0.1.2/src/yappy/logger.py +69 -0
- python_yappy-0.1.2/src/yappy/parser.py +154 -0
- python_yappy-0.1.1/src/yappy/__init__.py +0 -1
- python_yappy-0.1.1/src/yappy/app.py +0 -401
- python_yappy-0.1.1/src/yappy/errors.py +0 -8
- python_yappy-0.1.1/src/yappy/parser.py +0 -69
- {python_yappy-0.1.1 → python_yappy-0.1.2}/src/yappy/consts.py +0 -0
- {python_yappy-0.1.1 → python_yappy-0.1.2}/src/yappy/utils.py +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: python-yappy
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: API Bindings for Yapper!
|
|
5
5
|
Author: H1387Lmao
|
|
6
6
|
Requires-Dist: aiohttp>=3.14.5
|
|
7
7
|
Requires-Dist: load-dotenv>=0.1.0
|
|
8
|
+
Requires-Dist: rich>=13.0
|
|
8
9
|
Requires-Dist: uv-build>=0.13.1
|
|
9
10
|
Requires-Python: >=3.14
|
|
10
11
|
Description-Content-Type: text/markdown
|
|
@@ -24,7 +25,8 @@ YapPy brings a Python-friendly interface to [Yapper](https://yapper.website), a
|
|
|
24
25
|
- **Commands** — register named commands with descriptions.
|
|
25
26
|
- **Context-based interactions** — reply to messages through a context object.
|
|
26
27
|
- **Core models** — work with `Context`, `Message`, `Channel`, and `User` objects.
|
|
27
|
-
- **
|
|
28
|
+
- **Argument validation** — type-hinted command parameters are converted for you, and mistakes get a helpful reply.
|
|
29
|
+
- **Rich logging** — coloured logs and readable tracebacks; `verbose=True` adds debug output.
|
|
28
30
|
|
|
29
31
|
## Installation
|
|
30
32
|
|
|
@@ -114,7 +116,7 @@ async def on_message(ctx):
|
|
|
114
116
|
if ctx.is_bot:
|
|
115
117
|
return
|
|
116
118
|
|
|
117
|
-
await
|
|
119
|
+
await bot.process_command(ctx)
|
|
118
120
|
```
|
|
119
121
|
|
|
120
122
|
The event name and handler arguments should match the event interface supported by the installed YapPy version.
|
|
@@ -134,6 +136,66 @@ async def ping(ctx):
|
|
|
134
136
|
|
|
135
137
|
The `name` identifies the command, and `description` describes its purpose. Command registration and invocation behavior depend on the current implementation.
|
|
136
138
|
|
|
139
|
+
### Command arguments
|
|
140
|
+
|
|
141
|
+
Arguments from the message are matched to your function's parameters. Add type hints and YapPy converts them for you:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
@bot.command(name="add", description="Adds two numbers")
|
|
145
|
+
async def add(ctx, a: int, b: int = 0):
|
|
146
|
+
await ctx.reply(f"{a} + {b} = {a + b}")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
| Annotation | Accepts |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| `str` | the text exactly as typed (use `"quotes"` for spaces) |
|
|
152
|
+
| `int`, `float` | numbers |
|
|
153
|
+
| `bool` | `true`/`false`, `yes`/`no`, `on`/`off`, `1`/`0` |
|
|
154
|
+
| `User`, `Channel`, `Role` | a mention such as `<@123>` |
|
|
155
|
+
| *(none)* | guessed: whole numbers become `int`, decimals `float`, mentions are resolved, anything else is `str` |
|
|
156
|
+
|
|
157
|
+
Parameters with a default are optional, and `*args` collects any remaining arguments.
|
|
158
|
+
|
|
159
|
+
### Command errors
|
|
160
|
+
|
|
161
|
+
If someone uses a command the wrong way, the bot tells them instead of failing silently:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
!add
|
|
165
|
+
Missing required argument 'a'.
|
|
166
|
+
Usage: `!add <a> [b]`
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Too many arguments and values of the wrong type are handled the same way. Unknown commands are ignored, and an exception raised inside a command is logged rather than sent to chat.
|
|
170
|
+
|
|
171
|
+
To handle these yourself, register a `command_error` handler. It replaces the default replies:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from yappy import UnknownCommand
|
|
175
|
+
|
|
176
|
+
@bot.event("command_error")
|
|
177
|
+
async def on_command_error(ctx, error):
|
|
178
|
+
if isinstance(error, UnknownCommand):
|
|
179
|
+
return
|
|
180
|
+
await ctx.reply(str(error))
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`error` is one of `UnknownCommand`, `MissingArgument`, `TooManyArguments`, `BadArgument` or `CommandInvokeError` (all subclasses of `yappy.CommandError`). For `CommandInvokeError`, the exception your command raised is `error.original`, and `error.command` is the command involved.
|
|
184
|
+
|
|
185
|
+
### Logging
|
|
186
|
+
|
|
187
|
+
YapPy logs through Python's `logging` module under the name `yappy` and prints it with [Rich](https://github.com/Textualize/rich). Exceptions appear as readable tracebacks. `App(verbose=True)` also shows debug output: events, HTTP requests and command invocations.
|
|
188
|
+
|
|
189
|
+
To configure logging yourself, create the app with `configure_logging=False`:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
import logging
|
|
193
|
+
from yappy import App, setup_logging
|
|
194
|
+
|
|
195
|
+
bot = App(configure_logging=False)
|
|
196
|
+
setup_logging(level=logging.WARNING, show_locals=True) # or attach your own handlers to logging.getLogger("yappy")
|
|
197
|
+
```
|
|
198
|
+
|
|
137
199
|
### Context and models
|
|
138
200
|
|
|
139
201
|
YapPy currently provides these core models:
|
|
@@ -13,7 +13,8 @@ YapPy brings a Python-friendly interface to [Yapper](https://yapper.website), a
|
|
|
13
13
|
- **Commands** — register named commands with descriptions.
|
|
14
14
|
- **Context-based interactions** — reply to messages through a context object.
|
|
15
15
|
- **Core models** — work with `Context`, `Message`, `Channel`, and `User` objects.
|
|
16
|
-
- **
|
|
16
|
+
- **Argument validation** — type-hinted command parameters are converted for you, and mistakes get a helpful reply.
|
|
17
|
+
- **Rich logging** — coloured logs and readable tracebacks; `verbose=True` adds debug output.
|
|
17
18
|
|
|
18
19
|
## Installation
|
|
19
20
|
|
|
@@ -103,7 +104,7 @@ async def on_message(ctx):
|
|
|
103
104
|
if ctx.is_bot:
|
|
104
105
|
return
|
|
105
106
|
|
|
106
|
-
await
|
|
107
|
+
await bot.process_command(ctx)
|
|
107
108
|
```
|
|
108
109
|
|
|
109
110
|
The event name and handler arguments should match the event interface supported by the installed YapPy version.
|
|
@@ -123,6 +124,66 @@ async def ping(ctx):
|
|
|
123
124
|
|
|
124
125
|
The `name` identifies the command, and `description` describes its purpose. Command registration and invocation behavior depend on the current implementation.
|
|
125
126
|
|
|
127
|
+
### Command arguments
|
|
128
|
+
|
|
129
|
+
Arguments from the message are matched to your function's parameters. Add type hints and YapPy converts them for you:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
@bot.command(name="add", description="Adds two numbers")
|
|
133
|
+
async def add(ctx, a: int, b: int = 0):
|
|
134
|
+
await ctx.reply(f"{a} + {b} = {a + b}")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| Annotation | Accepts |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `str` | the text exactly as typed (use `"quotes"` for spaces) |
|
|
140
|
+
| `int`, `float` | numbers |
|
|
141
|
+
| `bool` | `true`/`false`, `yes`/`no`, `on`/`off`, `1`/`0` |
|
|
142
|
+
| `User`, `Channel`, `Role` | a mention such as `<@123>` |
|
|
143
|
+
| *(none)* | guessed: whole numbers become `int`, decimals `float`, mentions are resolved, anything else is `str` |
|
|
144
|
+
|
|
145
|
+
Parameters with a default are optional, and `*args` collects any remaining arguments.
|
|
146
|
+
|
|
147
|
+
### Command errors
|
|
148
|
+
|
|
149
|
+
If someone uses a command the wrong way, the bot tells them instead of failing silently:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
!add
|
|
153
|
+
Missing required argument 'a'.
|
|
154
|
+
Usage: `!add <a> [b]`
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Too many arguments and values of the wrong type are handled the same way. Unknown commands are ignored, and an exception raised inside a command is logged rather than sent to chat.
|
|
158
|
+
|
|
159
|
+
To handle these yourself, register a `command_error` handler. It replaces the default replies:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from yappy import UnknownCommand
|
|
163
|
+
|
|
164
|
+
@bot.event("command_error")
|
|
165
|
+
async def on_command_error(ctx, error):
|
|
166
|
+
if isinstance(error, UnknownCommand):
|
|
167
|
+
return
|
|
168
|
+
await ctx.reply(str(error))
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`error` is one of `UnknownCommand`, `MissingArgument`, `TooManyArguments`, `BadArgument` or `CommandInvokeError` (all subclasses of `yappy.CommandError`). For `CommandInvokeError`, the exception your command raised is `error.original`, and `error.command` is the command involved.
|
|
172
|
+
|
|
173
|
+
### Logging
|
|
174
|
+
|
|
175
|
+
YapPy logs through Python's `logging` module under the name `yappy` and prints it with [Rich](https://github.com/Textualize/rich). Exceptions appear as readable tracebacks. `App(verbose=True)` also shows debug output: events, HTTP requests and command invocations.
|
|
176
|
+
|
|
177
|
+
To configure logging yourself, create the app with `configure_logging=False`:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
import logging
|
|
181
|
+
from yappy import App, setup_logging
|
|
182
|
+
|
|
183
|
+
bot = App(configure_logging=False)
|
|
184
|
+
setup_logging(level=logging.WARNING, show_locals=True) # or attach your own handlers to logging.getLogger("yappy")
|
|
185
|
+
```
|
|
186
|
+
|
|
126
187
|
### Context and models
|
|
127
188
|
|
|
128
189
|
YapPy currently provides these core models:
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-yappy"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "API Bindings for Yapper!"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"aiohttp>=3.14.5",
|
|
9
9
|
"load-dotenv>=0.1.0",
|
|
10
|
+
"rich>=13.0",
|
|
10
11
|
"uv-build>=0.13.1",
|
|
11
12
|
]
|
|
12
13
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-yappy"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "API Bindings for Yapper!"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -10,6 +10,7 @@ requires-python = ">=3.14"
|
|
|
10
10
|
dependencies = [
|
|
11
11
|
"aiohttp>=3.14.5",
|
|
12
12
|
"load-dotenv>=0.1.0",
|
|
13
|
+
"rich>=13.0",
|
|
13
14
|
"uv-build>=0.13.1",
|
|
14
15
|
]
|
|
15
16
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
from .consts import BASE_URL
|
|
2
2
|
import aiohttp
|
|
3
|
+
import logging
|
|
3
4
|
import random
|
|
4
5
|
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
5
9
|
|
|
6
10
|
async def api_push(session: aiohttp.ClientSession, method: str, path: str, body=None, tries=3):
|
|
7
11
|
headers = {
|
|
@@ -9,10 +13,17 @@ async def api_push(session: aiohttp.ClientSession, method: str, path: str, body=
|
|
|
9
13
|
"Content-Type": "application/json"
|
|
10
14
|
}
|
|
11
15
|
|
|
16
|
+
started = time.perf_counter()
|
|
12
17
|
try:
|
|
13
18
|
async with session.request(method, BASE_URL + path, json=body, headers=headers, timeout=40) as r:
|
|
14
19
|
r.raise_for_status()
|
|
15
|
-
|
|
20
|
+
data = await r.json()
|
|
21
|
+
if not path.startswith("/events"): # the long poll would be noise
|
|
22
|
+
log.debug(
|
|
23
|
+
"%s %s -> %s (%.0f ms)", method, path, r.status,
|
|
24
|
+
(time.perf_counter() - started) * 1000
|
|
25
|
+
)
|
|
26
|
+
return data
|
|
16
27
|
except aiohttp.ClientResponseError as e:
|
|
17
28
|
try:
|
|
18
29
|
err = await e.history[-1].json() if e.history else {}
|
|
@@ -25,8 +36,12 @@ async def api_push(session: aiohttp.ClientSession, method: str, path: str, body=
|
|
|
25
36
|
wait = int(e.headers.get("Retry-After") or 0) if e.status == 429 or busy else 0
|
|
26
37
|
|
|
27
38
|
if 0 < wait <= 60 and tries > 1:
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
delay = wait * (1 + random.random() * 0.5)
|
|
40
|
+
log.warning(
|
|
41
|
+
"%s on %s %s, retrying in %.1fs",
|
|
42
|
+
"Server busy" if busy else "Rate limited", method, path, delay
|
|
43
|
+
)
|
|
44
|
+
await asyncio.sleep(delay)
|
|
30
45
|
return await api_push(session, method, path, body, tries - 1)
|
|
31
46
|
|
|
32
47
|
raise RuntimeError(f"{method} {path} failed ({e.status}): {msg or e.message}") from None
|