Skip to content

Commit 30003b4

Browse files
authored
feat💥: revamp restful #2
2 parents dd084e5 + 8ee27a1 commit 30003b4

File tree

12 files changed

+279
-198
lines changed

12 files changed

+279
-198
lines changed

README.md

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,40 @@ A library for interactions.py allowing runtime API structures
44

55
## Installation
66

7-
Using pip:
7+
Using pip:
88
`pip install interactions-restful`
99

10-
Using poetry:
10+
Using poetry:
1111
`poetry add interactions-restful`
1212

1313
Don't forget to specify backend you want to use:
14-
- flask `pip install interactions-restful[flask]`
15-
- fastapi `pip install interactions-restful[fastapi]`
14+
- [FastAPI](https://fastapi.tiangolo.com/): `pip install interactions-restful[fastapi]`
15+
- [Quart](https://pgjones.gitlab.io/quart/index.html): `pip install interactions-restful[quart]`
1616

17-
## Simple example
17+
Also make sure to install an ASGI server:
18+
- [Uvicorn](https://www.uvicorn.org/): `pip install interactions-restful[uvicorn]`
19+
- [Hypercorn](https://pgjones.gitlab.io/hypercorn/): `pip install interactions-restful[hypercorn]`
20+
- [Daphne](https://github.com/django/daphne): `pip install interactions-restful[daphne]`
21+
22+
You can also install both your backend and ASGI server at once, for example
23+
`pip install interactions-restful[fastapi,uvicorn]`
24+
25+
## Simple (FastAPI) example
1826

1927
### Main file
28+
- `main.py`
2029

2130
```python
2231
import interactions
23-
from interactions_restful import setup
24-
from interactions_restful.backends.fast_api import FastAPI
32+
from fastapi import FastAPI
33+
from interactions_restful.backends.fastapi import FastAPIHandler
2534

26-
client = interactions.Client()
35+
app = FastAPI()
2736

28-
setup(client, FastAPI, "127.0.0.1", 5000)
37+
client = interactions.Client(token="token")
38+
FastAPIHandler(client, app)
2939

3040
client.load_extension("api")
31-
32-
client.start("token")
3341
```
3442

3543
### Extension file
@@ -44,17 +52,18 @@ class MyAPI(interactions.Extension):
4452
@route("GET", "/")
4553
def index(self):
4654
return {"status": "Hello, i.py"}
47-
55+
4856
@interactions.slash_command()
4957
async def test_command(self, ctx):
5058
await ctx.send("Hello, API")
51-
5259
```
5360

54-
## Backends
61+
### Run
5562

56-
Currently, library support only flask and fastapi as a backend for building an api, but if you don't want to use them you can create own backend.
63+
```bash
64+
uvicorn main:app --reload
65+
```
5766

58-
## Documentation
67+
## Backends
5968

60-
[FastAPI documentation](https://fastapi.tiangolo.com/)
69+
Currently, the library support only Quart and FastAPI as a backend for building an API, but if you don't want to use them you can create own backend.

examples/bot.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import interactions
2-
from interactions_restful import setup
3-
from interactions_restful.backends.fast_api import FastAPI
2+
from fastapi import FastAPI
43

4+
from interactions_restful.backends.fastapi import FastAPIHandler
5+
6+
app = FastAPI()
7+
client = interactions.Client(token="TOKEN")
8+
FastAPIHandler(client, app)
59

6-
client = interactions.Client()
7-
setup(client, FastAPI, "localhost", 8000)
810
client.load_extension("exts.my_ext")
911

1012

1113
@interactions.listen()
1214
async def on_startup():
1315
print(f"Bot `{client.user.username}` started up")
14-
15-
16-
client.start("TOKEN")

interactions_restful/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
from .decorators import *
2-
from .extension import *
1+
from .decorators import * # noqa

interactions_restful/abc.py

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,82 @@
1-
from abc import ABC, abstractmethod
2-
from typing import Callable, Coroutine
1+
import asyncio
2+
import inspect
3+
from asyncio import Task
4+
from typing import Any, Callable, Coroutine, Protocol
35

4-
__all__ = ("BaseApi", "BaseRouter")
6+
from interactions import CallbackObject, Client
57

8+
from .manager import APIManager
69

7-
class BaseRouter(ABC):
8-
@abstractmethod
9-
def add_endpoint_method(self, coro: Callable[..., Coroutine], endpoint: str, method: str, **kwargs):
10-
pass
10+
__all__ = ("BaseAPIHandler", "BaseRouterWrapper")
1111

1212

13-
class BaseApi(ABC):
14-
@abstractmethod
15-
def __init__(self, host: str, port: int, **kwargs):
16-
pass
13+
class BaseRouterWrapper(Protocol):
14+
def add_endpoint_method(
15+
self, coro: Callable[..., Coroutine], endpoint: str, method: str, **kwargs
16+
):
17+
...
1718

18-
@abstractmethod
19-
def add_endpoint_method(self, coro: Callable[..., Coroutine], endpoint: str, method: str, **kwargs):
20-
pass
19+
20+
class BaseAPIHandler:
21+
bot: Client
22+
app: Any
23+
task: Task | None
24+
api_client: APIManager
25+
26+
def __init__(self, bot: Client, app: Any):
27+
self.bot = bot
28+
self.app = app
29+
self.task = None
30+
self.api_client = APIManager(self.bot, self)
31+
32+
# there's a problem deferring starting up the bot to an asgi server -
33+
# sys.modules[__main__] will be replaced with the asgi server's module
34+
# this means any files that were in the file where the bot is will be lost
35+
36+
# we can't actually fully work around this - not perfectly, anyway
37+
# what we can somewhat rely on is that people are using the api wrappers
38+
# in their main file - you could place it somewhere else, but why would
39+
# you want to?
40+
# regardless, we exploit this by getting the module of the caller of the
41+
# api wrapper - in this, we have to go two calls back because the subclasses
42+
# count as a call
43+
# we can then use that module as the module to get the commands from during startup
44+
stack = inspect.stack()
45+
self.__original_module = inspect.getmodule(stack[2][0])
2146

2247
@staticmethod
23-
@abstractmethod
24-
def create_router(**kwargs) -> BaseRouter:
25-
pass
48+
def create_router(**kwargs) -> BaseRouterWrapper:
49+
...
50+
51+
def add_router(self, router: BaseRouterWrapper):
52+
...
53+
54+
def remove_router(self, router: BaseRouterWrapper):
55+
...
56+
57+
def add_endpoint_method(
58+
self, coro: Callable[..., Coroutine], endpoint: str, method: str, **kwargs
59+
):
60+
...
61+
62+
async def startup(self):
63+
commands = inspect.getmembers(
64+
self.__original_module, lambda x: isinstance(x, CallbackObject)
65+
)
66+
for command in commands:
67+
self.bot.add_command(command[1])
68+
69+
route_callables = inspect.getmembers(
70+
self.__original_module, predicate=lambda x: getattr(x, "__api__", False)
71+
)
72+
for callable in route_callables:
73+
callback = callable[1]
74+
data: dict = callback.__api__
75+
self.add_endpoint_method(callback, data["endpoint"], data["method"], **data["kwargs"])
2676

27-
def add_router(self, router: BaseRouter):
28-
pass
77+
self.task = asyncio.create_task(self.bot.astart())
2978

30-
@abstractmethod
31-
async def run(self):
32-
pass
79+
async def shutdown(self):
80+
await self.bot.stop()
81+
if self.task:
82+
self.task.cancel()

interactions_restful/backends/fast_api.py

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from typing import Callable, Coroutine
2+
3+
from fastapi import APIRouter, FastAPI
4+
from interactions import Client
5+
6+
from ..abc import BaseAPIHandler, BaseRouterWrapper
7+
8+
__all__ = ("FastAPIHandler",)
9+
10+
11+
class FastAPIRouterWrapper(BaseRouterWrapper):
12+
def __init__(self, **kwargs):
13+
kwargs.pop("name") # neccessary only for quart
14+
self.api_router = APIRouter(**kwargs)
15+
16+
def add_endpoint_method(
17+
self, coro: Callable[..., Coroutine], endpoint: str, method: str, **kwargs
18+
):
19+
self.api_router.add_api_route(endpoint, coro, methods=[method], **kwargs)
20+
21+
22+
class FastAPIHandler(BaseAPIHandler):
23+
app: FastAPI
24+
25+
def __init__(self, bot: Client, app: FastAPI):
26+
super().__init__(bot, app)
27+
self.app.add_event_handler("startup", self.startup)
28+
self.app.add_event_handler("shutdown", self.shutdown)
29+
30+
@staticmethod
31+
def create_router(**kwargs):
32+
return FastAPIRouterWrapper(**kwargs)
33+
34+
def add_router(self, router: FastAPIRouterWrapper):
35+
self.app.include_router(router.api_router)
36+
37+
def remove_router(self, router: FastAPIRouterWrapper):
38+
paths_to_check = frozenset(
39+
f"{router.api_router.prefix}{r.path}" for r in router.api_router.routes
40+
)
41+
for i, r in enumerate(self.app.router.routes):
42+
if r.path in paths_to_check: # type: ignore
43+
del self.app.router.routes[i]
44+
45+
def add_endpoint_method(
46+
self, coro: Callable[..., Coroutine], endpoint: str, method: str, **kwargs
47+
):
48+
self.app.add_api_route(endpoint, coro, methods=[method], **kwargs)

interactions_restful/backends/flask_api.py

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)