What is my purpose?
This commit is contained in:
commit
89db0bb24d
29 changed files with 1607 additions and 0 deletions
4
.env-example
Normal file
4
.env-example
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# For information about this variables check config.py
|
||||||
|
|
||||||
|
SLACK_TOKEN=xxx
|
||||||
|
TELEGRAM_TOKEN=xxx
|
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
*.py[c|o]
|
||||||
|
__pycache__
|
||||||
|
.vscode
|
||||||
|
*~
|
||||||
|
*.cert
|
||||||
|
.env-local
|
||||||
|
test.py
|
||||||
|
|
||||||
|
# Distribution
|
||||||
|
dist
|
||||||
|
*.egg-info
|
||||||
|
pip-wheel-metadata
|
20
Dockerfile.dev
Normal file
20
Dockerfile.dev
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
FROM alpine:3.11
|
||||||
|
|
||||||
|
ENV PYTHON_VERSION=3.8.2-r0
|
||||||
|
ENV APP_PORT 8080
|
||||||
|
ENV BUILD_DIR /tmp/build
|
||||||
|
ENV BUTTERROBOT_VERSION 0.0.1
|
||||||
|
|
||||||
|
WORKDIR ${BUILD_DIR}
|
||||||
|
COPY poetry.lock ${BUILD_DIR}/poetry.lock
|
||||||
|
COPY pyproject.toml ${BUILD_DIR}/pyproject.toml
|
||||||
|
COPY ./butterrobot ${BUILD_DIR}/butterrobot
|
||||||
|
COPY ./butterrobot_plugins_contrib ${BUILD_DIR}/butterrobot_plugins_contrib
|
||||||
|
RUN apk --update add python3-dev==${PYTHON_VERSION} gcc musl-dev libffi-dev openssl-dev && \
|
||||||
|
pip3 install poetry && \
|
||||||
|
poetry build && \
|
||||||
|
pip3 install ${BUILD_DIR}/dist/butterrobot-${BUTTERROBOT_VERSION}.tar.gz && \
|
||||||
|
rm -rf ${BUILD_DIR}
|
||||||
|
COPY ./docker/bin/start-server.sh /usr/local/bin/start-server
|
||||||
|
|
||||||
|
CMD ["/usr/local/bin/start-server"]
|
17
Makefile
Normal file
17
Makefile
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Local development
|
||||||
|
setup:
|
||||||
|
poetry install
|
||||||
|
|
||||||
|
docker@build:
|
||||||
|
docker build -t fmartingr/butterrobot .
|
||||||
|
|
||||||
|
podman@build:
|
||||||
|
podman build -t fmartingr/butterrobot .
|
||||||
|
|
||||||
|
docker@save:
|
||||||
|
make docker@build
|
||||||
|
docker image save fmartingr/butterrobot -o fmartingr-butterrobot-docker-image.tar
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf dist
|
||||||
|
rm -rf butterrobot.egg-info
|
86
README.md
Normal file
86
README.md
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# Butter Robot
|
||||||
|
|
||||||
|
[](https://quay.io/repository/fmartingr/butterrobot)
|
||||||
|
|
||||||
|
Python framework to create bots for several platforms.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> What is my purpose?
|
||||||
|
|
||||||
|
## Supported platforms
|
||||||
|
|
||||||
|
| Name | Receive messages | Send messages |
|
||||||
|
| --------------- | ---------------- | ------------- |
|
||||||
|
| Slack (app) | Yes | Yes |
|
||||||
|
| Slack (webhook) | Planned | No[^1] |
|
||||||
|
| Telegram | Yes | Yes |
|
||||||
|
|
||||||
|
[^1]: Slack webhooks only supports answering to incoming event, not
|
||||||
|
sending messages on demand.
|
||||||
|
|
||||||
|
## Provided plugins
|
||||||
|
|
||||||
|
### Butter robot
|
||||||
|
|
||||||
|
- [ ] Help
|
||||||
|
- [ ] Usage
|
||||||
|
- [ ] Changelog
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
- [x] Ping
|
||||||
|
|
||||||
|
### Fun and entertainment
|
||||||
|
|
||||||
|
- [ ] Dice roll
|
||||||
|
- [x] Loquito
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### PyPi
|
||||||
|
|
||||||
|
You can run it directly by installing the package and calling it
|
||||||
|
with `python` though this is not recommended and only intended for
|
||||||
|
development purposes.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pip install --user butterrobot
|
||||||
|
$ python -m butterrobot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Containers
|
||||||
|
|
||||||
|
The `fmartingr/butterrobot` container image is published on quay.io to
|
||||||
|
use with your favourite tool:
|
||||||
|
|
||||||
|
```
|
||||||
|
podman run -d --name butterrobot -p 8080:8080 quay.io/fmartingr/butterrobot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
To run the project locally you will need [poetry](https://python-poetry.org/).
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone git@github.com:fmartingr/butterrobot.git
|
||||||
|
cd butterrobot
|
||||||
|
poetry install
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a `.env-local` file with the required environment variables,
|
||||||
|
you have [an example file](.env-example).
|
||||||
|
|
||||||
|
```
|
||||||
|
SLACK_TOKEN=xxx
|
||||||
|
TELEGRAM_TOKEN=xxx
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
And then you can run it directly with poetry
|
||||||
|
|
||||||
|
TODO: Autoload .env-local
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -it --rm --env-file .env-local -p 5000:5000 -v $PWD/butterrobot:/etc/app/butterrobot local/butterrobot python -m butterrobot
|
||||||
|
```
|
BIN
assets/icon.png
Normal file
BIN
assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 131 KiB |
BIN
assets/icon@120.png
Normal file
BIN
assets/icon@120.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
0
butterrobot/__init__.py
Normal file
0
butterrobot/__init__.py
Normal file
6
butterrobot/__main__.py
Normal file
6
butterrobot/__main__.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from butterrobot.app import app
|
||||||
|
from butterrobot.config import DEBUG
|
||||||
|
|
||||||
|
# Only used for local development!
|
||||||
|
# python -m butterrobot
|
||||||
|
app.run(debug=DEBUG, host="0.0.0.0")
|
60
butterrobot/app.py
Normal file
60
butterrobot/app.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import asyncio
|
||||||
|
import traceback
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
from quart import Quart, request
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
import butterrobot.logging
|
||||||
|
from butterrobot.config import SLACK_TOKEN, LOG_LEVEL, ENABLED_PLUGINS
|
||||||
|
from butterrobot.plugins import get_available_plugins
|
||||||
|
from butterrobot.platforms import PLATFORMS
|
||||||
|
from butterrobot.platforms.base import Platform
|
||||||
|
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
app = Quart(__name__)
|
||||||
|
available_platforms = {}
|
||||||
|
plugins = get_available_plugins()
|
||||||
|
enabled_plugins = [plugin for plugin_name, plugin in plugins.items() if plugin in ENABLED_PLUGINS]
|
||||||
|
|
||||||
|
|
||||||
|
@app.before_serving
|
||||||
|
async def init_platforms():
|
||||||
|
for platform in PLATFORMS.values():
|
||||||
|
logger.debug("Setting up", platform=platform.ID)
|
||||||
|
try:
|
||||||
|
await platform.init(app=app)
|
||||||
|
available_platforms[platform.ID] = platform
|
||||||
|
logger.info("platform setup completed", platform=platform.ID)
|
||||||
|
except platform.platformInitError as error:
|
||||||
|
logger.error(f"platform init error", error=error, platform=platform.ID)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/<platform>/incoming", methods=["POST"])
|
||||||
|
@app.route("/<platform>/incoming/<path:path>", methods=["POST"])
|
||||||
|
async def incoming_platform_message_view(platform, path=None):
|
||||||
|
if platform not in available_platforms:
|
||||||
|
return {"error": "Unknown platform"}, 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
message = await available_platforms[platform].parse_incoming_message(request=request)
|
||||||
|
except Platform.PlatformAuthResponse as response:
|
||||||
|
return response.data, response.status_code
|
||||||
|
except Exception as error:
|
||||||
|
logger.error(f"Error parsing message", platform=platform, error=error, traceback=traceback.format_exc())
|
||||||
|
return {"error": str(error)}, 400
|
||||||
|
|
||||||
|
if not message:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
for plugin in enabled_plugins:
|
||||||
|
if result := await plugin.on_message(message):
|
||||||
|
await available_platforms[platform].methods.send_message(result)
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/healthz")
|
||||||
|
def healthz():
|
||||||
|
return {}
|
27
butterrobot/config.py
Normal file
27
butterrobot/config.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
# --- Butter Robot -----------------------------------------------------------------
|
||||||
|
DEBUG = os.environ.get("DEBUG", "n") == "y"
|
||||||
|
|
||||||
|
HOSTNAME = os.environ.get("BUTTERROBOT_HOSTNAME", "butterrobot-dev.int.fmartingr.network")
|
||||||
|
|
||||||
|
LOG_LEVEL = os.environ.get("LOG_LEVEL", "ERROR")
|
||||||
|
|
||||||
|
ENABLED_PLUGINS = os.environ.get("ENABLED_PLUGINS", "contrib/dev/ping").split(",")
|
||||||
|
|
||||||
|
|
||||||
|
# --- PLATFORMS ---------------------------------------------------------------------
|
||||||
|
# ---
|
||||||
|
# Slack
|
||||||
|
# ---
|
||||||
|
# Slack app access token
|
||||||
|
SLACK_TOKEN = os.environ.get("SLACK_TOKEN")
|
||||||
|
|
||||||
|
# Slack app oauth access token to send messages on the bot behalf
|
||||||
|
SLACK_BOT_OAUTH_ACCESS_TOKEN = os.environ.get("SLACK_BOT_OAUTH_ACCESS_TOKEN")
|
||||||
|
|
||||||
|
# ---
|
||||||
|
# Telegram
|
||||||
|
# ---
|
||||||
|
# Telegram auth token
|
||||||
|
TELEGRAM_TOKEN = os.environ.get("TELEGRAM_TOKEN")
|
0
butterrobot/lib/__init__.py
Normal file
0
butterrobot/lib/__init__.py
Normal file
39
butterrobot/lib/slack.py
Normal file
39
butterrobot/lib/slack.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
from typing import Optional, Text
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
from butterrobot.config import SLACK_BOT_OAUTH_ACCESS_TOKEN
|
||||||
|
|
||||||
|
|
||||||
|
logger = structlog.get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class SlackAPI:
|
||||||
|
BASE_URL = "https://slack.com/api"
|
||||||
|
|
||||||
|
class SlackError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SlackClientError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def send_message(cls, platform, message, thread: Optional[Text] = None):
|
||||||
|
payload = {
|
||||||
|
"text": message,
|
||||||
|
"platform": platform,
|
||||||
|
}
|
||||||
|
|
||||||
|
if thread:
|
||||||
|
payload["thread_ts"] = thread
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(
|
||||||
|
f"{cls.BASE_URL}/chat.postMessage",
|
||||||
|
data=payload,
|
||||||
|
headers={"Authorization": f"Bearer {SLACK_BOT_OAUTH_ACCESS_TOKEN}"},
|
||||||
|
) as response:
|
||||||
|
response = await response.json()
|
||||||
|
if not response["ok"]:
|
||||||
|
raise cls.SlackClientError(response)
|
59
butterrobot/lib/telegram.py
Normal file
59
butterrobot/lib/telegram.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import aiohttp
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
from butterrobot.config import TELEGRAM_TOKEN
|
||||||
|
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramAPI:
|
||||||
|
BASE_URL = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}"
|
||||||
|
|
||||||
|
DEFAULT_ALLOWED_UPDATES = ["message"]
|
||||||
|
|
||||||
|
class TelegramError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TelegramClientError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def set_webhook(cls, webhook_url, max_connections=40, allowed_updates=None):
|
||||||
|
allowed_updates = allowed_updates or cls.DEFAULT_ALLOWED_UPDATES
|
||||||
|
url = f"{cls.BASE_URL}/setWebhook"
|
||||||
|
payload = {
|
||||||
|
"url": webhook_url,
|
||||||
|
"max_connections": max_connections,
|
||||||
|
"allowed_updates": allowed_updates,
|
||||||
|
}
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(url, json=payload) as response:
|
||||||
|
response = await response.json()
|
||||||
|
if not response["ok"]:
|
||||||
|
raise cls.TelegramClientError
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def send_message(
|
||||||
|
cls,
|
||||||
|
chat_id,
|
||||||
|
text,
|
||||||
|
parse_mode="markdown",
|
||||||
|
disable_web_page_preview=False,
|
||||||
|
disable_notification=False,
|
||||||
|
reply_to_message_id=None,
|
||||||
|
):
|
||||||
|
url = f"{cls.BASE_URL}/sendMessage"
|
||||||
|
payload = {
|
||||||
|
"chat_id": chat_id,
|
||||||
|
"text": text,
|
||||||
|
"parse_mode": parse_mode,
|
||||||
|
"disable_web_page_preview": disable_web_page_preview,
|
||||||
|
"disable_notification": disable_notification,
|
||||||
|
"reply_to_message_id": reply_to_message_id,
|
||||||
|
}
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.post(url, json=payload) as response:
|
||||||
|
response = await response.json()
|
||||||
|
if not response["ok"]:
|
||||||
|
raise cls.TelegramClientError(response)
|
23
butterrobot/logging.py
Normal file
23
butterrobot/logging.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
from butterrobot.config import LOG_LEVEL, DEBUG
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(format="%(message)s", level=LOG_LEVEL)
|
||||||
|
structlog.configure(
|
||||||
|
processors=[
|
||||||
|
structlog.stdlib.add_log_level,
|
||||||
|
structlog.stdlib.add_logger_name,
|
||||||
|
structlog.dev.set_exc_info,
|
||||||
|
structlog.processors.StackInfoRenderer(),
|
||||||
|
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M.%S"),
|
||||||
|
structlog.processors.format_exc_info,
|
||||||
|
structlog.dev.ConsoleRenderer() if DEBUG else structlog.processors.JSONRenderer(),
|
||||||
|
],
|
||||||
|
context_class=dict,
|
||||||
|
logger_factory=structlog.stdlib.LoggerFactory(),
|
||||||
|
wrapper_class=structlog.BoundLogger,
|
||||||
|
cache_logger_on_first_use=True,
|
||||||
|
)
|
13
butterrobot/objects.py
Normal file
13
butterrobot/objects.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Text, Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Message:
|
||||||
|
text: Text
|
||||||
|
chat: Text
|
||||||
|
date: Optional[datetime] = None
|
||||||
|
id: Optional[Text] = None
|
||||||
|
reply_to: Optional[Text] = None
|
||||||
|
raw: dict = field(default_factory=dict)
|
5
butterrobot/platforms/__init__.py
Normal file
5
butterrobot/platforms/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from butterrobot.platforms.slack import SlackPlatform
|
||||||
|
from butterrobot.platforms.telegram import TelegramPlatform
|
||||||
|
|
||||||
|
|
||||||
|
PLATFORMS = {platform.ID: platform for platform in (SlackPlatform, TelegramPlatform,)}
|
35
butterrobot/platforms/base.py
Normal file
35
butterrobot/platforms/base.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
from abc import abstractclassmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
class Platform:
|
||||||
|
class PlatformError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PlatformInitError(PlatformError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PlatformAuthError(PlatformError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlatformAuthResponse(PlatformError):
|
||||||
|
"""
|
||||||
|
Used when the platform needs to make a response right away instead of async.
|
||||||
|
"""
|
||||||
|
data: dict
|
||||||
|
status_code: int = 200
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def init(cls, app):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformMethods:
|
||||||
|
@abstractclassmethod
|
||||||
|
def send_message(cls, message):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractclassmethod
|
||||||
|
def reply_message(cls, message, reply_to):
|
||||||
|
pass
|
70
butterrobot/platforms/slack.py
Normal file
70
butterrobot/platforms/slack.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
from butterrobot.platforms.base import Platform, PlatformMethods
|
||||||
|
from butterrobot.config import SLACK_TOKEN, SLACK_BOT_OAUTH_ACCESS_TOKEN
|
||||||
|
from butterrobot.objects import Message
|
||||||
|
from butterrobot.lib.slack import SlackAPI
|
||||||
|
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SlackMethods(PlatformMethods):
|
||||||
|
@classmethod
|
||||||
|
async def send_message(self, message: Message):
|
||||||
|
logger.debug(
|
||||||
|
"Outgoing message", message=message.__dict__, platform=SlackPlatform.ID
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
await SlackAPI.send_message(
|
||||||
|
platform=message.chat, message=message.text, thread=message.reply_to
|
||||||
|
)
|
||||||
|
except SlackAPI.SlackClientError as error:
|
||||||
|
logger.error(
|
||||||
|
"Send message error",
|
||||||
|
platform=SlackPlatform.ID,
|
||||||
|
error=error,
|
||||||
|
message=message.__dict__,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SlackPlatform(Platform):
|
||||||
|
ID = "slack"
|
||||||
|
|
||||||
|
methods = SlackMethods
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def init(cls, app):
|
||||||
|
if not (SLACK_TOKEN and SLACK_BOT_OAUTH_ACCESS_TOKEN):
|
||||||
|
logger.error("Missing token. platform not enabled.", platform=cls.ID)
|
||||||
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def parse_incoming_message(cls, request):
|
||||||
|
data = await request.get_json()
|
||||||
|
logger.debug("Parsing message", platform=cls.ID, data=data)
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
if data.get("token") != SLACK_TOKEN:
|
||||||
|
raise cls.PlatformAuthError("Authentication error")
|
||||||
|
|
||||||
|
# Confirms challenge request to configure webhook
|
||||||
|
if "challenge" in data:
|
||||||
|
raise cls.PlatformAuthResponse(data={"challenge": data["challenge"]})
|
||||||
|
|
||||||
|
# Discard messages by bots
|
||||||
|
if "bot_id" in data["event"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
if data["event"]["type"] != "message":
|
||||||
|
return
|
||||||
|
|
||||||
|
return Message(
|
||||||
|
id=data["event"].get("thread_ts", data["event"]["ts"]),
|
||||||
|
date=datetime.fromtimestamp(int(float(data["event"]["event_ts"]))),
|
||||||
|
text=data["event"]["text"],
|
||||||
|
chat=data["event"]["platform"],
|
||||||
|
raw=data,
|
||||||
|
)
|
66
butterrobot/platforms/telegram.py
Normal file
66
butterrobot/platforms/telegram.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
from butterrobot.platforms.base import Platform, PlatformMethods
|
||||||
|
from butterrobot.config import TELEGRAM_TOKEN, HOSTNAME
|
||||||
|
from butterrobot.lib.telegram import TelegramAPI
|
||||||
|
from butterrobot.objects import Message
|
||||||
|
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramMethods(PlatformMethods):
|
||||||
|
@classmethod
|
||||||
|
async def send_message(self, message: Message):
|
||||||
|
logger.debug(
|
||||||
|
"Outgoing message", message=message.__dict__, platform=TelegramPlatform.ID
|
||||||
|
)
|
||||||
|
await TelegramAPI.send_message(
|
||||||
|
chat_id=message.chat,
|
||||||
|
text=message.text,
|
||||||
|
reply_to_message_id=message.reply_to,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramPlatform(Platform):
|
||||||
|
ID = "telegram"
|
||||||
|
|
||||||
|
methods = TelegramMethods
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def init(cls, app):
|
||||||
|
"""
|
||||||
|
Initializes the Telegram webhook endpoint to receive updates
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not TELEGRAM_TOKEN:
|
||||||
|
logger.error("Missing token. platform not enabled.", platform=cls.ID)
|
||||||
|
return
|
||||||
|
|
||||||
|
webhook_url = f"https://{HOSTNAME}/telegram/incoming/{TELEGRAM_TOKEN}"
|
||||||
|
try:
|
||||||
|
await TelegramAPI.set_webhook(webhook_url)
|
||||||
|
except TelegramAPI.TelegramError as error:
|
||||||
|
logger.error(f"Error setting Telegram webhook: {error}", platform=cls.ID)
|
||||||
|
raise Platform.PlatformInitError()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def parse_incoming_message(cls, request):
|
||||||
|
token = request.path.split("/")[-1]
|
||||||
|
if token != TELEGRAM_TOKEN:
|
||||||
|
raise cls.PlatformAuthError("Authentication error")
|
||||||
|
|
||||||
|
request_data = await request.get_json()
|
||||||
|
logger.debug("Parsing message", data=request_data, platform=cls.ID)
|
||||||
|
|
||||||
|
if "text" in request_data["message"]:
|
||||||
|
# Ignore all messages but text messages
|
||||||
|
return Message(
|
||||||
|
id=request_data["message"]["message_id"],
|
||||||
|
date=datetime.fromtimestamp(request_data["message"]["date"]),
|
||||||
|
text=str(request_data["message"]["text"]),
|
||||||
|
chat=str(request_data["message"]["chat"]["id"]),
|
||||||
|
raw=request_data,
|
||||||
|
)
|
37
butterrobot/plugins.py
Normal file
37
butterrobot/plugins.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import traceback
|
||||||
|
import pkg_resources
|
||||||
|
from abc import abstractclassmethod
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin:
|
||||||
|
@abstractclassmethod
|
||||||
|
def on_message(cls, message):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_plugins():
|
||||||
|
"""Retrieves every available plugin"""
|
||||||
|
plugins = {}
|
||||||
|
logger.debug("Loading plugins")
|
||||||
|
for ep in pkg_resources.iter_entry_points("butterrobot.plugins"):
|
||||||
|
try:
|
||||||
|
plugin_cls = ep.load()
|
||||||
|
plugins[plugin_cls.id] = plugin_cls
|
||||||
|
except Exception as error:
|
||||||
|
logger.error(
|
||||||
|
"Error loading plugin",
|
||||||
|
exception=str(error),
|
||||||
|
traceback=traceback.format_exc(),
|
||||||
|
plugin=ep.name,
|
||||||
|
project_name=ep.dist.project_name,
|
||||||
|
entry_point=ep,
|
||||||
|
module=ep.module_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Plugins loaded", plugins=list(plugins.keys()))
|
||||||
|
return plugins
|
0
butterrobot_plugins_contrib/__init__.py
Normal file
0
butterrobot_plugins_contrib/__init__.py
Normal file
17
butterrobot_plugins_contrib/dev.py
Normal file
17
butterrobot_plugins_contrib/dev.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from butterrobot.plugins import Plugin
|
||||||
|
from butterrobot.objects import Message
|
||||||
|
|
||||||
|
|
||||||
|
class PingPlugin(Plugin):
|
||||||
|
id = "contrib/dev/ping"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def on_message(cls, message):
|
||||||
|
if message.text == "!ping":
|
||||||
|
delta = datetime.now() - message.date
|
||||||
|
delta_ms = delta.seconds * 1000 + delta.microseconds / 1000
|
||||||
|
return Message(
|
||||||
|
chat=message.chat, reply_to=message.id, text=f"pong! ({delta_ms}ms)",
|
||||||
|
)
|
11
butterrobot_plugins_contrib/fun.py
Normal file
11
butterrobot_plugins_contrib/fun.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from butterrobot.plugins import Plugin
|
||||||
|
from butterrobot.objects import Message
|
||||||
|
|
||||||
|
|
||||||
|
class LoquitoPlugin(Plugin):
|
||||||
|
id = "contrib/fun/loquito"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def on_message(cls, message):
|
||||||
|
if "lo quito" in message.text.lower():
|
||||||
|
return Message(chat=message.chat, reply_to=message.id, text="Loquito tu.",)
|
14
docker/Dockerfile
Normal file
14
docker/Dockerfile
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
FROM alpine:3.11
|
||||||
|
|
||||||
|
ENV PYTHON_VERSION=3.8.2-r0
|
||||||
|
ENV APP_PORT 8080
|
||||||
|
ENV BUTTERROBOT_VERSION 0.0.2
|
||||||
|
ENV EXTRA_DEPENDENCIES ""
|
||||||
|
|
||||||
|
COPY bin/start-server.sh /usr/local/bin/start-server
|
||||||
|
RUN apk --update add python3-dev==${PYTHON_VERSION} gcc musl-dev libffi-dev openssl-dev && \
|
||||||
|
pip3 install butterrobot==${BUTTERROBOT_VERSION} ${EXTRA_DEPENDENCIES}
|
||||||
|
|
||||||
|
USER 1000
|
||||||
|
|
||||||
|
CMD ["/usr/local/bin/start-server"]
|
3
docker/bin/start-server.sh
Executable file
3
docker/bin/start-server.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh -xe
|
||||||
|
|
||||||
|
hypercorn butterrobot.app -b "0.0.0.0:${APP_PORT}"
|
932
poetry.lock
generated
Normal file
932
poetry.lock
generated
Normal file
|
@ -0,0 +1,932 @@
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "File support for asyncio."
|
||||||
|
name = "aiofiles"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.5.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Async http client/server framework (asyncio)"
|
||||||
|
name = "aiohttp"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5.3"
|
||||||
|
version = "3.6.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
async-timeout = ">=3.0,<4.0"
|
||||||
|
attrs = ">=17.3.0"
|
||||||
|
chardet = ">=2.0,<4.0"
|
||||||
|
multidict = ">=4.5,<5.0"
|
||||||
|
yarl = ">=1.0,<2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
speedups = ["aiodns", "brotlipy", "cchardet"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||||
|
name = "appdirs"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "1.4.3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Disable App Nap on OS X 10.9"
|
||||||
|
marker = "python_version >= \"3.4\" and sys_platform == \"darwin\""
|
||||||
|
name = "appnope"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Timeout context manager for asyncio programs"
|
||||||
|
name = "async-timeout"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5.3"
|
||||||
|
version = "3.0.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Classes Without Boilerplate"
|
||||||
|
name = "attrs"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "19.3.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
|
||||||
|
dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
|
||||||
|
docs = ["sphinx", "zope.interface"]
|
||||||
|
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Specifications for callback functions passed in to an API"
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "backcall"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "The uncompromising code formatter."
|
||||||
|
name = "black"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
version = "19.10b0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
appdirs = "*"
|
||||||
|
attrs = ">=18.1.0"
|
||||||
|
click = ">=6.5"
|
||||||
|
pathspec = ">=0.6,<1"
|
||||||
|
regex = "*"
|
||||||
|
toml = ">=0.9.4"
|
||||||
|
typed-ast = ">=1.4.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Fast, simple object-to-object and broadcast signaling"
|
||||||
|
name = "blinker"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "1.4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Universal encoding detector for Python 2 and 3"
|
||||||
|
name = "chardet"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "3.0.4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Composable command line interface toolkit"
|
||||||
|
name = "click"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
version = "7.1.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
name = "colorama"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
version = "0.4.3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Decorators for Humans"
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "decorator"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
|
||||||
|
version = "4.4.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Discover and load entry points from installed packages."
|
||||||
|
name = "entrypoints"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7"
|
||||||
|
version = "0.3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "the modular source code checker: pep8, pyflakes and co"
|
||||||
|
name = "flake8"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "3.7.9"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
entrypoints = ">=0.3.0,<0.4.0"
|
||||||
|
mccabe = ">=0.6.0,<0.7.0"
|
||||||
|
pycodestyle = ">=2.5.0,<2.6.0"
|
||||||
|
pyflakes = ">=2.1.0,<2.2.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||||
|
name = "h11"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.9.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "HTTP/2 State-Machine based protocol implementation"
|
||||||
|
name = "h2"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "3.2.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
hpack = ">=3.0,<4"
|
||||||
|
hyperframe = ">=5.2.0,<6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Pure-Python HPACK header compression"
|
||||||
|
name = "hpack"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "3.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn."
|
||||||
|
name = "hypercorn"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
version = "0.9.5"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
h11 = "*"
|
||||||
|
h2 = ">=3.1.0"
|
||||||
|
priority = "*"
|
||||||
|
toml = "*"
|
||||||
|
typing-extensions = "*"
|
||||||
|
wsproto = ">=0.14.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
h3 = ["aioquic (>=0.8.1,<1.0)"]
|
||||||
|
tests = ["asynctest", "hypothesis", "pytest", "pytest-asyncio", "pytest-cov", "pytest-trio", "trio"]
|
||||||
|
trio = ["trio (>=0.11.0)"]
|
||||||
|
uvloop = ["uvloop"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "HTTP/2 framing layer for Python"
|
||||||
|
name = "hyperframe"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "5.2.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
|
name = "idna"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "2.9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "IPython-enabled pdb"
|
||||||
|
name = "ipdb"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7"
|
||||||
|
version = "0.13.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
setuptools = "*"
|
||||||
|
|
||||||
|
[package.dependencies.ipython]
|
||||||
|
python = ">=3.4"
|
||||||
|
version = ">=5.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "IPython: Productive Interactive Computing"
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "ipython"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
version = "7.13.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
appnope = "*"
|
||||||
|
backcall = "*"
|
||||||
|
colorama = "*"
|
||||||
|
decorator = "*"
|
||||||
|
jedi = ">=0.10"
|
||||||
|
pexpect = "*"
|
||||||
|
pickleshare = "*"
|
||||||
|
prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0"
|
||||||
|
pygments = "*"
|
||||||
|
setuptools = ">=18.5"
|
||||||
|
traitlets = ">=4.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["numpy (>=1.14)", "testpath", "notebook", "nose (>=0.10.1)", "nbconvert", "requests", "ipywidgets", "qtconsole", "ipyparallel", "Sphinx (>=1.3)", "pygments", "nbformat", "ipykernel"]
|
||||||
|
doc = ["Sphinx (>=1.3)"]
|
||||||
|
kernel = ["ipykernel"]
|
||||||
|
nbconvert = ["nbconvert"]
|
||||||
|
nbformat = ["nbformat"]
|
||||||
|
notebook = ["notebook", "ipywidgets"]
|
||||||
|
parallel = ["ipyparallel"]
|
||||||
|
qtconsole = ["qtconsole"]
|
||||||
|
test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Vestigial utilities from IPython"
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "ipython-genutils"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.2.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "A Python utility / library to sort Python imports."
|
||||||
|
name = "isort"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "4.3.21"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
pipfile = ["pipreqs", "requirementslib"]
|
||||||
|
pyproject = ["toml"]
|
||||||
|
requirements = ["pipreqs", "pip-api"]
|
||||||
|
xdg_home = ["appdirs (>=1.4.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Various helpers to pass data to untrusted environments and back."
|
||||||
|
name = "itsdangerous"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "1.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "An autocompletion tool for Python that can be used for text editors."
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "jedi"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
version = "0.17.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
parso = ">=0.7.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
qa = ["flake8 (3.7.9)"]
|
||||||
|
testing = ["colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A very fast and expressive template engine."
|
||||||
|
name = "jinja2"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
version = "2.11.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
MarkupSafe = ">=0.23"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
i18n = ["Babel (>=0.8)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Safely add untrusted strings to HTML/XML markup."
|
||||||
|
name = "markupsafe"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||||
|
version = "1.1.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "McCabe checker, plugin for flake8"
|
||||||
|
name = "mccabe"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.6.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "multidict implementation"
|
||||||
|
name = "multidict"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "4.7.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "A Python Parser"
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "parso"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "0.7.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["docopt", "pytest (>=3.0.7)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
|
name = "pathspec"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
version = "0.8.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Pexpect allows easy control of interactive console applications."
|
||||||
|
marker = "python_version >= \"3.4\" and sys_platform != \"win32\""
|
||||||
|
name = "pexpect"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "4.8.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
ptyprocess = ">=0.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Tiny 'shelve'-like database with concurrency support"
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "pickleshare"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.7.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A pure-Python implementation of the HTTP/2 priority tree"
|
||||||
|
name = "priority"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "1.3.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Library for building powerful interactive command lines in Python"
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "prompt-toolkit"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.1"
|
||||||
|
version = "3.0.5"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
wcwidth = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Run a subprocess in a pseudo terminal"
|
||||||
|
marker = "python_version >= \"3.4\" and sys_platform != \"win32\""
|
||||||
|
name = "ptyprocess"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.6.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Python style guide checker"
|
||||||
|
name = "pycodestyle"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "2.5.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "passive checker of Python programs"
|
||||||
|
name = "pyflakes"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
version = "2.1.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "pygments"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "2.6.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "A Python ASGI web microframework with the same API as Flask"
|
||||||
|
name = "quart"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7.0"
|
||||||
|
version = "0.11.5"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
aiofiles = "*"
|
||||||
|
blinker = "*"
|
||||||
|
click = "*"
|
||||||
|
hypercorn = ">=0.7.0"
|
||||||
|
itsdangerous = "*"
|
||||||
|
jinja2 = "*"
|
||||||
|
toml = "*"
|
||||||
|
werkzeug = ">=1.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dotenv = ["python-dotenv"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Alternative regular expression module, to replace re."
|
||||||
|
name = "regex"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "2020.4.4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "a python refactoring library..."
|
||||||
|
name = "rope"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.16.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pytest"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
|
name = "six"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
version = "1.14.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Structured Logging for Python"
|
||||||
|
name = "structlog"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "20.1.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
azure-pipelines = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest (>=3.3.0)", "simplejson", "pytest-azurepipelines", "python-rapidjson", "pytest-asyncio"]
|
||||||
|
dev = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest (>=3.3.0)", "simplejson", "sphinx", "twisted", "pre-commit", "python-rapidjson", "pytest-asyncio"]
|
||||||
|
docs = ["sphinx", "twisted"]
|
||||||
|
tests = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest (>=3.3.0)", "simplejson", "python-rapidjson", "pytest-asyncio"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||||
|
name = "toml"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.10.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Traitlets Python config system"
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "traitlets"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "4.3.3"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
decorator = "*"
|
||||||
|
ipython-genutils = "*"
|
||||||
|
six = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["pytest", "mock"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||||
|
name = "typed-ast"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "1.4.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Backported and Experimental Type Hints for Python 3.5+"
|
||||||
|
name = "typing-extensions"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "3.7.4.2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "dev"
|
||||||
|
description = "Measures number of Terminal column cells of wide-character codes"
|
||||||
|
marker = "python_version >= \"3.4\""
|
||||||
|
name = "wcwidth"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
version = "0.1.9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "The comprehensive WSGI web application library."
|
||||||
|
name = "werkzeug"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
version = "1.0.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
|
||||||
|
watchdog = ["watchdog"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "WebSockets state-machine based protocol implementation"
|
||||||
|
name = "wsproto"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.1"
|
||||||
|
version = "0.15.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
h11 = ">=0.8.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
category = "main"
|
||||||
|
description = "Yet another URL library"
|
||||||
|
name = "yarl"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
version = "1.4.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
idna = ">=2.0"
|
||||||
|
multidict = ">=4.0"
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
content-hash = "ceba58ecf5ec2b4dd95cc58ca7122659610ad89052f420d2e9f22223aff38dd4"
|
||||||
|
python-versions = "^3.7"
|
||||||
|
|
||||||
|
[metadata.files]
|
||||||
|
aiofiles = [
|
||||||
|
{file = "aiofiles-0.5.0-py3-none-any.whl", hash = "sha256:377fdf7815cc611870c59cbd07b68b180841d2a2b79812d8c218be02448c2acb"},
|
||||||
|
{file = "aiofiles-0.5.0.tar.gz", hash = "sha256:98e6bcfd1b50f97db4980e182ddd509b7cc35909e903a8fe50d8849e02d815af"},
|
||||||
|
]
|
||||||
|
aiohttp = [
|
||||||
|
{file = "aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e"},
|
||||||
|
{file = "aiohttp-3.6.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec"},
|
||||||
|
{file = "aiohttp-3.6.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48"},
|
||||||
|
{file = "aiohttp-3.6.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59"},
|
||||||
|
{file = "aiohttp-3.6.2-cp36-cp36m-win32.whl", hash = "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a"},
|
||||||
|
{file = "aiohttp-3.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17"},
|
||||||
|
{file = "aiohttp-3.6.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a"},
|
||||||
|
{file = "aiohttp-3.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd"},
|
||||||
|
{file = "aiohttp-3.6.2-cp37-cp37m-win32.whl", hash = "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"},
|
||||||
|
{file = "aiohttp-3.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654"},
|
||||||
|
{file = "aiohttp-3.6.2-py3-none-any.whl", hash = "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4"},
|
||||||
|
{file = "aiohttp-3.6.2.tar.gz", hash = "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326"},
|
||||||
|
]
|
||||||
|
appdirs = [
|
||||||
|
{file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
|
||||||
|
{file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
|
||||||
|
]
|
||||||
|
appnope = [
|
||||||
|
{file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"},
|
||||||
|
{file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"},
|
||||||
|
]
|
||||||
|
async-timeout = [
|
||||||
|
{file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"},
|
||||||
|
{file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"},
|
||||||
|
]
|
||||||
|
attrs = [
|
||||||
|
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
|
||||||
|
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
|
||||||
|
]
|
||||||
|
backcall = [
|
||||||
|
{file = "backcall-0.1.0.tar.gz", hash = "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4"},
|
||||||
|
{file = "backcall-0.1.0.zip", hash = "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"},
|
||||||
|
]
|
||||||
|
black = [
|
||||||
|
{file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
|
||||||
|
{file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
|
||||||
|
]
|
||||||
|
blinker = [
|
||||||
|
{file = "blinker-1.4.tar.gz", hash = "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"},
|
||||||
|
]
|
||||||
|
chardet = [
|
||||||
|
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
|
||||||
|
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
|
||||||
|
]
|
||||||
|
click = [
|
||||||
|
{file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"},
|
||||||
|
{file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"},
|
||||||
|
]
|
||||||
|
colorama = [
|
||||||
|
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
|
||||||
|
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
||||||
|
]
|
||||||
|
decorator = [
|
||||||
|
{file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"},
|
||||||
|
{file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"},
|
||||||
|
]
|
||||||
|
entrypoints = [
|
||||||
|
{file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"},
|
||||||
|
{file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"},
|
||||||
|
]
|
||||||
|
flake8 = [
|
||||||
|
{file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"},
|
||||||
|
{file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"},
|
||||||
|
]
|
||||||
|
h11 = [
|
||||||
|
{file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"},
|
||||||
|
{file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"},
|
||||||
|
]
|
||||||
|
h2 = [
|
||||||
|
{file = "h2-3.2.0-py2.py3-none-any.whl", hash = "sha256:61e0f6601fa709f35cdb730863b4e5ec7ad449792add80d1410d4174ed139af5"},
|
||||||
|
{file = "h2-3.2.0.tar.gz", hash = "sha256:875f41ebd6f2c44781259005b157faed1a5031df3ae5aa7bcb4628a6c0782f14"},
|
||||||
|
]
|
||||||
|
hpack = [
|
||||||
|
{file = "hpack-3.0.0-py2.py3-none-any.whl", hash = "sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89"},
|
||||||
|
{file = "hpack-3.0.0.tar.gz", hash = "sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"},
|
||||||
|
]
|
||||||
|
hypercorn = [
|
||||||
|
{file = "Hypercorn-0.9.5-py3-none-any.whl", hash = "sha256:c53eb444d05e40ac1aacecaa6d3a8fabada90bbea8aacf617e75d41ac065c310"},
|
||||||
|
{file = "Hypercorn-0.9.5.tar.gz", hash = "sha256:d94fa535e238ce1cd9c9b5f4cb77cb785d53069a5dc57a017e7c2fc51104ad5e"},
|
||||||
|
]
|
||||||
|
hyperframe = [
|
||||||
|
{file = "hyperframe-5.2.0-py2.py3-none-any.whl", hash = "sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40"},
|
||||||
|
{file = "hyperframe-5.2.0.tar.gz", hash = "sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"},
|
||||||
|
]
|
||||||
|
idna = [
|
||||||
|
{file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"},
|
||||||
|
{file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
|
||||||
|
]
|
||||||
|
ipdb = [
|
||||||
|
{file = "ipdb-0.13.2.tar.gz", hash = "sha256:77fb1c2a6fccdfee0136078c9ed6fe547ab00db00bebff181f1e8c9e13418d49"},
|
||||||
|
]
|
||||||
|
ipython = [
|
||||||
|
{file = "ipython-7.13.0-py3-none-any.whl", hash = "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333"},
|
||||||
|
{file = "ipython-7.13.0.tar.gz", hash = "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a"},
|
||||||
|
]
|
||||||
|
ipython-genutils = [
|
||||||
|
{file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"},
|
||||||
|
{file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"},
|
||||||
|
]
|
||||||
|
isort = [
|
||||||
|
{file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
|
||||||
|
{file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
|
||||||
|
]
|
||||||
|
itsdangerous = [
|
||||||
|
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
|
||||||
|
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
|
||||||
|
]
|
||||||
|
jedi = [
|
||||||
|
{file = "jedi-0.17.0-py2.py3-none-any.whl", hash = "sha256:cd60c93b71944d628ccac47df9a60fec53150de53d42dc10a7fc4b5ba6aae798"},
|
||||||
|
{file = "jedi-0.17.0.tar.gz", hash = "sha256:df40c97641cb943661d2db4c33c2e1ff75d491189423249e989bcea4464f3030"},
|
||||||
|
]
|
||||||
|
jinja2 = [
|
||||||
|
{file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
|
||||||
|
{file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
|
||||||
|
]
|
||||||
|
markupsafe = [
|
||||||
|
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
|
||||||
|
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
|
||||||
|
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
|
||||||
|
]
|
||||||
|
mccabe = [
|
||||||
|
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||||
|
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||||
|
]
|
||||||
|
multidict = [
|
||||||
|
{file = "multidict-4.7.5-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:fc3b4adc2ee8474cb3cd2a155305d5f8eda0a9c91320f83e55748e1fcb68f8e3"},
|
||||||
|
{file = "multidict-4.7.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:42f56542166040b4474c0c608ed051732033cd821126493cf25b6c276df7dd35"},
|
||||||
|
{file = "multidict-4.7.5-cp35-cp35m-win32.whl", hash = "sha256:7774e9f6c9af3f12f296131453f7b81dabb7ebdb948483362f5afcaac8a826f1"},
|
||||||
|
{file = "multidict-4.7.5-cp35-cp35m-win_amd64.whl", hash = "sha256:c2c37185fb0af79d5c117b8d2764f4321eeb12ba8c141a95d0aa8c2c1d0a11dd"},
|
||||||
|
{file = "multidict-4.7.5-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:e439c9a10a95cb32abd708bb8be83b2134fa93790a4fb0535ca36db3dda94d20"},
|
||||||
|
{file = "multidict-4.7.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:85cb26c38c96f76b7ff38b86c9d560dea10cf3459bb5f4caf72fc1bb932c7136"},
|
||||||
|
{file = "multidict-4.7.5-cp36-cp36m-win32.whl", hash = "sha256:620b37c3fea181dab09267cd5a84b0f23fa043beb8bc50d8474dd9694de1fa6e"},
|
||||||
|
{file = "multidict-4.7.5-cp36-cp36m-win_amd64.whl", hash = "sha256:6e6fef114741c4d7ca46da8449038ec8b1e880bbe68674c01ceeb1ac8a648e78"},
|
||||||
|
{file = "multidict-4.7.5-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:a326f4240123a2ac66bb163eeba99578e9d63a8654a59f4688a79198f9aa10f8"},
|
||||||
|
{file = "multidict-4.7.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dc561313279f9d05a3d0ffa89cd15ae477528ea37aa9795c4654588a3287a9ab"},
|
||||||
|
{file = "multidict-4.7.5-cp37-cp37m-win32.whl", hash = "sha256:4b7df040fb5fe826d689204f9b544af469593fb3ff3a069a6ad3409f742f5928"},
|
||||||
|
{file = "multidict-4.7.5-cp37-cp37m-win_amd64.whl", hash = "sha256:317f96bc0950d249e96d8d29ab556d01dd38888fbe68324f46fd834b430169f1"},
|
||||||
|
{file = "multidict-4.7.5-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:b51249fdd2923739cd3efc95a3d6c363b67bbf779208e9f37fd5e68540d1a4d4"},
|
||||||
|
{file = "multidict-4.7.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ae402f43604e3b2bc41e8ea8b8526c7fa7139ed76b0d64fc48e28125925275b2"},
|
||||||
|
{file = "multidict-4.7.5-cp38-cp38-win32.whl", hash = "sha256:bb519becc46275c594410c6c28a8a0adc66fe24fef154a9addea54c1adb006f5"},
|
||||||
|
{file = "multidict-4.7.5-cp38-cp38-win_amd64.whl", hash = "sha256:544fae9261232a97102e27a926019100a9db75bec7b37feedd74b3aa82f29969"},
|
||||||
|
{file = "multidict-4.7.5.tar.gz", hash = "sha256:aee283c49601fa4c13adc64c09c978838a7e812f85377ae130a24d7198c0331e"},
|
||||||
|
]
|
||||||
|
parso = [
|
||||||
|
{file = "parso-0.7.0-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"},
|
||||||
|
{file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"},
|
||||||
|
]
|
||||||
|
pathspec = [
|
||||||
|
{file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
|
||||||
|
{file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"},
|
||||||
|
]
|
||||||
|
pexpect = [
|
||||||
|
{file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
|
||||||
|
{file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
|
||||||
|
]
|
||||||
|
pickleshare = [
|
||||||
|
{file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
|
||||||
|
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
|
||||||
|
]
|
||||||
|
priority = [
|
||||||
|
{file = "priority-1.3.0-py2.py3-none-any.whl", hash = "sha256:be4fcb94b5e37cdeb40af5533afe6dd603bd665fe9c8b3052610fc1001d5d1eb"},
|
||||||
|
{file = "priority-1.3.0.tar.gz", hash = "sha256:6bc1961a6d7fcacbfc337769f1a382c8e746566aaa365e78047abe9f66b2ffbe"},
|
||||||
|
]
|
||||||
|
prompt-toolkit = [
|
||||||
|
{file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"},
|
||||||
|
{file = "prompt_toolkit-3.0.5.tar.gz", hash = "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8"},
|
||||||
|
]
|
||||||
|
ptyprocess = [
|
||||||
|
{file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"},
|
||||||
|
{file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"},
|
||||||
|
]
|
||||||
|
pycodestyle = [
|
||||||
|
{file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"},
|
||||||
|
{file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"},
|
||||||
|
]
|
||||||
|
pyflakes = [
|
||||||
|
{file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"},
|
||||||
|
{file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"},
|
||||||
|
]
|
||||||
|
pygments = [
|
||||||
|
{file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"},
|
||||||
|
{file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"},
|
||||||
|
]
|
||||||
|
quart = [
|
||||||
|
{file = "Quart-0.11.5-py3-none-any.whl", hash = "sha256:187427d1a2d7fed20dcb825dddbe20fd971efd7ec413639f95d2e28ff59a0cb1"},
|
||||||
|
{file = "Quart-0.11.5.tar.gz", hash = "sha256:bd93650fa856dcfbc3890952ab3ca53f7755ab506d453a209db63713eceeceda"},
|
||||||
|
]
|
||||||
|
regex = [
|
||||||
|
{file = "regex-2020.4.4-cp27-cp27m-win32.whl", hash = "sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f"},
|
||||||
|
{file = "regex-2020.4.4-cp27-cp27m-win_amd64.whl", hash = "sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1"},
|
||||||
|
{file = "regex-2020.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b"},
|
||||||
|
{file = "regex-2020.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db"},
|
||||||
|
{file = "regex-2020.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156"},
|
||||||
|
{file = "regex-2020.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3"},
|
||||||
|
{file = "regex-2020.4.4-cp36-cp36m-win32.whl", hash = "sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8"},
|
||||||
|
{file = "regex-2020.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a"},
|
||||||
|
{file = "regex-2020.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468"},
|
||||||
|
{file = "regex-2020.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6"},
|
||||||
|
{file = "regex-2020.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd"},
|
||||||
|
{file = "regex-2020.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948"},
|
||||||
|
{file = "regex-2020.4.4-cp37-cp37m-win32.whl", hash = "sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e"},
|
||||||
|
{file = "regex-2020.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a"},
|
||||||
|
{file = "regex-2020.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e"},
|
||||||
|
{file = "regex-2020.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683"},
|
||||||
|
{file = "regex-2020.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b"},
|
||||||
|
{file = "regex-2020.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89"},
|
||||||
|
{file = "regex-2020.4.4-cp38-cp38-win32.whl", hash = "sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3"},
|
||||||
|
{file = "regex-2020.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3"},
|
||||||
|
{file = "regex-2020.4.4.tar.gz", hash = "sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142"},
|
||||||
|
]
|
||||||
|
rope = [
|
||||||
|
{file = "rope-0.16.0-py2-none-any.whl", hash = "sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad"},
|
||||||
|
{file = "rope-0.16.0-py3-none-any.whl", hash = "sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203"},
|
||||||
|
{file = "rope-0.16.0.tar.gz", hash = "sha256:d2830142c2e046f5fc26a022fe680675b6f48f81c7fc1f03a950706e746e9dfe"},
|
||||||
|
]
|
||||||
|
six = [
|
||||||
|
{file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
|
||||||
|
{file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
|
||||||
|
]
|
||||||
|
structlog = [
|
||||||
|
{file = "structlog-20.1.0-py2.py3-none-any.whl", hash = "sha256:8a672be150547a93d90a7d74229a29e765be05bd156a35cdcc527ebf68e9af92"},
|
||||||
|
{file = "structlog-20.1.0.tar.gz", hash = "sha256:7a48375db6274ed1d0ae6123c486472aa1d0890b08d314d2b016f3aa7f35990b"},
|
||||||
|
]
|
||||||
|
toml = [
|
||||||
|
{file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"},
|
||||||
|
{file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"},
|
||||||
|
{file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
|
||||||
|
]
|
||||||
|
traitlets = [
|
||||||
|
{file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"},
|
||||||
|
{file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"},
|
||||||
|
]
|
||||||
|
typed-ast = [
|
||||||
|
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
|
||||||
|
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
|
||||||
|
{file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
|
||||||
|
{file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
|
||||||
|
{file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
|
||||||
|
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
|
||||||
|
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
|
||||||
|
{file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
|
||||||
|
{file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
|
||||||
|
{file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
|
||||||
|
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
|
||||||
|
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
|
||||||
|
{file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
|
||||||
|
{file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
|
||||||
|
{file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
|
||||||
|
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
|
||||||
|
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
|
||||||
|
{file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
|
||||||
|
{file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
|
||||||
|
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
|
||||||
|
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
|
||||||
|
]
|
||||||
|
typing-extensions = [
|
||||||
|
{file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"},
|
||||||
|
{file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"},
|
||||||
|
{file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"},
|
||||||
|
]
|
||||||
|
wcwidth = [
|
||||||
|
{file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"},
|
||||||
|
{file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"},
|
||||||
|
]
|
||||||
|
werkzeug = [
|
||||||
|
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
|
||||||
|
{file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"},
|
||||||
|
]
|
||||||
|
wsproto = [
|
||||||
|
{file = "wsproto-0.15.0-py2.py3-none-any.whl", hash = "sha256:e3d190a11d9307112ba23bbe60055604949b172143969c8f641318476a9b6f1d"},
|
||||||
|
{file = "wsproto-0.15.0.tar.gz", hash = "sha256:614798c30e5dc2b3f65acc03d2d50842b97621487350ce79a80a711229edfa9d"},
|
||||||
|
]
|
||||||
|
yarl = [
|
||||||
|
{file = "yarl-1.4.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b"},
|
||||||
|
{file = "yarl-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1"},
|
||||||
|
{file = "yarl-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080"},
|
||||||
|
{file = "yarl-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a"},
|
||||||
|
{file = "yarl-1.4.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f"},
|
||||||
|
{file = "yarl-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea"},
|
||||||
|
{file = "yarl-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb"},
|
||||||
|
{file = "yarl-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70"},
|
||||||
|
{file = "yarl-1.4.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d"},
|
||||||
|
{file = "yarl-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce"},
|
||||||
|
{file = "yarl-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"},
|
||||||
|
{file = "yarl-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce"},
|
||||||
|
{file = "yarl-1.4.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b"},
|
||||||
|
{file = "yarl-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae"},
|
||||||
|
{file = "yarl-1.4.2-cp38-cp38-win32.whl", hash = "sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462"},
|
||||||
|
{file = "yarl-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6"},
|
||||||
|
{file = "yarl-1.4.2.tar.gz", hash = "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b"},
|
||||||
|
]
|
35
pyproject.toml
Normal file
35
pyproject.toml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "butterrobot"
|
||||||
|
version = "0.0.2"
|
||||||
|
description = "What is my purpose?"
|
||||||
|
authors = ["Felipe Martin <me@fmartingr.com>"]
|
||||||
|
license = "GPL-2.0"
|
||||||
|
packages = [
|
||||||
|
{ include = "butterrobot" },
|
||||||
|
{ include = "butterrobot_plugins_contrib" },
|
||||||
|
]
|
||||||
|
include = ["README.md"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.7"
|
||||||
|
quart = "^0.11.3"
|
||||||
|
aiohttp = "^3.6.2"
|
||||||
|
structlog = "^20.1.0"
|
||||||
|
colorama = "^0.4.3"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
black = "^19.10b0"
|
||||||
|
flake8 = "^3.7.9"
|
||||||
|
rope = "^0.16.0"
|
||||||
|
isort = "^4.3.21"
|
||||||
|
ipdb = "^0.13.2"
|
||||||
|
|
||||||
|
[tool.poetry.plugins]
|
||||||
|
[tool.poetry.plugins."butterrobot.plugins"]
|
||||||
|
"fun.loquito" = "butterrobot_plugins_contrib.fun:LoquitoPlugin"
|
||||||
|
"dev.ping" = "butterrobot_plugins_contrib.dev:PingPlugin"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry>=0.12"]
|
||||||
|
build-backend = "poetry.masonry.api"
|
16
setup.cfg
Normal file
16
setup.cfg
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[flake8]
|
||||||
|
ignore = E203, E266, E501, W503, F403
|
||||||
|
max-line-length = 88
|
||||||
|
max-complexity = 18
|
||||||
|
select = B,C,E,F,W,T4,B9
|
||||||
|
|
||||||
|
[isort]
|
||||||
|
use_parentheses = True
|
||||||
|
multi_line_output = 3
|
||||||
|
include_trailing_comma = True
|
||||||
|
length_sort = 1
|
||||||
|
lines_between_types = 0
|
||||||
|
line_length = 88
|
||||||
|
known_third_party = click,django,docker,factory,pydantic,pytest,requests,toml
|
||||||
|
sections = FUTURE, STDLIB, DJANGO, THIRDPARTY, FIRSTPARTY, LOCALFOLDER
|
||||||
|
no_lines_before = LOCALFOLDER
|
Loading…
Add table
Add a link
Reference in a new issue