shirasu


Nameshirasu JSON
Version 1.1.6 PyPI version JSON
download
home_page
SummaryA simple bot framework to study the principles, based on OneBot v11.
upload_time2023-02-03 07:59:20
maintainer
docs_urlNone
authorkifuan
requires_python>=3.10,<4.0
licenseMIT
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"
}
        
Elapsed time: 0.03848s