First pass at implemenation with pagerduty webhooks
This commit is contained in:
parent
b71b8d95ff
commit
80c6fbd7bb
15 changed files with 1419 additions and 0 deletions
4
.flake8
Normal file
4
.flake8
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[flake8]
|
||||||
|
min_python_version = 3.10.0
|
||||||
|
extend-ignore = E501,E203
|
||||||
|
ban-relative-imports = true
|
||||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
__pycache__
|
||||||
|
*.egg-info
|
||||||
|
.*_cache
|
||||||
|
.venv
|
||||||
|
devstate
|
||||||
|
.env*
|
||||||
37
.pre-commit-config.yaml
Normal file
37
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
fail_fast: true
|
||||||
|
repos:
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: system
|
||||||
|
name: requirements.txt
|
||||||
|
entry: poetry export --format=requirements.txt --without-hashes --dev --output=requirements.txt
|
||||||
|
pass_filenames: false
|
||||||
|
language: system
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: system
|
||||||
|
name: black
|
||||||
|
entry: poetry run black .
|
||||||
|
pass_filenames: false
|
||||||
|
language: system
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: system
|
||||||
|
name: isort
|
||||||
|
entry: poetry run isort --profile black .
|
||||||
|
pass_filenames: false
|
||||||
|
language: system
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: system
|
||||||
|
name: mypy
|
||||||
|
entry: poetry run mypy
|
||||||
|
pass_filenames: false
|
||||||
|
language: system
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: system
|
||||||
|
name: flake8
|
||||||
|
entry: poetry run flake8 ops_bot
|
||||||
|
pass_filenames: false
|
||||||
|
language: system
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
3.9.7
|
||||||
14
Makefile
Normal file
14
Makefile
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
POETRY ?= poetry run
|
||||||
|
SRC := ops_bot
|
||||||
|
TESTS := tests
|
||||||
|
fmt:
|
||||||
|
$(POETRY) black $(SRC)
|
||||||
|
$(POETRY) isort --profile black $(SRC)
|
||||||
|
lint:
|
||||||
|
$(POETRY) flake8 $(SRC)
|
||||||
|
$(POETRY) bandit --silent --recursive $(SRC)
|
||||||
|
types:
|
||||||
|
$(POETRY) mypy $(SRC)
|
||||||
|
|
||||||
|
test:
|
||||||
|
$(POETRY) pytest $(TESTS)
|
||||||
23
README.md
23
README.md
|
|
@ -2,8 +2,30 @@
|
||||||
|
|
||||||
a bot for ops in matrix
|
a bot for ops in matrix
|
||||||
|
|
||||||
|
## Dev Setup
|
||||||
|
|
||||||
|
|
||||||
|
`.env`:
|
||||||
|
```
|
||||||
|
MATRIX_HOMESERVER=https://matrix.org
|
||||||
|
MATRIX_USER_ID=@YOURBOT:matrix.org
|
||||||
|
MATRIX_PASSWORD="changeme"
|
||||||
|
MATRIX_DEVICE_NAME=my-bot-server
|
||||||
|
MATRIX_VERIFY_SSL=True
|
||||||
|
MATRIX_STORE_PATH=/abs/path/to/persistent-store
|
||||||
|
BOT_ROUTING_KEYS="{\"room1\": \"!XXXX:matrix.org\", \"room2\": \"!YYYYY:matrix.org\"}"
|
||||||
|
BOT_BEARER_TOKEN="changeme"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
source .env
|
||||||
|
poetry install
|
||||||
|
poetry run start
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
```
|
||||||
matrix-ops-bot
|
matrix-ops-bot
|
||||||
Copyright (C) 2022 Abel Luck <abel@guardianproject.info>
|
Copyright (C) 2022 Abel Luck <abel@guardianproject.info>
|
||||||
|
|
||||||
|
|
@ -19,3 +41,4 @@ GNU Affero General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
```
|
||||||
1
ops_bot/__init__.py
Normal file
1
ops_bot/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
__version__ = "0.1.0"
|
||||||
103
ops_bot/main.py
Normal file
103
ops_bot/main.py
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
import asyncio
|
||||||
|
from typing import Any, Dict, Optional, cast
|
||||||
|
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import Depends, FastAPI, HTTPException, Request, status
|
||||||
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
|
from pydantic import BaseSettings
|
||||||
|
|
||||||
|
from ops_bot import pagerduty
|
||||||
|
from ops_bot.matrix import MatrixClient, MatrixClientSettings
|
||||||
|
|
||||||
|
|
||||||
|
class BotSettings(BaseSettings):
|
||||||
|
bearer_token: str
|
||||||
|
routing_keys: Dict[str, str]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_prefix = "BOT_"
|
||||||
|
case_sensitive = False
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
security = HTTPBearer()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_matrix_service(request: Request) -> MatrixClient:
|
||||||
|
"""A helper to fetch the matrix client from the app state"""
|
||||||
|
return cast(MatrixClient, request.app.state.matrix_client)
|
||||||
|
|
||||||
|
|
||||||
|
async def matrix_main(matrix_client: MatrixClient) -> None:
|
||||||
|
"""Execs the matrix client asyncio task"""
|
||||||
|
workers = [asyncio.create_task(matrix_client.start())]
|
||||||
|
await asyncio.gather(*workers)
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
async def startup_event() -> None:
|
||||||
|
bot_settings = BotSettings(_env_file=".env", _env_file_encoding="utf-8")
|
||||||
|
matrix_settings = MatrixClientSettings(_env_file=".env", _env_file_encoding="utf-8")
|
||||||
|
matrix_settings.join_rooms = list(bot_settings.routing_keys.values())
|
||||||
|
c = MatrixClient(settings=matrix_settings)
|
||||||
|
app.state.matrix_client = c
|
||||||
|
app.state.bot_settings = bot_settings
|
||||||
|
asyncio.create_task(matrix_main(c))
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event("shutdown")
|
||||||
|
async def shutdown_event() -> None:
|
||||||
|
await app.state.matrix_client.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root() -> Dict[str, str]:
|
||||||
|
return {"message": "Hello World"}
|
||||||
|
|
||||||
|
|
||||||
|
def authorize(
|
||||||
|
request: Request, credentials: HTTPAuthorizationCredentials = Depends(security)
|
||||||
|
) -> bool:
|
||||||
|
bearer_token = request.app.state.bot_settings.bearer_token
|
||||||
|
if credentials.credentials != bearer_token:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Incorrect bearer token",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_destination(bot_settings: BotSettings, routing_key: str) -> Optional[str]:
|
||||||
|
return bot_settings.routing_keys.get(routing_key, None)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/hook/pagerduty/{routing_key}")
|
||||||
|
async def pagerduty_hook(
|
||||||
|
request: Request,
|
||||||
|
matrix_client: MatrixClient = Depends(get_matrix_service),
|
||||||
|
auth: bool = Depends(authorize),
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
payload: Any = await request.json()
|
||||||
|
room_id = get_destination(
|
||||||
|
request.app.state.bot_settings, request.path_params["routing_key"]
|
||||||
|
)
|
||||||
|
if room_id is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail="Unknown routing key"
|
||||||
|
)
|
||||||
|
msg_plain, msg_formatted = pagerduty.parse_pagerduty_event(payload)
|
||||||
|
await matrix_client.room_send(
|
||||||
|
room_id,
|
||||||
|
msg_plain,
|
||||||
|
message_formatted=msg_formatted,
|
||||||
|
)
|
||||||
|
return {"message": msg_plain, "message_formatted": msg_formatted}
|
||||||
|
|
||||||
|
|
||||||
|
def start_dev() -> None:
|
||||||
|
uvicorn.run("ops_bot.main:app", port=1111, host="127.0.0.1", reload=True)
|
||||||
|
|
||||||
|
|
||||||
|
def start() -> None:
|
||||||
|
uvicorn.run("ops_bot.main:app", port=1111, host="0.0.0.0") # nosec B104
|
||||||
166
ops_bot/matrix.py
Normal file
166
ops_bot/matrix.py
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
from typing import List, Optional, Protocol
|
||||||
|
|
||||||
|
from markdown import markdown
|
||||||
|
from nio import AsyncClient, AsyncClientConfig, LoginResponse
|
||||||
|
from pydantic import BaseModel, BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class ClientCredentials(BaseModel):
|
||||||
|
homeserver: str
|
||||||
|
user_id: str
|
||||||
|
device_id: str
|
||||||
|
access_token: str
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialStorage(Protocol):
|
||||||
|
def save_config(self, config: ClientCredentials) -> None:
|
||||||
|
"""Save config"""
|
||||||
|
|
||||||
|
def read_config(self) -> ClientCredentials:
|
||||||
|
"""Load config"""
|
||||||
|
|
||||||
|
|
||||||
|
class LocalCredentialStore:
|
||||||
|
def __init__(self, config_file_path: pathlib.Path):
|
||||||
|
self.credential_file: pathlib.Path = config_file_path
|
||||||
|
|
||||||
|
def save(self, config: ClientCredentials) -> None:
|
||||||
|
with self.credential_file.open(mode="w") as f:
|
||||||
|
json.dump(config.dict(), f)
|
||||||
|
|
||||||
|
def read(self) -> ClientCredentials:
|
||||||
|
with self.credential_file.open(mode="r") as f:
|
||||||
|
return ClientCredentials(**json.load(f))
|
||||||
|
|
||||||
|
def exists(self) -> bool:
|
||||||
|
return self.credential_file.exists()
|
||||||
|
|
||||||
|
|
||||||
|
class MatrixClientSettings(BaseSettings):
|
||||||
|
homeserver: str
|
||||||
|
user_id: str
|
||||||
|
password: str
|
||||||
|
device_name: str
|
||||||
|
store_path: str
|
||||||
|
join_rooms: Optional[List[str]]
|
||||||
|
verify_ssl: Optional[bool] = True
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_prefix = "MATRIX_"
|
||||||
|
case_sensitive = False
|
||||||
|
|
||||||
|
|
||||||
|
class MatrixClient:
|
||||||
|
def __init__(self, settings: MatrixClientSettings):
|
||||||
|
self.settings = settings
|
||||||
|
self.store_path = pathlib.Path(settings.store_path)
|
||||||
|
self.credential_store = LocalCredentialStore(
|
||||||
|
self.store_path.joinpath("credentials.json")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client: AsyncClient = None
|
||||||
|
self.client_config = AsyncClientConfig(
|
||||||
|
max_limit_exceeded=0,
|
||||||
|
max_timeouts=0,
|
||||||
|
store_sync_tokens=True,
|
||||||
|
encryption_enabled=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.greeting_sent = False
|
||||||
|
|
||||||
|
async def start(self) -> None:
|
||||||
|
await self.login()
|
||||||
|
|
||||||
|
if self.client.should_upload_keys:
|
||||||
|
await self.client.keys_upload()
|
||||||
|
|
||||||
|
if self.settings.join_rooms:
|
||||||
|
for room in self.settings.join_rooms:
|
||||||
|
await self.client.join(room)
|
||||||
|
|
||||||
|
await self.client.joined_rooms()
|
||||||
|
await self.client.sync_forever(timeout=300000, full_state=True)
|
||||||
|
|
||||||
|
def save_credentials(self, resp: LoginResponse, homeserver: str) -> None:
|
||||||
|
credentials = ClientCredentials(
|
||||||
|
homeserver=homeserver,
|
||||||
|
user_id=resp.user_id,
|
||||||
|
device_id=resp.device_id,
|
||||||
|
access_token=resp.access_token,
|
||||||
|
)
|
||||||
|
self.credential_store.save(credentials)
|
||||||
|
|
||||||
|
async def login_fresh(self) -> None:
|
||||||
|
self.client = AsyncClient(
|
||||||
|
self.settings.homeserver,
|
||||||
|
self.settings.user_id,
|
||||||
|
self.settings.store_path,
|
||||||
|
config=self.client_config,
|
||||||
|
ssl=self.settings.verify_ssl,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await self.client.login(
|
||||||
|
password=self.settings.password, device_name=self.settings.device_name
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(response, LoginResponse):
|
||||||
|
self.save_credentials(response, self.settings.homeserver)
|
||||||
|
else:
|
||||||
|
logging.error(
|
||||||
|
f'Login for "{self.settings.user_id}" via homeserver="{self.settings.homeserver}"'
|
||||||
|
)
|
||||||
|
logging.info(f"Login failure response: {response}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
async def login_with_credentials(self) -> None:
|
||||||
|
credentials = self.credential_store.read()
|
||||||
|
self.client = AsyncClient(
|
||||||
|
credentials.homeserver,
|
||||||
|
credentials.user_id,
|
||||||
|
device_id=credentials.device_id,
|
||||||
|
store_path=self.store_path,
|
||||||
|
config=self.client_config,
|
||||||
|
ssl=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.client.restore_login(
|
||||||
|
user_id=credentials.user_id,
|
||||||
|
device_id=credentials.device_id,
|
||||||
|
access_token=credentials.access_token,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def login(self) -> None:
|
||||||
|
if self.credential_store.exists():
|
||||||
|
await self.login_with_credentials()
|
||||||
|
else:
|
||||||
|
await self.login_fresh()
|
||||||
|
|
||||||
|
async def room_send(
|
||||||
|
self,
|
||||||
|
room: str,
|
||||||
|
message: str,
|
||||||
|
message_formatted: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
content = {
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": f"{message}",
|
||||||
|
}
|
||||||
|
if message_formatted is not None:
|
||||||
|
content["format"] = "org.matrix.custom.html"
|
||||||
|
content["formatted_body"] = markdown(
|
||||||
|
message_formatted, extensions=["extra"]
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.client.room_send(
|
||||||
|
room_id=room,
|
||||||
|
message_type="m.room.message",
|
||||||
|
content=content,
|
||||||
|
ignore_unverified_devices=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def shutdown(self) -> None:
|
||||||
|
await self.client.close()
|
||||||
41
ops_bot/pagerduty.py
Normal file
41
ops_bot/pagerduty.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import json
|
||||||
|
from typing import Any, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
def urgency_color(urgency: str) -> str:
|
||||||
|
if urgency == "high":
|
||||||
|
return "#dc3545"
|
||||||
|
else:
|
||||||
|
return "#17a2b8"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_pagerduty_event(payload: Any) -> Tuple[str, str]:
|
||||||
|
"""
|
||||||
|
Parses a pagerduty webhook v3 event into a human readable message.
|
||||||
|
Returns a tuple where the first item is plain text, and the second item is matrix html formatted text
|
||||||
|
"""
|
||||||
|
event = payload["event"]
|
||||||
|
# evt_id = event["id"]
|
||||||
|
# event_type = event["event_type"]
|
||||||
|
# resource_type = event["resource_type"]
|
||||||
|
# occurred_at = event["occurred_at"]
|
||||||
|
data = event["data"]
|
||||||
|
data_type = data["type"]
|
||||||
|
|
||||||
|
if data_type == "incident":
|
||||||
|
url = data["html_url"]
|
||||||
|
status: str = data["status"]
|
||||||
|
title: str = data["title"]
|
||||||
|
service_name: str = data["service"]["summary"]
|
||||||
|
urgency: str = data.get("urgency", "high")
|
||||||
|
plain = f"{status}: on {service_name}: {title} {url}"
|
||||||
|
formatted = f"<strong><font color={urgency_color(urgency)}>{status.upper()}</font></strong> on {service_name}: [{title}]({url})"
|
||||||
|
return plain, formatted
|
||||||
|
|
||||||
|
payload_str = json.dumps(payload, sort_keys=True, indent=2)
|
||||||
|
return (
|
||||||
|
"unhandled",
|
||||||
|
f"""**unhandled pager duty event**
|
||||||
|
<pre><code class="language-json">{payload_str}</code></pre>
|
||||||
|
""",
|
||||||
|
)
|
||||||
884
poetry.lock
generated
Normal file
884
poetry.lock
generated
Normal file
|
|
@ -0,0 +1,884 @@
|
||||||
|
[[package]]
|
||||||
|
name = "aiofiles"
|
||||||
|
version = "0.6.0"
|
||||||
|
description = "File support for asyncio."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiohttp"
|
||||||
|
version = "3.8.1"
|
||||||
|
description = "Async http client/server framework (asyncio)"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
aiosignal = ">=1.1.2"
|
||||||
|
async-timeout = ">=4.0.0a3,<5.0"
|
||||||
|
attrs = ">=17.3.0"
|
||||||
|
charset-normalizer = ">=2.0,<3.0"
|
||||||
|
frozenlist = ">=1.1.1"
|
||||||
|
multidict = ">=4.5,<7.0"
|
||||||
|
yarl = ">=1.0,<2.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
speedups = ["aiodns", "brotli", "cchardet"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiohttp-socks"
|
||||||
|
version = "0.7.1"
|
||||||
|
description = "Proxy connector for aiohttp"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
aiohttp = ">=2.3.2"
|
||||||
|
attrs = ">=19.2.0"
|
||||||
|
python-socks = {version = ">=2.0.0,<3.0.0", extras = ["asyncio"]}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiosignal"
|
||||||
|
version = "1.2.0"
|
||||||
|
description = "aiosignal: a list of registered asynchronous callbacks"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
frozenlist = ">=1.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "3.6.1"
|
||||||
|
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
idna = ">=2.8"
|
||||||
|
sniffio = ">=1.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
|
||||||
|
test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
|
||||||
|
trio = ["trio (>=0.16)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-timeout"
|
||||||
|
version = "4.0.2"
|
||||||
|
description = "Timeout context manager for asyncio programs"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomicwrites"
|
||||||
|
version = "1.4.1"
|
||||||
|
description = "Atomic file writes."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "attrs"
|
||||||
|
version = "21.4.0"
|
||||||
|
description = "Classes Without Boilerplate"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
|
||||||
|
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
|
||||||
|
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
|
||||||
|
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bandit"
|
||||||
|
version = "1.7.4"
|
||||||
|
description = "Security oriented static analyser for python code."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""}
|
||||||
|
GitPython = ">=1.0.1"
|
||||||
|
PyYAML = ">=5.3.1"
|
||||||
|
stevedore = ">=1.20.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml", "beautifulsoup4 (>=4.8.0)", "pylint (==1.9.4)"]
|
||||||
|
toml = ["toml"]
|
||||||
|
yaml = ["pyyaml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "black"
|
||||||
|
version = "22.6.0"
|
||||||
|
description = "The uncompromising code formatter."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.2"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
click = ">=8.0.0"
|
||||||
|
mypy-extensions = ">=0.4.3"
|
||||||
|
pathspec = ">=0.9.0"
|
||||||
|
platformdirs = ">=2"
|
||||||
|
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
|
||||||
|
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
colorama = ["colorama (>=0.4.3)"]
|
||||||
|
d = ["aiohttp (>=3.7.4)"]
|
||||||
|
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||||
|
uvloop = ["uvloop (>=0.15.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cachetools"
|
||||||
|
version = "4.2.4"
|
||||||
|
description = "Extensible memoizing collections and decorators"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "~=3.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cffi"
|
||||||
|
version = "1.15.1"
|
||||||
|
description = "Foreign Function Interface for Python calling C code."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pycparser = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charset-normalizer"
|
||||||
|
version = "2.1.0"
|
||||||
|
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
unicode_backport = ["unicodedata2"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.1.3"
|
||||||
|
description = "Composable command line interface toolkit"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.5"
|
||||||
|
description = "Cross-platform colored terminal text."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastapi"
|
||||||
|
version = "0.79.0"
|
||||||
|
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
|
||||||
|
starlette = "0.19.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
|
||||||
|
dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)", "pre-commit (>=2.17.0,<3.0.0)"]
|
||||||
|
doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer (>=0.4.1,<0.5.0)", "pyyaml (>=5.3.1,<7.0.0)"]
|
||||||
|
test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<4.0.0)", "black (==22.3.0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==4.2.1)", "types-orjson (==3.6.2)", "types-dataclasses (==0.6.5)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flake8"
|
||||||
|
version = "4.0.1"
|
||||||
|
description = "the modular source code checker: pep8 pyflakes and co"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mccabe = ">=0.6.0,<0.7.0"
|
||||||
|
pycodestyle = ">=2.8.0,<2.9.0"
|
||||||
|
pyflakes = ">=2.4.0,<2.5.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "frozenlist"
|
||||||
|
version = "1.3.0"
|
||||||
|
description = "A list-like structure which implements collections.abc.MutableSequence"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "future"
|
||||||
|
version = "0.18.2"
|
||||||
|
description = "Clean single-source support for Python 3 and 2"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitdb"
|
||||||
|
version = "4.0.9"
|
||||||
|
description = "Git Object Database"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
smmap = ">=3.0.1,<6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gitpython"
|
||||||
|
version = "3.1.27"
|
||||||
|
description = "GitPython is a python library used to interact with Git repositories"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
gitdb = ">=4.0.1,<5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h11"
|
||||||
|
version = "0.12.0"
|
||||||
|
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h2"
|
||||||
|
version = "4.1.0"
|
||||||
|
description = "HTTP/2 State-Machine based protocol implementation"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
hpack = ">=4.0,<5"
|
||||||
|
hyperframe = ">=6.0,<7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hpack"
|
||||||
|
version = "4.0.0"
|
||||||
|
description = "Pure-Python HPACK header compression"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyperframe"
|
||||||
|
version = "6.0.1"
|
||||||
|
description = "HTTP/2 framing layer for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.3"
|
||||||
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "importlib-metadata"
|
||||||
|
version = "4.12.0"
|
||||||
|
description = "Read metadata from Python packages"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
zipp = ">=0.5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
|
||||||
|
perf = ["ipython"]
|
||||||
|
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "isort"
|
||||||
|
version = "5.10.1"
|
||||||
|
description = "A Python utility / library to sort Python imports."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.1,<4.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
|
||||||
|
requirements_deprecated_finder = ["pipreqs", "pip-api"]
|
||||||
|
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||||
|
plugins = ["setuptools"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonschema"
|
||||||
|
version = "3.2.0"
|
||||||
|
description = "An implementation of JSON Schema validation for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
attrs = ">=17.4.0"
|
||||||
|
pyrsistent = ">=0.14.0"
|
||||||
|
six = ">=1.11.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"]
|
||||||
|
format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "logbook"
|
||||||
|
version = "1.5.3"
|
||||||
|
description = "A logging replacement for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["redis", "brotli", "pytest (>4.0)", "execnet (>=1.0.9)", "cython", "pyzmq", "pytest-cov (>=2.6)", "sqlalchemy", "jinja2"]
|
||||||
|
compression = ["brotli"]
|
||||||
|
dev = ["pytest-cov (>=2.6)", "pytest (>4.0)", "cython"]
|
||||||
|
execnet = ["execnet (>=1.0.9)"]
|
||||||
|
jinja = ["jinja2"]
|
||||||
|
redis = ["redis"]
|
||||||
|
sqlalchemy = ["sqlalchemy"]
|
||||||
|
test = ["pytest-cov (>=2.6)", "pytest (>4.0)"]
|
||||||
|
zmq = ["pyzmq"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown"
|
||||||
|
version = "3.4.1"
|
||||||
|
description = "Python implementation of Markdown."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
testing = ["coverage", "pyyaml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matrix-nio"
|
||||||
|
version = "0.19.0"
|
||||||
|
description = "A Python Matrix client library, designed according to sans I/O principles."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.1,<4.0.0"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
aiofiles = ">=0.6.0,<0.7.0"
|
||||||
|
aiohttp = ">=3.7.4,<4.0.0"
|
||||||
|
aiohttp-socks = ">=0.7.0,<0.8.0"
|
||||||
|
atomicwrites = {version = ">=1.4.0,<2.0.0", optional = true, markers = "extra == \"e2e\""}
|
||||||
|
cachetools = {version = ">=4.2.1,<5.0.0", optional = true, markers = "extra == \"e2e\""}
|
||||||
|
future = ">=0.18.2,<0.19.0"
|
||||||
|
h11 = ">=0.12.0,<0.13.0"
|
||||||
|
h2 = ">=4.0.0,<5.0.0"
|
||||||
|
jsonschema = ">=3.2.0,<4.0.0"
|
||||||
|
logbook = ">=1.5.3,<2.0.0"
|
||||||
|
peewee = {version = ">=3.14.4,<4.0.0", optional = true, markers = "extra == \"e2e\""}
|
||||||
|
pycryptodome = ">=3.10.1,<4.0.0"
|
||||||
|
python-olm = {version = ">=3.1.3,<4.0.0", optional = true, markers = "extra == \"e2e\""}
|
||||||
|
unpaddedbase64 = ">=2.1.0,<3.0.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
e2e = ["python-olm (>=3.1.3,<4.0.0)", "peewee (>=3.14.4,<4.0.0)", "cachetools (>=4.2.1,<5.0.0)", "atomicwrites (>=1.4.0,<2.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mccabe"
|
||||||
|
version = "0.6.1"
|
||||||
|
description = "McCabe checker, plugin for flake8"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "more-itertools"
|
||||||
|
version = "8.13.0"
|
||||||
|
description = "More routines for operating on iterables, beyond itertools"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multidict"
|
||||||
|
version = "6.0.2"
|
||||||
|
description = "multidict implementation"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy"
|
||||||
|
version = "0.971"
|
||||||
|
description = "Optional static typing for Python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
mypy-extensions = ">=0.4.3"
|
||||||
|
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||||
|
typing-extensions = ">=3.10"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dmypy = ["psutil (>=4.0)"]
|
||||||
|
python2 = ["typed-ast (>=1.4.0,<2)"]
|
||||||
|
reports = ["lxml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-extensions"
|
||||||
|
version = "0.4.3"
|
||||||
|
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "21.3"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathspec"
|
||||||
|
version = "0.9.0"
|
||||||
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pbr"
|
||||||
|
version = "5.9.0"
|
||||||
|
description = "Python Build Reasonableness"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "peewee"
|
||||||
|
version = "3.15.1"
|
||||||
|
description = "a little orm"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "platformdirs"
|
||||||
|
version = "2.5.2"
|
||||||
|
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
||||||
|
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "0.13.1"
|
||||||
|
description = "plugin and hook calling mechanisms for python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["pre-commit", "tox"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "py"
|
||||||
|
version = "1.11.0"
|
||||||
|
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycodestyle"
|
||||||
|
version = "2.8.0"
|
||||||
|
description = "Python style guide checker"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycparser"
|
||||||
|
version = "2.21"
|
||||||
|
description = "C parser in Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycryptodome"
|
||||||
|
version = "3.15.0"
|
||||||
|
description = "Cryptographic library for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "1.9.1"
|
||||||
|
description = "Data validation and settings management using python type hints"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.1"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""}
|
||||||
|
typing-extensions = ">=3.7.4.3"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dotenv = ["python-dotenv (>=0.10.4)"]
|
||||||
|
email = ["email-validator (>=1.0.3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyflakes"
|
||||||
|
version = "2.4.0"
|
||||||
|
description = "passive checker of Python programs"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyparsing"
|
||||||
|
version = "3.0.9"
|
||||||
|
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.8"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
diagrams = ["railroad-diagrams", "jinja2"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyrsistent"
|
||||||
|
version = "0.18.1"
|
||||||
|
description = "Persistent/Functional/Immutable data structures"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "5.4.3"
|
||||||
|
description = "pytest: simple powerful testing with Python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||||
|
attrs = ">=17.4.0"
|
||||||
|
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
|
more-itertools = ">=4.0.0"
|
||||||
|
packaging = "*"
|
||||||
|
pluggy = ">=0.12,<1.0"
|
||||||
|
py = ">=1.5.0"
|
||||||
|
wcwidth = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
checkqa-mypy = ["mypy (==v0.761)"]
|
||||||
|
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "0.20.0"
|
||||||
|
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
cli = ["click (>=5.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-olm"
|
||||||
|
version = "3.1.3"
|
||||||
|
description = "python CFFI bindings for the olm cryptographic ratchet library"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cffi = ">=1.0.0"
|
||||||
|
future = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-socks"
|
||||||
|
version = "2.0.3"
|
||||||
|
description = "Core proxy (SOCKS4, SOCKS5, HTTP tunneling) functionality for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
async-timeout = {version = ">=3.0.1", optional = true, markers = "extra == \"asyncio\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
anyio = ["anyio (>=3.3.4)"]
|
||||||
|
asyncio = ["async-timeout (>=3.0.1)"]
|
||||||
|
curio = ["curio (>=1.4)"]
|
||||||
|
trio = ["trio (>=0.16.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyyaml"
|
||||||
|
version = "6.0"
|
||||||
|
description = "YAML parser and emitter for Python"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.16.0"
|
||||||
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smmap"
|
||||||
|
version = "5.0.0"
|
||||||
|
description = "A pure Python implementation of a sliding window memory map manager"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.2.0"
|
||||||
|
description = "Sniff out which async library your code is running under"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "starlette"
|
||||||
|
version = "0.19.1"
|
||||||
|
description = "The little ASGI library that shines."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
anyio = ">=3.4.0,<5"
|
||||||
|
typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stevedore"
|
||||||
|
version = "4.0.0"
|
||||||
|
description = "Manage dynamic plugins for Python applications"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pbr = ">=2.0.0,<2.1.0 || >2.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "ANSII Color formatting for output in terminal."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tomli"
|
||||||
|
version = "2.0.1"
|
||||||
|
description = "A lil' TOML parser"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-markdown"
|
||||||
|
version = "3.4.0"
|
||||||
|
description = "Typing stubs for Markdown"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-termcolor"
|
||||||
|
version = "1.1.5"
|
||||||
|
description = "Typing stubs for termcolor"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.3.0"
|
||||||
|
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unpaddedbase64"
|
||||||
|
version = "2.1.0"
|
||||||
|
description = "Encode and decode Base64 without \"=\" padding"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6,<4.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uvicorn"
|
||||||
|
version = "0.18.2"
|
||||||
|
description = "The lightning-fast ASGI server."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
click = ">=7.0"
|
||||||
|
h11 = ">=0.8"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchfiles (>=0.13)", "python-dotenv (>=0.13)", "PyYAML (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "colorama (>=0.4)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wcwidth"
|
||||||
|
version = "0.2.5"
|
||||||
|
description = "Measures the displayed width of unicode strings in a terminal"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yarl"
|
||||||
|
version = "1.7.2"
|
||||||
|
description = "Yet another URL library"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
idna = ">=2.0"
|
||||||
|
multidict = ">=4.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zipp"
|
||||||
|
version = "3.8.1"
|
||||||
|
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"]
|
||||||
|
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "1.1"
|
||||||
|
python-versions = "^3.9"
|
||||||
|
content-hash = "294fd35d1328fe80c8d9d4aa1e4eaa86433e03e6166eef0c4f6404919dd455c8"
|
||||||
|
|
||||||
|
[metadata.files]
|
||||||
|
aiofiles = []
|
||||||
|
aiohttp = []
|
||||||
|
aiohttp-socks = []
|
||||||
|
aiosignal = []
|
||||||
|
anyio = []
|
||||||
|
async-timeout = []
|
||||||
|
atomicwrites = []
|
||||||
|
attrs = []
|
||||||
|
bandit = []
|
||||||
|
black = []
|
||||||
|
cachetools = []
|
||||||
|
cffi = []
|
||||||
|
charset-normalizer = []
|
||||||
|
click = []
|
||||||
|
colorama = []
|
||||||
|
fastapi = []
|
||||||
|
flake8 = []
|
||||||
|
frozenlist = []
|
||||||
|
future = []
|
||||||
|
gitdb = []
|
||||||
|
gitpython = []
|
||||||
|
h11 = []
|
||||||
|
h2 = []
|
||||||
|
hpack = []
|
||||||
|
hyperframe = []
|
||||||
|
idna = []
|
||||||
|
importlib-metadata = []
|
||||||
|
isort = []
|
||||||
|
jsonschema = []
|
||||||
|
logbook = []
|
||||||
|
markdown = []
|
||||||
|
matrix-nio = []
|
||||||
|
mccabe = []
|
||||||
|
more-itertools = []
|
||||||
|
multidict = []
|
||||||
|
mypy = []
|
||||||
|
mypy-extensions = []
|
||||||
|
packaging = []
|
||||||
|
pathspec = []
|
||||||
|
pbr = []
|
||||||
|
peewee = []
|
||||||
|
platformdirs = []
|
||||||
|
pluggy = []
|
||||||
|
py = []
|
||||||
|
pycodestyle = []
|
||||||
|
pycparser = []
|
||||||
|
pycryptodome = []
|
||||||
|
pydantic = []
|
||||||
|
pyflakes = []
|
||||||
|
pyparsing = []
|
||||||
|
pyrsistent = []
|
||||||
|
pytest = []
|
||||||
|
python-dotenv = []
|
||||||
|
python-olm = []
|
||||||
|
python-socks = []
|
||||||
|
pyyaml = []
|
||||||
|
six = []
|
||||||
|
smmap = []
|
||||||
|
sniffio = []
|
||||||
|
starlette = []
|
||||||
|
stevedore = []
|
||||||
|
termcolor = []
|
||||||
|
tomli = []
|
||||||
|
types-markdown = []
|
||||||
|
types-termcolor = []
|
||||||
|
typing-extensions = []
|
||||||
|
unpaddedbase64 = []
|
||||||
|
uvicorn = []
|
||||||
|
wcwidth = []
|
||||||
|
yarl = []
|
||||||
|
zipp = []
|
||||||
63
pyproject.toml
Normal file
63
pyproject.toml
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "ops_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Abel Luck <abel@guardianproject.info>"]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.9"
|
||||||
|
matrix-nio = {extras = ["e2e"], version = "^0.19.0"}
|
||||||
|
fastapi = "^0.79.0"
|
||||||
|
uvicorn = "^0.18.2"
|
||||||
|
termcolor = "^1.1.0"
|
||||||
|
Markdown = "^3.4.1"
|
||||||
|
pydantic = {extras = ["dotenv"], version = "^1.9.1"}
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
pytest = "^5.2"
|
||||||
|
black = "^22.6.0"
|
||||||
|
isort = "^5.10.1"
|
||||||
|
mypy = "^0.971"
|
||||||
|
bandit = "^1.7.4"
|
||||||
|
flake8 = "^4.0.1"
|
||||||
|
types-Markdown = "^3.4.0"
|
||||||
|
types-termcolor = "^1.1.5"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
start = "ops_bot.main:start_dev"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 88
|
||||||
|
target-version = ['py39']
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
files = "ops_bot,tests"
|
||||||
|
mypy_path = "ops_bot"
|
||||||
|
ignore_missing_imports = true
|
||||||
|
follow_imports = "silent"
|
||||||
|
# Ensure full coverage
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
|
||||||
|
# Restrict dynamic typing
|
||||||
|
disallow_any_generics = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
warn_return_any = true
|
||||||
|
|
||||||
|
# Know exactly what you're doing
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
warn_unreachable = true
|
||||||
|
show_error_codes = true
|
||||||
|
|
||||||
|
# Explicit is better than implicit
|
||||||
|
no_implicit_optional = true
|
||||||
71
requirements.txt
Normal file
71
requirements.txt
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
aiofiles==0.6.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
aiohttp-socks==0.7.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
aiohttp==3.8.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.6"
|
||||||
|
aiosignal==1.2.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.6"
|
||||||
|
anyio==3.6.1; python_version >= "3.6" and python_full_version >= "3.6.2"
|
||||||
|
async-timeout==4.0.2; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.6"
|
||||||
|
atomicwrites==1.4.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and (python_version >= "3.5" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.5" and python_full_version >= "3.4.0")
|
||||||
|
attrs==21.4.0; python_full_version >= "3.6.1" and python_version >= "3.6" and python_full_version < "4.0.0"
|
||||||
|
bandit==1.7.4; python_version >= "3.7"
|
||||||
|
black==22.6.0; python_full_version >= "3.6.2"
|
||||||
|
cachetools==4.2.4; python_version >= "3.5" and python_version < "4.0" and python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
cffi==1.15.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
charset-normalizer==2.1.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.6"
|
||||||
|
click==8.1.3; python_version >= "3.7" and python_full_version >= "3.6.2"
|
||||||
|
colorama==0.4.5; sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.6.2" and platform_system == "Windows"
|
||||||
|
fastapi==0.79.0; python_full_version >= "3.6.1"
|
||||||
|
flake8==4.0.1; python_version >= "3.6"
|
||||||
|
frozenlist==1.3.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.7"
|
||||||
|
future==0.18.2; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
gitdb==4.0.9; python_version >= "3.7"
|
||||||
|
gitpython==3.1.27; python_version >= "3.7"
|
||||||
|
h11==0.12.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.7"
|
||||||
|
h2==4.1.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
hpack==4.0.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
hyperframe==6.0.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
idna==3.3; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.6"
|
||||||
|
importlib-metadata==4.12.0; python_version < "3.10" and python_version >= "3.7"
|
||||||
|
isort==5.10.1; python_full_version >= "3.6.1" and python_version < "4.0"
|
||||||
|
jsonschema==3.2.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
logbook==1.5.3; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
markdown==3.4.1; python_version >= "3.7"
|
||||||
|
matrix-nio==0.19.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
mccabe==0.6.1; python_version >= "3.6"
|
||||||
|
more-itertools==8.13.0; python_version >= "3.5"
|
||||||
|
multidict==6.0.2; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.7"
|
||||||
|
mypy-extensions==0.4.3; python_full_version >= "3.6.2" and python_version >= "3.6"
|
||||||
|
mypy==0.971; python_version >= "3.6"
|
||||||
|
packaging==21.3; python_version >= "3.6"
|
||||||
|
pathspec==0.9.0; python_full_version >= "3.6.2"
|
||||||
|
pbr==5.9.0; python_version >= "3.8"
|
||||||
|
peewee==3.15.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
platformdirs==2.5.2; python_version >= "3.7" and python_full_version >= "3.6.2"
|
||||||
|
pluggy==0.13.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5"
|
||||||
|
py==1.11.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5"
|
||||||
|
pycodestyle==2.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
|
||||||
|
pycparser==2.21; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
pycryptodome==3.15.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
pydantic==1.9.1; python_full_version >= "3.6.1"
|
||||||
|
pyflakes==2.4.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
|
||||||
|
pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.6"
|
||||||
|
pyrsistent==0.18.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.7"
|
||||||
|
pytest==5.4.3; python_version >= "3.5"
|
||||||
|
python-dotenv==0.20.0; python_full_version >= "3.6.1" and python_version >= "3.5"
|
||||||
|
python-olm==3.1.3; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
python-socks==2.0.3; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
pyyaml==6.0; python_version >= "3.7"
|
||||||
|
six==1.16.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
smmap==5.0.0; python_version >= "3.7"
|
||||||
|
sniffio==1.2.0; python_version >= "3.6" and python_full_version >= "3.6.2"
|
||||||
|
starlette==0.19.1; python_version >= "3.6" and python_full_version >= "3.6.1"
|
||||||
|
stevedore==4.0.0; python_version >= "3.8"
|
||||||
|
termcolor==1.1.0
|
||||||
|
tomli==2.0.1; python_full_version < "3.11.0a7" and python_full_version >= "3.6.2" and python_version >= "3.7" and python_version < "3.11"
|
||||||
|
types-markdown==3.4.0
|
||||||
|
types-termcolor==1.1.5
|
||||||
|
typing-extensions==4.3.0; python_version >= "3.7" and python_full_version >= "3.6.2" and python_version < "3.10"
|
||||||
|
unpaddedbase64==2.1.0; python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.1" and python_full_version < "4.0.0"
|
||||||
|
uvicorn==0.18.2; python_version >= "3.7"
|
||||||
|
wcwidth==0.2.5; python_version >= "3.5"
|
||||||
|
yarl==1.7.2; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.6"
|
||||||
|
zipp==3.8.1; python_version < "3.10" and python_version >= "3.7"
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
5
tests/test_ops_bot.py
Normal file
5
tests/test_ops_bot.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
from ops_bot import __version__
|
||||||
|
|
||||||
|
|
||||||
|
def test_version() -> None:
|
||||||
|
assert __version__ == "0.1.0"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue