Name | shirasu JSON |
Version |
1.1.6
JSON |
| download |
home_page | |
Summary | A simple bot framework to study the principles, based on OneBot v11. |
upload_time | 2023-02-03 07:59:20 |
maintainer | |
docs_url | None |
author | kifuan |
requires_python | >=3.10,<4.0 |
license | MIT |
keywords |
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# Shirasu
## Introduction
`Shirasu` is a simple bot framework to study the principles, based on `OneBot v11` (especially `go-cqhttp`).
出于练习英语的目的,此 `README` 我就用简单英文写了,我也写了一篇[中文博客](https://blog.kifuan.me/archives/onebot-v11-python-sdk-details)介绍这个项目,内容大体一致但更详细一点(来自寒假末尾最后的倔强,其实是 `Grammarly` 监督才能避免一堆语法错误)。
## Usage
Install `shirasu` first. `Python 3.10+` is required.
```bash
> pip install shirasu
```
To create a simple bot application, you should create a `.py` file containing` the following code:
```python
import asyncio
from shirasu import AddonPool, OneBotClient
if __name__ == '__main__':
pool = AddonPool.from_modules(
'shirasu.addons.echo',
'shirasu.addons.help',
)
asyncio.run(OneBotClient.listen(pool))
```
Then, create a `shirasu.yml`, which could be other names configured by specifying the `config` argument of `OneBotClient.listen`.
```yaml
# The WebSocket server URL(not reverse WebSocket).
ws: ws://127.0.0.1:8080
# The prefixes of commands.
command_prefixes: ['/', '']
# Superuser accounts.
superusers: [123456, 234567]
# The separator of commands, using regex.
command_separator: '\s+'
# The configurations of addons.
addons:
help:
show_addon_list: true
```
For more information please see the next chapter.
## Features
There are some features slightly different from others.
### Dependency Injection System
I'm used to `Spring`'s DI system, like:
```java
@Autowired
private Foo foo;
```
I know it's better to use a setter or constructor, but let me just keep it simple.
However, DI in `FastAPI` is like this:
```python
async def get_foo() -> Foo:
return Foo()
async def use(foo: Foo = Depends(get_foo)) -> None:
await foo.use()
```
There are other frameworks like `Spring` for sure, but I implemented a simple DI system to study the principles.
```python
import asyncio
from datetime import datetime
from shirasu.di import inject, provide
@provide('now')
async def provide_now() -> datetime:
return datetime.now()
@provide('today')
async def provide_today(now: datetime) -> int:
await asyncio.sleep(.1)
return now.day
@inject()
async def use_today(today: int) -> None:
print(today)
@inject()
async def use_now(now: datetime) -> None:
await asyncio.sleep(.1)
print(now.year)
# 1
await use_today()
# 2023
await use_now()
```
To keep consistent, all functions should be `async`.
### Addon System
Addons in `shirasu` have no business with runtime context when creating them. Therefore you can `import` them from other modules **directly** without ensuring whether they have been imported by `shirasu`.
To write an addon, take `shirasu.addons.echo` as an example:
```python
from shirasu import Client, Addon, MessageEvent, command
echo = Addon(
name='echo',
usage='/echo text',
description='Sends your text back.',
)
@echo.receive(command('echo'))
async def handle_echo(client: Client, event: MessageEvent) -> None:
await client.send(event.arg)
```
Note that the receiver(i.e. `handle_echo`) **is injected automatically**, so you don't need to add `@inject()` decorator to it.
For configurations, take `shirasu.addons.square` as an example:
```python
from pydantic import BaseModel
from shirasu import Client, Addon, MessageEvent, command
class SquareConfig(BaseModel):
precision: int = 2
square = Addon(
name='square',
usage='/square number',
description='Calculates the square of given number.',
config_model=SquareConfig,
)
@square.receive(command('square'))
async def handle_square(client: Client, event: MessageEvent, config: SquareConfig) -> None:
arg = event.arg
try:
result = round(float(arg) ** 2, config.precision)
await client.send(f'{result:g}')
except ValueError:
await client.reject(f'Invalid number: {arg}')
```
The configurations should be written in `shirasu.yml`, for example, if you want to set the `precision` to `3`:
```yaml
# Configurations for addons.
addons:
square:
# Configurations for addon square.
precision: 3
```
### Unit tests
It's hard to write tests for some frameworks, so I tried my best to make it simple for this framework.
To start with, you should install `pytest` and `pytest-asyncio` as our framework is designed to be async. Take the `square` addon as an example:
```python
import pytest
from shirasu import MockClient, AddonPool
@pytest.mark.asyncio
async def test_square():
pool = AddonPool.from_modules('shirasu.addons.square')
client = MockClient(pool)
await client.post_message('/square 2')
square2_msg = await client.get_message()
assert square2_msg.plain_text == '4'
await client.post_message('/square a')
rejected_msg = await client.get_message_event()
assert rejected_msg.is_rejected
```
However, if your addon does not send any message sometimes, you could assert that the `asyncio.TimeoutError` will be raised. Take the `echo` addon as an example:
```python
import pytest
import asyncio
from shirasu import MockClient, AddonPool
@pytest.mark.asyncio
async def test_echo():
pool = AddonPool.from_modules('shirasu.addons.echo')
client = MockClient(pool)
await client.post_message('/echo hello')
echo_msg = await client.get_message()
assert echo_msg.plain_text == 'hello'
await client.post_message('echo hello')
with pytest.raises(asyncio.TimeoutError):
await client.get_message()
```
In this case, we tested the `command_prefixes`, not the addon itself.
Raw data
{
"_id": null,
"home_page": "",
"name": "shirasu",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.10,<4.0",
"maintainer_email": "",
"keywords": "",
"author": "kifuan",
"author_email": "kifuan@foxmail.com",
"download_url": "https://files.pythonhosted.org/packages/5b/13/ab61d4f644157ccb456d5098cd0ef57f72575fbe43f0f4e93da077b47f98/shirasu-1.1.6.tar.gz",
"platform": null,
"description": "# Shirasu\n\n## Introduction\n\n`Shirasu` is a simple bot framework to study the principles, based on `OneBot v11` (especially `go-cqhttp`).\n\n\u51fa\u4e8e\u7ec3\u4e60\u82f1\u8bed\u7684\u76ee\u7684\uff0c\u6b64 `README` \u6211\u5c31\u7528\u7b80\u5355\u82f1\u6587\u5199\u4e86\uff0c\u6211\u4e5f\u5199\u4e86\u4e00\u7bc7[\u4e2d\u6587\u535a\u5ba2](https://blog.kifuan.me/archives/onebot-v11-python-sdk-details)\u4ecb\u7ecd\u8fd9\u4e2a\u9879\u76ee\uff0c\u5185\u5bb9\u5927\u4f53\u4e00\u81f4\u4f46\u66f4\u8be6\u7ec6\u4e00\u70b9\uff08\u6765\u81ea\u5bd2\u5047\u672b\u5c3e\u6700\u540e\u7684\u5014\u5f3a\uff0c\u5176\u5b9e\u662f `Grammarly` \u76d1\u7763\u624d\u80fd\u907f\u514d\u4e00\u5806\u8bed\u6cd5\u9519\u8bef\uff09\u3002\n\n## Usage\n\nInstall `shirasu` first. `Python 3.10+` is required.\n\n```bash\n> pip install shirasu\n```\n\nTo create a simple bot application, you should create a `.py` file containing` the following code:\n\n```python\nimport asyncio\nfrom shirasu import AddonPool, OneBotClient\n\n\nif __name__ == '__main__':\n pool = AddonPool.from_modules(\n 'shirasu.addons.echo',\n 'shirasu.addons.help',\n )\n asyncio.run(OneBotClient.listen(pool))\n```\n\nThen, create a `shirasu.yml`, which could be other names configured by specifying the `config` argument of `OneBotClient.listen`.\n\n```yaml\n# The WebSocket server URL(not reverse WebSocket).\nws: ws://127.0.0.1:8080\n\n# The prefixes of commands.\ncommand_prefixes: ['/', '']\n\n# Superuser accounts.\nsuperusers: [123456, 234567]\n\n# The separator of commands, using regex.\ncommand_separator: '\\s+'\n\n# The configurations of addons.\naddons:\n help:\n show_addon_list: true\n```\n\nFor more information please see the next chapter.\n\n## Features\n\nThere are some features slightly different from others.\n\n### Dependency Injection System\n\nI'm used to `Spring`'s DI system, like:\n\n```java\n@Autowired\nprivate Foo foo;\n```\n\nI know it's better to use a setter or constructor, but let me just keep it simple.\n\nHowever, DI in `FastAPI` is like this:\n\n```python\nasync def get_foo() -> Foo:\n return Foo()\n\nasync def use(foo: Foo = Depends(get_foo)) -> None:\n await foo.use()\n```\n\nThere are other frameworks like `Spring` for sure, but I implemented a simple DI system to study the principles.\n\n```python\nimport asyncio\nfrom datetime import datetime\nfrom shirasu.di import inject, provide\n\n\n@provide('now')\nasync def provide_now() -> datetime:\n return datetime.now()\n\n\n@provide('today')\nasync def provide_today(now: datetime) -> int:\n await asyncio.sleep(.1)\n return now.day\n\n\n@inject()\nasync def use_today(today: int) -> None:\n print(today)\n\n\n@inject()\nasync def use_now(now: datetime) -> None:\n await asyncio.sleep(.1)\n print(now.year)\n\n\n# 1\nawait use_today()\n\n# 2023\nawait use_now()\n```\n\nTo keep consistent, all functions should be `async`.\n\n### Addon System\n\nAddons in `shirasu` have no business with runtime context when creating them. Therefore you can `import` them from other modules **directly** without ensuring whether they have been imported by `shirasu`.\n\nTo write an addon, take `shirasu.addons.echo` as an example:\n\n```python\nfrom shirasu import Client, Addon, MessageEvent, command\n\n\necho = Addon(\n name='echo',\n usage='/echo text',\n description='Sends your text back.',\n)\n\n\n@echo.receive(command('echo'))\nasync def handle_echo(client: Client, event: MessageEvent) -> None:\n await client.send(event.arg)\n```\n\nNote that the receiver(i.e. `handle_echo`) **is injected automatically**, so you don't need to add `@inject()` decorator to it.\n\nFor configurations, take `shirasu.addons.square` as an example:\n\n```python\nfrom pydantic import BaseModel\nfrom shirasu import Client, Addon, MessageEvent, command\n\n\nclass SquareConfig(BaseModel):\n precision: int = 2\n\n\nsquare = Addon(\n name='square',\n usage='/square number',\n description='Calculates the square of given number.',\n config_model=SquareConfig,\n)\n\n\n@square.receive(command('square'))\nasync def handle_square(client: Client, event: MessageEvent, config: SquareConfig) -> None:\n arg = event.arg\n\n try:\n result = round(float(arg) ** 2, config.precision)\n await client.send(f'{result:g}')\n except ValueError:\n await client.reject(f'Invalid number: {arg}')\n```\n\nThe configurations should be written in `shirasu.yml`, for example, if you want to set the `precision` to `3`:\n\n```yaml\n# Configurations for addons.\naddons:\n square:\n # Configurations for addon square.\n precision: 3\n```\n\n### Unit tests\n\nIt's hard to write tests for some frameworks, so I tried my best to make it simple for this framework.\n\nTo start with, you should install `pytest` and `pytest-asyncio` as our framework is designed to be async. Take the `square` addon as an example:\n\n```python\nimport pytest\nfrom shirasu import MockClient, AddonPool\n\n\n@pytest.mark.asyncio\nasync def test_square():\n pool = AddonPool.from_modules('shirasu.addons.square')\n client = MockClient(pool)\n\n await client.post_message('/square 2')\n square2_msg = await client.get_message()\n assert square2_msg.plain_text == '4'\n\n await client.post_message('/square a')\n rejected_msg = await client.get_message_event()\n assert rejected_msg.is_rejected\n```\n\nHowever, if your addon does not send any message sometimes, you could assert that the `asyncio.TimeoutError` will be raised. Take the `echo` addon as an example:\n\n```python\nimport pytest\nimport asyncio\nfrom shirasu import MockClient, AddonPool\n\n\n@pytest.mark.asyncio\nasync def test_echo():\n pool = AddonPool.from_modules('shirasu.addons.echo')\n client = MockClient(pool)\n\n await client.post_message('/echo hello')\n echo_msg = await client.get_message()\n assert echo_msg.plain_text == 'hello'\n\n await client.post_message('echo hello')\n with pytest.raises(asyncio.TimeoutError):\n await client.get_message()\n```\n\nIn this case, we tested the `command_prefixes`, not the addon itself.",
"bugtrack_url": null,
"license": "MIT",
"summary": "A simple bot framework to study the principles, based on OneBot v11.",
"version": "1.1.6",
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "116674e6354bfbd2bce15dfdf2e688c2ac92f423d5f561cc651debc563b1d0f5",
"md5": "014653adf29ed25fdbecbd32684bab15",
"sha256": "055a06c7209fa824855975cba9cf2fdceb2191532833e2a41ea28c5249273f5f"
},
"downloads": -1,
"filename": "shirasu-1.1.6-py3-none-any.whl",
"has_sig": false,
"md5_digest": "014653adf29ed25fdbecbd32684bab15",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10,<4.0",
"size": 23028,
"upload_time": "2023-02-03T07:59:18",
"upload_time_iso_8601": "2023-02-03T07:59:18.044031Z",
"url": "https://files.pythonhosted.org/packages/11/66/74e6354bfbd2bce15dfdf2e688c2ac92f423d5f561cc651debc563b1d0f5/shirasu-1.1.6-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "5b13ab61d4f644157ccb456d5098cd0ef57f72575fbe43f0f4e93da077b47f98",
"md5": "8bd9effae2a57876c2b79fb4ab538c5b",
"sha256": "76735929629f45a7c9e1019afb1d3c4b7324ff70b374fe4a7efc92258d51e2d9"
},
"downloads": -1,
"filename": "shirasu-1.1.6.tar.gz",
"has_sig": false,
"md5_digest": "8bd9effae2a57876c2b79fb4ab538c5b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10,<4.0",
"size": 33895,
"upload_time": "2023-02-03T07:59:20",
"upload_time_iso_8601": "2023-02-03T07:59:20.769023Z",
"url": "https://files.pythonhosted.org/packages/5b/13/ab61d4f644157ccb456d5098cd0ef57f72575fbe43f0f4e93da077b47f98/shirasu-1.1.6.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-02-03 07:59:20",
"github": false,
"gitlab": false,
"bitbucket": false,
"lcname": "shirasu"
}