mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
Make the bot runnable
This commit is contained in:
parent
ef645ab143
commit
192b4cf3d6
29 changed files with 247 additions and 315 deletions
|
@ -3,6 +3,7 @@
|
|||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/royalnet" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/royalnet.egg-info" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.8 (royalnet-1MWM6-kd-py3.8)" jdkType="Python SDK" />
|
||||
|
|
39
poetry.lock
generated
39
poetry.lock
generated
|
@ -82,7 +82,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|||
version = "7.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
category = "main"
|
||||
description = "Cross-platform colored terminal text."
|
||||
marker = "sys_platform == \"win32\""
|
||||
name = "colorama"
|
||||
|
@ -90,6 +90,17 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "0.4.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Log formatting with colors!"
|
||||
name = "colorlog"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
version = "4.0.2"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
|
@ -142,7 +153,7 @@ voice = ["PyNaCl (1.3.0)"]
|
|||
[package.source]
|
||||
reference = "09a08f9a9f126aa1f55c2444eb70508d1d52f8d9"
|
||||
type = "git"
|
||||
url = "https://github.com/Steffo99/discord.py.git"
|
||||
url = "https://github.com/Steffo99/discord.py"
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Discover and load entry points from installed packages."
|
||||
|
@ -328,7 +339,7 @@ description = "pytest: simple powerful testing with Python"
|
|||
name = "pytest"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "5.2.2"
|
||||
version = "5.2.3"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = ">=1.0"
|
||||
|
@ -562,6 +573,7 @@ version = "2019.11.5"
|
|||
alchemy_easy = ["sqlalchemy", "psycopg2_binary"]
|
||||
alchemy_hard = ["sqlalchemy", "psycopg2"]
|
||||
bard = ["ffmpeg_python", "youtube_dl"]
|
||||
colorlog = ["colorlog"]
|
||||
constellation = ["starlette", "uvicorn"]
|
||||
discord = ["discord.py", "pynacl"]
|
||||
herald = ["websockets"]
|
||||
|
@ -569,7 +581,7 @@ sentry = ["sentry_sdk"]
|
|||
telegram = ["python_telegram_bot"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "a51bc903341dd7fb6c2923e4f0a45654ccc9c011182ad9a67c2c80d24f62fb26"
|
||||
content-hash = "48fd4f6a0a25ffaf89999db4a999774b8e22794e07488b032ac0f244049caa5f"
|
||||
python-versions = "^3.8"
|
||||
|
||||
[metadata.files]
|
||||
|
@ -660,6 +672,10 @@ colorama = [
|
|||
{file = "colorama-0.4.1-py2.py3-none-any.whl", hash = "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"},
|
||||
{file = "colorama-0.4.1.tar.gz", hash = "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d"},
|
||||
]
|
||||
colorlog = [
|
||||
{file = "colorlog-4.0.2-py2.py3-none-any.whl", hash = "sha256:450f52ea2a2b6ebb308f034ea9a9b15cea51e65650593dca1da3eb792e4e4981"},
|
||||
{file = "colorlog-4.0.2.tar.gz", hash = "sha256:3cf31b25cbc8f86ec01fef582ef3b840950dea414084ed19ab922c8b493f9b42"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-2.8-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"},
|
||||
{file = "cryptography-2.8-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2"},
|
||||
|
@ -845,8 +861,8 @@ pyparsing = [
|
|||
{file = "pyparsing-2.4.5.tar.gz", hash = "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-5.2.2-py3-none-any.whl", hash = "sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4"},
|
||||
{file = "pytest-5.2.2.tar.gz", hash = "sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6"},
|
||||
{file = "pytest-5.2.3-py3-none-any.whl", hash = "sha256:b6cf7ad9064049ee486586b3a0ddd70dc5136c40e1147e7d286efd77ba66c5eb"},
|
||||
{file = "pytest-5.2.3.tar.gz", hash = "sha256:15837d2880cb94821087bc07476892ea740696b20e90288fd6c19e44b435abdb"},
|
||||
]
|
||||
python-dateutil = [
|
||||
{file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
|
||||
|
@ -936,24 +952,13 @@ websockets = [
|
|||
{file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"},
|
||||
{file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"},
|
||||
{file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"},
|
||||
{file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"},
|
||||
{file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"},
|
||||
{file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"},
|
||||
{file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"},
|
||||
{file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"},
|
||||
{file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"},
|
||||
{file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"},
|
||||
{file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"},
|
||||
{file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"},
|
||||
{file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"},
|
||||
{file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"},
|
||||
{file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"},
|
||||
{file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"},
|
||||
{file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"},
|
||||
{file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"},
|
||||
{file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"},
|
||||
{file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"},
|
||||
{file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"},
|
||||
{file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"},
|
||||
]
|
||||
yarl = [
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
# telegram
|
||||
python_telegram_bot = {version="^12.2.0", optional=true}
|
||||
# discord
|
||||
"discord.py" = {git="https://github.com/Steffo99/discord.py.git", optional=true} # discord.py 1.2.4 is missing Go Live related methods
|
||||
"discord.py" = {git="https://github.com/Steffo99/discord.py", optional=true} # discord.py 1.2.4 is missing Go Live related methods
|
||||
pynacl = {version="^1.3.0", optional=true} # This requires libffi-dev and python3.*-dev to be installed on Linux systems
|
||||
# bard
|
||||
ffmpeg_python = {version="~0.2.0", optional=true}
|
||||
|
@ -40,6 +40,8 @@
|
|||
sentry_sdk = {version="~0.13.2", optional=true}
|
||||
# herald
|
||||
websockets = {version="^8.1", optional=true}
|
||||
# colorlog
|
||||
colorlog = {version="^4.0.2", optional=true}
|
||||
|
||||
# Development dependencies
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
@ -55,7 +57,7 @@
|
|||
constellation = ["starlette", "uvicorn"]
|
||||
sentry = ["sentry_sdk"]
|
||||
herald = ["websockets"]
|
||||
|
||||
colorlog = ["colorlog"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
|
|
|
@ -1 +1,14 @@
|
|||
__version__ = "5.1a1"
|
||||
|
||||
from . import alchemy, bard, commands, constellation, herald, backpack, serf, utils
|
||||
|
||||
__all__ = [
|
||||
"alchemy",
|
||||
"bard",
|
||||
"commands",
|
||||
"constellation",
|
||||
"herald",
|
||||
"serf",
|
||||
"utils",
|
||||
"backpack",
|
||||
]
|
||||
|
|
|
@ -2,10 +2,9 @@ import click
|
|||
import typing
|
||||
import importlib
|
||||
import royalnet as r
|
||||
import royalherald as rh
|
||||
import multiprocessing
|
||||
import keyring
|
||||
import logging
|
||||
from logging import Formatter, StreamHandler, getLogger, Logger
|
||||
|
||||
|
||||
@click.command()
|
||||
|
@ -13,55 +12,53 @@ import logging
|
|||
help="Enable/disable the Telegram bot.")
|
||||
@click.option("--discord/--no-discord", default=None,
|
||||
help="Enable/disable the Discord bot.")
|
||||
@click.option("--webserver/--no-webserver", default=None,
|
||||
help="Enable/disable the Web server.")
|
||||
@click.option("--webserver-port", default=8001,
|
||||
help="The port on which the web server will listen on.")
|
||||
@click.option("-d", "--database", type=str, default=None,
|
||||
help="The PostgreSQL database path.")
|
||||
@click.option("-p", "--packs", type=str, multiple=True, default=[],
|
||||
help="The names of the Packs that should be used.")
|
||||
@click.option("-n", "--network-address", type=str, default=None,
|
||||
help="The Network server URL to connect to.")
|
||||
@click.option("-l", "--local-network-server", is_flag=True, default=False,
|
||||
help="Locally run a Network server and bind it to port 44444. Overrides -n.")
|
||||
@click.option("--local-network-server-port", type=int, default=44444,
|
||||
help="The port on which the local network will be ran.")
|
||||
@click.option("--constellation/--no-constellation", default=None,
|
||||
help="Enable/disable the Constellation web server.")
|
||||
@click.option("--herald/--no-herald", default=None,
|
||||
help="Enable/disable the integrated Herald server."
|
||||
" If turned off, Royalnet will try to connect to another server.")
|
||||
@click.option("--remote-herald-address", type=str, default=None,
|
||||
help="If --no-herald is specified, connect to the Herald server at this URL instead.")
|
||||
@click.option("-c", "--constellation-port", default=44445,
|
||||
help="The port on which the Constellation will serve webpages on.")
|
||||
@click.option("-a", "--alchemy-url", type=str, default=None,
|
||||
help="The Alchemy database path.")
|
||||
@click.option("-h", "--herald-port", type=int, default=44444,
|
||||
help="The port on which the Herald should be running.")
|
||||
@click.option("-p", "--pack", type=str, multiple=True, default=tuple(),
|
||||
help="Import the pack with the specified name and use it in the Royalnet instance.")
|
||||
@click.option("-s", "--secrets-name", type=str, default="__default__",
|
||||
help="The name in the keyring that the secrets are stored with.")
|
||||
@click.option("-v", "--verbose", is_flag=True, default=False,
|
||||
help="Print all possible debug information.")
|
||||
@click.option("-l", "--log-level", type=str, default="INFO",
|
||||
help="Select how much information you want to be printed on the console."
|
||||
" Valid log levels are: FATAL/ERROR/WARNING/INFO/DEBUG")
|
||||
def run(telegram: typing.Optional[bool],
|
||||
discord: typing.Optional[bool],
|
||||
webserver: typing.Optional[bool],
|
||||
webserver_port: typing.Optional[int],
|
||||
database: typing.Optional[str],
|
||||
packs: typing.Tuple[str],
|
||||
network_address: typing.Optional[str],
|
||||
local_network_server: bool,
|
||||
local_network_server_port: int,
|
||||
constellation: typing.Optional[bool],
|
||||
herald: typing.Optional[bool],
|
||||
remote_herald_address: typing.Optional[str],
|
||||
constellation_port: int,
|
||||
alchemy_url: typing.Optional[str],
|
||||
herald_port: int,
|
||||
pack: typing.Tuple[str],
|
||||
secrets_name: str,
|
||||
verbose: bool):
|
||||
# Setup logging
|
||||
if verbose:
|
||||
core_logger = logging.root
|
||||
core_logger.setLevel(logging.DEBUG)
|
||||
stream_handler = logging.StreamHandler()
|
||||
stream_handler.formatter = logging.Formatter("{asctime}\t{name}\t{levelname}\t{message}", style="{")
|
||||
core_logger.addHandler(stream_handler)
|
||||
core_logger.debug("Logging setup complete.")
|
||||
log_level: str):
|
||||
# Initialize logging
|
||||
royalnet_log: Logger = getLogger("royalnet")
|
||||
royalnet_log.setLevel(log_level)
|
||||
stream_handler = StreamHandler()
|
||||
stream_handler.formatter = Formatter("{asctime}\t{name}\t{levelname}\t{message}", style="{")
|
||||
royalnet_log.addHandler(stream_handler)
|
||||
|
||||
# Get the network password
|
||||
network_password = keyring.get_password(f"Royalnet/{secrets_name}", "network")
|
||||
|
||||
# Get the sentry dsn
|
||||
sentry_dsn = keyring.get_password(f"Royalnet/{secrets_name}", "sentry")
|
||||
def get_secret(username: str):
|
||||
return keyring.get_password(f"Royalnet/{secrets_name}", username)
|
||||
|
||||
# Enable / Disable interfaces
|
||||
interfaces = {
|
||||
"telegram": telegram,
|
||||
"discord": discord,
|
||||
"webserver": webserver
|
||||
"herald": herald,
|
||||
"constellation": constellation,
|
||||
}
|
||||
# If any interface is True, then the undefined ones should be False
|
||||
if any(interfaces[name] is True for name in interfaces):
|
||||
|
@ -79,36 +76,30 @@ def run(telegram: typing.Optional[bool],
|
|||
for name in interfaces:
|
||||
interfaces[name] = True
|
||||
|
||||
server_process: typing.Optional[multiprocessing.Process] = None
|
||||
# Start the network server
|
||||
if local_network_server:
|
||||
server_process = multiprocessing.Process(name="Network Server",
|
||||
target=rh.Server("0.0.0.0", local_network_server_port, network_password).run_blocking,
|
||||
herald_process: typing.Optional[multiprocessing.Process] = None
|
||||
# Start the Herald server
|
||||
if interfaces["herald"]:
|
||||
herald_config = r.herald.Config(name="<server>",
|
||||
address="127.0.0.1",
|
||||
port=herald_port,
|
||||
secret=get_secret("herald"),
|
||||
secure=False,
|
||||
path="/")
|
||||
herald_process = multiprocessing.Process(name="Herald",
|
||||
target=r.herald.Server(config=herald_config).run_blocking,
|
||||
daemon=True)
|
||||
server_process.start()
|
||||
network_address = f"ws://127.0.0.1:{local_network_server_port}/"
|
||||
|
||||
# Create a Royalnet configuration
|
||||
network_config: typing.Optional[rh.Config] = None
|
||||
if network_address is not None:
|
||||
network_config = rh.Config(network_address, network_password)
|
||||
|
||||
# Create a Alchemy configuration
|
||||
telegram_db_config: typing.Optional[r.alchemy.DatabaseConfig] = None
|
||||
discord_db_config: typing.Optional[r.alchemy.DatabaseConfig] = None
|
||||
if database is not None:
|
||||
telegram_db_config = r.alchemy.DatabaseConfig(database,
|
||||
r.packs.common.tables.User,
|
||||
r.packs.common.tables.Telegram,
|
||||
"tg_id")
|
||||
discord_db_config = r.alchemy.DatabaseConfig(database,
|
||||
r.packs.common.tables.User,
|
||||
r.packs.common.tables.Discord,
|
||||
"discord_id")
|
||||
herald_process.start()
|
||||
else:
|
||||
herald_config = r.herald.Config(name=...,
|
||||
address=remote_herald_address,
|
||||
port=herald_port,
|
||||
secret=get_secret("herald"),
|
||||
secure=False,
|
||||
path="/")
|
||||
|
||||
# Import command and star packs
|
||||
packs: typing.List[str] = list(packs)
|
||||
packs.append("royalnet.packs.common") # common pack is always imported
|
||||
packs: typing.List[str] = list(pack)
|
||||
packs.append("royalnet.backpack") # backpack is always imported
|
||||
enabled_commands = []
|
||||
enabled_page_stars = []
|
||||
enabled_exception_stars = []
|
||||
|
@ -132,62 +123,66 @@ def run(telegram: typing.Optional[bool],
|
|||
|
||||
telegram_process: typing.Optional[multiprocessing.Process] = None
|
||||
if interfaces["telegram"]:
|
||||
click.echo("\n@BotFather Commands String")
|
||||
for command in enabled_commands:
|
||||
click.echo(f"{command.name} - {command.description}")
|
||||
click.echo("")
|
||||
telegram_bot = r.interfaces.TelegramBot(network_config=network_config,
|
||||
database_config=telegram_db_config,
|
||||
sentry_dsn=sentry_dsn,
|
||||
commands=enabled_commands,
|
||||
secrets_name=secrets_name)
|
||||
telegram_process = multiprocessing.Process(name="Telegram Interface",
|
||||
target=telegram_bot.run_blocking,
|
||||
args=(verbose,),
|
||||
telegram_db_config = r.serf.AlchemyConfig(database_url=alchemy_url,
|
||||
master_table=r.backpack.tables.User,
|
||||
identity_table=r.backpack.tables.Telegram,
|
||||
identity_column="tg_id")
|
||||
telegram_serf_kwargs = {
|
||||
'alchemy_config': telegram_db_config,
|
||||
'commands': enabled_commands,
|
||||
'network_config': herald_config.copy(name="telegram"),
|
||||
'secrets_name': secrets_name
|
||||
}
|
||||
telegram_process = multiprocessing.Process(name="Telegram Serf",
|
||||
target=r.serf.telegram.TelegramSerf.run_process,
|
||||
kwargs=telegram_serf_kwargs,
|
||||
daemon=True)
|
||||
telegram_process.start()
|
||||
|
||||
discord_process: typing.Optional[multiprocessing.Process] = None
|
||||
if interfaces["discord"]:
|
||||
discord_bot = r.interfaces.DiscordBot(network_config=network_config,
|
||||
database_config=discord_db_config,
|
||||
sentry_dsn=sentry_dsn,
|
||||
commands=enabled_commands,
|
||||
secrets_name=secrets_name)
|
||||
discord_process = multiprocessing.Process(name="Discord Interface",
|
||||
target=discord_bot.run_blocking,
|
||||
args=(verbose,),
|
||||
discord_db_config = r.serf.AlchemyConfig(database_url=alchemy_url,
|
||||
master_table=r.backpack.tables.User,
|
||||
identity_table=r.backpack.tables.Discord,
|
||||
identity_column="discord_id")
|
||||
discord_serf_kwargs = {
|
||||
'alchemy_config': discord_db_config,
|
||||
'commands': enabled_commands,
|
||||
'network_config': herald_config.copy(name="discord"),
|
||||
'secrets_name': secrets_name
|
||||
}
|
||||
discord_process = multiprocessing.Process(name="Discord Serf",
|
||||
target=r.serf.discord.DiscordSerf.run_process,
|
||||
kwargs=discord_serf_kwargs,
|
||||
daemon=True)
|
||||
discord_process.start()
|
||||
|
||||
webserver_process: typing.Optional[multiprocessing.Process] = None
|
||||
if interfaces["webserver"]:
|
||||
# Common tables are always included
|
||||
constellation_tables = set(r.packs.common.available_tables)
|
||||
# Find the required tables
|
||||
for star in [*enabled_page_stars, *enabled_exception_stars]:
|
||||
constellation_tables = constellation_tables.union(star.tables)
|
||||
constellation_process: typing.Optional[multiprocessing.Process] = None
|
||||
if interfaces["constellation"]:
|
||||
# Create the Constellation
|
||||
constellation = r.web.Constellation(page_stars=enabled_page_stars,
|
||||
exc_stars=enabled_exception_stars,
|
||||
secrets_name=secrets_name,
|
||||
database_uri=database,
|
||||
tables=constellation_tables)
|
||||
webserver_process = multiprocessing.Process(name="Constellation Webserver",
|
||||
target=constellation.run_blocking,
|
||||
args=("0.0.0.0", webserver_port, verbose,),
|
||||
daemon=True)
|
||||
webserver_process.start()
|
||||
constellation_kwargs = {
|
||||
'address': "127.0.0.1",
|
||||
'port': constellation_port,
|
||||
'secrets_name': secrets_name,
|
||||
'database_uri': alchemy_url,
|
||||
'page_stars': enabled_page_stars,
|
||||
'exc_stars': enabled_exception_stars,
|
||||
}
|
||||
constellation_process = multiprocessing.Process(name="Constellation",
|
||||
target=r.constellation.Constellation.run_process,
|
||||
kwargs=constellation_kwargs,
|
||||
daemon=True)
|
||||
constellation_process.start()
|
||||
|
||||
click.echo("Royalnet processes have been started. You can force-quit by pressing Ctrl+C.")
|
||||
if server_process is not None:
|
||||
server_process.join()
|
||||
click.echo("Royalnet is now running! You can stop its execution by pressing Ctrl+C at any time.")
|
||||
if herald_process is not None:
|
||||
herald_process.join()
|
||||
if telegram_process is not None:
|
||||
telegram_process.join()
|
||||
if discord_process is not None:
|
||||
discord_process.join()
|
||||
if webserver_process is not None:
|
||||
webserver_process.join()
|
||||
if constellation_process is not None:
|
||||
constellation_process.join()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
3
royalnet/backpack/README.md
Normal file
3
royalnet/backpack/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# `backpack`
|
||||
|
||||
A Pack that is imported by default by all `royalnet` instances.
|
|
@ -1,4 +1,4 @@
|
|||
# This is a template Pack __init__. You can use this without changing anything in other packages too!
|
||||
"""A Pack that is imported by default by all :mod:`royalnet` instances."""
|
||||
|
||||
from . import commands, tables, stars
|
||||
from .commands import available_commands
|
|
@ -1,5 +1,5 @@
|
|||
import royalnet
|
||||
from royalnet.commands import *
|
||||
from royalnet.version import semantic
|
||||
|
||||
|
||||
class VersionCommand(Command):
|
||||
|
@ -8,7 +8,7 @@ class VersionCommand(Command):
|
|||
description: str = "Get the current Royalnet version."
|
||||
|
||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||
message = f"ℹ️ Royalnet {semantic}\n"
|
||||
message = f"ℹ️ Royalnet {royalnet.__version__}\n"
|
||||
if "69" in message:
|
||||
message += "(Nice.)"
|
||||
await data.reply(message)
|
|
@ -1,15 +1,18 @@
|
|||
import royalnet
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import *
|
||||
from royalnet.web import PageStar
|
||||
from royalnet.constellation import PageStar
|
||||
from ..tables import available_tables
|
||||
|
||||
|
||||
class ApiRoyalnetVersionStar(PageStar):
|
||||
path = "/api/royalnet/version"
|
||||
|
||||
tables = set(available_tables)
|
||||
|
||||
async def page(self, request: Request) -> JSONResponse:
|
||||
return JSONResponse({
|
||||
"version": {
|
||||
"semantic": royalnet.version.semantic
|
||||
"semantic": royalnet.__version__,
|
||||
}
|
||||
})
|
|
@ -34,7 +34,7 @@ class YtdlMp3:
|
|||
)
|
||||
self.mp3_filename = destination_filename
|
||||
|
||||
def delete_asap(self) -> None:
|
||||
async def delete_asap(self) -> None:
|
||||
"""Delete the mp3 file."""
|
||||
if self.is_converted:
|
||||
async with self.lock.exclusive():
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from asyncio import AbstractEventLoop
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from .errors import UnsupportedError
|
||||
from .commandinterface import CommandInterface
|
||||
|
@ -8,9 +9,10 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class CommandData:
|
||||
def __init__(self, interface: CommandInterface, session: Optional["Session"]):
|
||||
def __init__(self, interface: CommandInterface, session: Optional["Session"], loop: AbstractEventLoop):
|
||||
self._interface: CommandInterface = interface
|
||||
self._session: Optional["Session"] = session
|
||||
self.loop: AbstractEventLoop = loop
|
||||
|
||||
@property
|
||||
def session(self) -> "Session":
|
||||
|
|
|
@ -6,9 +6,9 @@ import keyring
|
|||
def run():
|
||||
click.echo("Welcome to the Royalnet configuration creator!")
|
||||
secrets_name = click.prompt("Desired secrets name", default="__default__")
|
||||
network = click.prompt("Network password", default="")
|
||||
network = click.prompt("Herald password", default="")
|
||||
if network:
|
||||
keyring.set_password(f"Royalnet/{secrets_name}", "network", network)
|
||||
keyring.set_password(f"Royalnet/{secrets_name}", "herald", network)
|
||||
telegram = click.prompt("Telegram Bot API token", default="")
|
||||
if telegram:
|
||||
keyring.set_password(f"Royalnet/{secrets_name}", "telegram", telegram)
|
||||
|
@ -21,9 +21,6 @@ def run():
|
|||
sentry = click.prompt("Sentry DSN", default="")
|
||||
if sentry:
|
||||
keyring.set_password(f"Royalnet/{secrets_name}", "sentry", sentry)
|
||||
leagueoflegends = click.prompt("League of Legends API Token", default="")
|
||||
if leagueoflegends:
|
||||
keyring.set_password(f"Royalnet/{secrets_name}", "leagueoflegends", leagueoflegends)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -2,7 +2,6 @@ import typing
|
|||
import logging
|
||||
import royalnet
|
||||
import keyring
|
||||
from royalnet.alchemy import Alchemy
|
||||
from .star import PageStar, ExceptionStar
|
||||
|
||||
try:
|
||||
|
@ -60,7 +59,7 @@ class Constellation:
|
|||
"""The :class:`Starlette` app."""
|
||||
|
||||
log.debug("Finding required Tables...")
|
||||
tables = set()
|
||||
tables = set(royalnet.backpack.available_tables)
|
||||
for SelectedPageStar in page_stars:
|
||||
tables = tables.union(SelectedPageStar.tables)
|
||||
for SelectedExcStar in exc_stars:
|
||||
|
@ -68,7 +67,7 @@ class Constellation:
|
|||
log.debug(f"Found Tables: {' '.join([table.__name__ for table in tables])}")
|
||||
|
||||
log.info(f"Creating Alchemy...")
|
||||
self.alchemy: Alchemy = Alchemy(database_uri=database_uri, tables=tables)
|
||||
self.alchemy: royalnet.alchemy.Alchemy = royalnet.alchemy.Alchemy(database_uri=database_uri, tables=tables)
|
||||
"""The :class:`Alchemy: of this Constellation."""
|
||||
|
||||
log.info("Registering PageStars...")
|
||||
|
@ -98,19 +97,34 @@ class Constellation:
|
|||
username: the name of the secret that should be retrieved."""
|
||||
return keyring.get_password(f"Royalnet/{self.secrets_name}", username)
|
||||
|
||||
def run_blocking(self, address: str, port: int):
|
||||
"""Blockingly run the Constellation.
|
||||
@classmethod
|
||||
def run_process(cls,
|
||||
address: str,
|
||||
port: int,
|
||||
secrets_name: str,
|
||||
database_uri: str,
|
||||
page_stars: typing.List[typing.Type[PageStar]] = None,
|
||||
exc_stars: typing.List[typing.Type[ExceptionStar]] = None,
|
||||
*,
|
||||
debug: bool = __debug__,):
|
||||
"""Blockingly create and run the Constellation.
|
||||
|
||||
This should be used as the target of a :class:`multiprocessing.Process`.
|
||||
|
||||
Args:
|
||||
address: The IP address this Constellation should bind to.
|
||||
port: The port this Constellation should listen for requests on."""
|
||||
constellation = cls(secrets_name=secrets_name,
|
||||
database_uri=database_uri,
|
||||
page_stars=page_stars,
|
||||
exc_stars=exc_stars,
|
||||
debug=debug)
|
||||
|
||||
# Initialize Sentry on the process
|
||||
if sentry_sdk is None:
|
||||
log.info("Sentry: not installed")
|
||||
else:
|
||||
sentry_dsn = self.get_secret("sentry")
|
||||
sentry_dsn = constellation.get_secret("sentry")
|
||||
if not sentry_dsn:
|
||||
log.info("Sentry: disabled")
|
||||
else:
|
||||
|
@ -128,11 +142,11 @@ class Constellation:
|
|||
log.info(f"Sentry: enabled (Royalnet {release})")
|
||||
# Run the server
|
||||
log.info(f"Running Constellation on {address}:{port}...")
|
||||
self.running = True
|
||||
constellation.running = True
|
||||
try:
|
||||
uvicorn.run(self.starlette, host=address, port=port)
|
||||
uvicorn.run(constellation.starlette, host=address, port=port)
|
||||
finally:
|
||||
self.running = False
|
||||
constellation.running = False
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__}: {'running' if self.running else 'inactive'}>"
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from typing import Optional
|
||||
|
||||
|
||||
class Config:
|
||||
def __init__(self,
|
||||
name: str,
|
||||
|
@ -30,5 +33,20 @@ class Config:
|
|||
def url(self):
|
||||
return f"ws{'s' if self.secure else ''}://{self.address}:{self.port}{self.path}"
|
||||
|
||||
def copy(self,
|
||||
name: Optional[str] = None,
|
||||
address: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
secret: Optional[str] = None,
|
||||
secure: Optional[bool] = None,
|
||||
path: Optional[str] = None):
|
||||
"""Create an exact copy of this configuration, but with different parameters."""
|
||||
return self.__class__(name=name if name else self.name,
|
||||
address=address if address else self.address,
|
||||
port=port if port else self.port,
|
||||
secret=secret if secret else self.secret,
|
||||
secure=secure if secure else self.secure,
|
||||
path=path if path else self.path)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<HeraldConfig for {self.url}>"
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
from .serf import Serf
|
||||
from .alchemyconfig import AlchemyConfig
|
||||
from . import telegram, discord
|
||||
|
||||
__all__ = [
|
||||
"Serf",
|
||||
"AlchemyConfig"
|
||||
"AlchemyConfig",
|
||||
"telegram",
|
||||
"discord",
|
||||
]
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
from typing import Type, TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.schema import Table
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
class AlchemyConfig:
|
||||
"""A helper class to configure :class:`Alchemy` in a :class:`Serf`."""
|
||||
def __init__(self,
|
||||
database_url: str,
|
||||
master_table: Type[Table],
|
||||
identity_table: Type[Table],
|
||||
master_table: type,
|
||||
identity_table: type,
|
||||
identity_column: str):
|
||||
self.database_url: str = database_url
|
||||
self.master_table: Type[Table] = master_table
|
||||
self.identity_table: Type[Table] = identity_table
|
||||
self.master_table: type = master_table
|
||||
self.identity_table: type = identity_table
|
||||
self.identity_column: str = identity_column
|
||||
|
||||
def __repr__(self):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import asyncio
|
||||
import logging
|
||||
from typing import Type, Optional, List, Union
|
||||
from royalnet.commands import Command, CommandInterface, CommandData, CommandArgs, CommandError, InvalidInputError, \
|
||||
|
@ -63,8 +64,12 @@ class DiscordSerf(Serf):
|
|||
def data_factory(self) -> Type[CommandData]:
|
||||
# noinspection PyMethodParameters,PyAbstractClass
|
||||
class DiscordData(CommandData):
|
||||
def __init__(data, interface: CommandInterface, session, message: discord.Message):
|
||||
super().__init__(interface=interface, session=session)
|
||||
def __init__(data,
|
||||
interface: CommandInterface,
|
||||
session,
|
||||
loop: asyncio.AbstractEventLoop,
|
||||
message: discord.Message):
|
||||
super().__init__(interface=interface, session=session, loop=loop)
|
||||
data.message = message
|
||||
|
||||
async def reply(data, text: str):
|
||||
|
@ -118,7 +123,7 @@ class DiscordSerf(Serf):
|
|||
else:
|
||||
session = None
|
||||
# Prepare data
|
||||
data = self.Data(interface=command.interface, session=session, message=message)
|
||||
data = self.Data(interface=command.interface, session=session, loop=self.loop, message=message)
|
||||
try:
|
||||
# Run the command
|
||||
await command.run(CommandArgs(parameters), data)
|
||||
|
@ -196,6 +201,7 @@ class DiscordSerf(Serf):
|
|||
return DiscordClient
|
||||
|
||||
async def run(self):
|
||||
await super().run()
|
||||
token = self.get_secret("discord")
|
||||
await self.client.login(token)
|
||||
await self.client.connect()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import logging
|
||||
from asyncio import Task, AbstractEventLoop
|
||||
from asyncio import Task, AbstractEventLoop, get_event_loop
|
||||
from typing import Type, Optional, Awaitable, Dict, List, Any, Callable, Union, Set
|
||||
from keyring import get_password
|
||||
from sqlalchemy.schema import Table
|
||||
|
@ -130,10 +130,10 @@ class Serf:
|
|||
self.alchemy = Alchemy(alchemy_config.database_url, tables)
|
||||
self._master_table = self.alchemy.get(alchemy_config.master_table)
|
||||
self._identity_table = self.alchemy.get(alchemy_config.identity_table)
|
||||
# FIXME: this MAY break
|
||||
self._identity_column = self._identity_table.__getattribute__(alchemy_config.identity_column)
|
||||
# self._identity_column = self._identity_table.__getattribute__(self._identity_table,
|
||||
# alchemy_config.identity_column)
|
||||
# This is fine, as Pycharm doesn't know that identity_table is a class and not an object
|
||||
# noinspection PyArgumentList
|
||||
self._identity_column = self._identity_table.__getattribute__(self._identity_table,
|
||||
alchemy_config.identity_column)
|
||||
|
||||
@property
|
||||
def _identity_chain(self) -> tuple:
|
||||
|
@ -147,7 +147,6 @@ class Serf:
|
|||
class GenericInterface(CommandInterface):
|
||||
alchemy: Alchemy = self.alchemy
|
||||
bot: "Serf" = self
|
||||
loop: AbstractEventLoop = self.loop
|
||||
|
||||
def register_herald_action(ci,
|
||||
event_name: str,
|
||||
|
@ -232,10 +231,9 @@ class Serf:
|
|||
def init_network(self, config: HeraldConfig):
|
||||
"""Create a :py:class:`Link`, and run it as a :py:class:`asyncio.Task`."""
|
||||
log.debug(f"Initializing herald...")
|
||||
self.herald: Link = Link(config, self._network_handler)
|
||||
self.herald_task = self.loop.create_task(self.herald.run())
|
||||
self.herald: Link = Link(config, self.network_handler)
|
||||
|
||||
async def _network_handler(self, message: Union[Request, Broadcast]) -> Response:
|
||||
async def network_handler(self, message: Union[Request, Broadcast]) -> Response:
|
||||
try:
|
||||
network_handler = self.herald_handlers[message.handler]
|
||||
except KeyError:
|
||||
|
@ -288,19 +286,24 @@ class Serf:
|
|||
|
||||
async def run(self):
|
||||
"""A coroutine that starts the event loop and handles command calls."""
|
||||
raise NotImplementedError()
|
||||
self.herald_task = self.loop.create_task(self.herald.run())
|
||||
# OVERRIDE THIS METHOD!
|
||||
|
||||
def run_blocking(self):
|
||||
"""Blockingly run the Serf.
|
||||
@classmethod
|
||||
def run_process(cls, *args, **kwargs):
|
||||
"""Blockingly create and run the Serf.
|
||||
|
||||
This should be used as the target of a :class:`multiprocessing.Process`."""
|
||||
serf = cls(*args, **kwargs)
|
||||
|
||||
if sentry_sdk is None:
|
||||
log.info("Sentry: not installed")
|
||||
else:
|
||||
sentry_dsn = self.get_secret("sentry")
|
||||
sentry_dsn = serf.get_secret("sentry")
|
||||
if sentry_dsn is None:
|
||||
log.info("Sentry: disabled")
|
||||
else:
|
||||
self.init_sentry(sentry_dsn)
|
||||
serf.init_sentry(sentry_dsn)
|
||||
|
||||
self.loop.run_until_complete(self.run())
|
||||
serf.loop = get_event_loop()
|
||||
serf.loop.run_until_complete(serf.run())
|
||||
|
|
|
@ -99,8 +99,12 @@ class TelegramSerf(Serf):
|
|||
def data_factory(self) -> Type[CommandData]:
|
||||
# noinspection PyMethodParameters
|
||||
class TelegramData(CommandData):
|
||||
def __init__(data, interface: CommandInterface, session, update: telegram.Update):
|
||||
super().__init__(interface=interface, session=session)
|
||||
def __init__(data,
|
||||
interface: CommandInterface,
|
||||
session,
|
||||
loop: asyncio.AbstractEventLoop,
|
||||
update: telegram.Update):
|
||||
super().__init__(interface=interface, session=session, loop=loop)
|
||||
data.update = update
|
||||
|
||||
async def reply(data, text: str):
|
||||
|
@ -192,7 +196,7 @@ class TelegramSerf(Serf):
|
|||
session = None
|
||||
try:
|
||||
# Create the command data
|
||||
data = self.Data(interface=command.interface, session=session, update=update)
|
||||
data = self.Data(interface=command.interface, session=session, loop=self.loop, update=update)
|
||||
try:
|
||||
# Run the command
|
||||
await command.run(CommandArgs(parameters), data)
|
||||
|
@ -240,6 +244,7 @@ class TelegramSerf(Serf):
|
|||
pass
|
||||
|
||||
async def run(self):
|
||||
await super().run()
|
||||
while True:
|
||||
# Get the latest 100 updates
|
||||
last_updates: List[telegram.Update] = await self.api_call(self.client.get_updates,
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
"""Miscellaneous useful functions and classes."""
|
||||
|
||||
from .asyncify import asyncify
|
||||
from .escaping import telegram_escape, discord_escape
|
||||
from .safeformat import safeformat
|
||||
from .classdictjanitor import cdj
|
||||
from .sleep_until import sleep_until
|
||||
from .formatters import andformat, plusformat, underscorize, ytdldateformat, numberemojiformat, splitstring, ordinalformat
|
||||
from .formatters import andformat, underscorize, ytdldateformat, numberemojiformat, splitstring, ordinalformat
|
||||
from .urluuid import to_urluuid, from_urluuid
|
||||
from .multilock import MultiLock
|
||||
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
import typing
|
||||
import uvicorn
|
||||
import logging
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
|
||||
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
|
||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||
import royalnet
|
||||
import keyring
|
||||
from starlette.applications import Starlette
|
||||
from .star import PageStar, ExceptionStar
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Constellation:
|
||||
def __init__(self,
|
||||
secrets_name: str,
|
||||
database_uri: str,
|
||||
tables: set,
|
||||
page_stars: typing.List[typing.Type[PageStar]] = None,
|
||||
exc_stars: typing.List[typing.Type[ExceptionStar]] = None,
|
||||
*,
|
||||
debug: bool = __debug__,):
|
||||
if page_stars is None:
|
||||
page_stars = []
|
||||
|
||||
if exc_stars is None:
|
||||
exc_stars = []
|
||||
|
||||
self.secrets_name: str = secrets_name
|
||||
|
||||
log.info("Creating starlette app...")
|
||||
self.starlette = Starlette(debug=debug)
|
||||
|
||||
log.info(f"Creating alchemy with tables: {' '.join([table.__name__ for table in tables])}")
|
||||
self.alchemy: royalnet.alchemy.Alchemy = royalnet.alchemy.Alchemy(database_uri=database_uri, tables=tables)
|
||||
|
||||
log.info("Registering page_stars...")
|
||||
for SelectedPageStar in page_stars:
|
||||
try:
|
||||
page_star_instance = SelectedPageStar(constellation=self)
|
||||
except Exception as e:
|
||||
log.error(f"{e.__class__.__qualname__} during the registration of {SelectedPageStar.__qualname__}")
|
||||
sentry_sdk.capture_exception(e)
|
||||
continue
|
||||
log.info(f"Registering: {page_star_instance.path} -> {page_star_instance.__class__.__name__}")
|
||||
self.starlette.add_route(page_star_instance.path, page_star_instance.page, page_star_instance.methods)
|
||||
|
||||
log.info("Registering exc_stars...")
|
||||
for SelectedExcStar in exc_stars:
|
||||
try:
|
||||
exc_star_instance = SelectedExcStar(constellation=self)
|
||||
except Exception as e:
|
||||
log.error(f"{e.__class__.__qualname__} during the registration of {SelectedExcStar.__qualname__}")
|
||||
sentry_sdk.capture_exception(e)
|
||||
continue
|
||||
log.info(f"Registering: {exc_star_instance.error} -> {exc_star_instance.__class__.__name__}")
|
||||
self.starlette.add_exception_handler(exc_star_instance.error, exc_star_instance.page)
|
||||
|
||||
def _init_sentry(self):
|
||||
sentry_dsn = self.get_secret("sentry")
|
||||
if sentry_dsn:
|
||||
# noinspection PyUnreachableCode
|
||||
if __debug__:
|
||||
release = "DEV"
|
||||
else:
|
||||
release = royalnet.version.semantic
|
||||
log.info(f"Sentry: enabled (Royalnet {release})")
|
||||
self.sentry = sentry_sdk.init(sentry_dsn,
|
||||
integrations=[AioHttpIntegration(),
|
||||
SqlalchemyIntegration(),
|
||||
LoggingIntegration(event_level=None)],
|
||||
release=release)
|
||||
else:
|
||||
log.info("Sentry: disabled")
|
||||
|
||||
def get_secret(self, username: str):
|
||||
return keyring.get_password(f"Royalnet/{self.secrets_name}", username)
|
||||
|
||||
def set_secret(self, username: str, password: str):
|
||||
return keyring.set_password(f"Royalnet/{self.secrets_name}", username, password)
|
||||
|
||||
def run_blocking(self, address: str, port: int, verbose: bool):
|
||||
if verbose:
|
||||
core_logger = logging.root
|
||||
core_logger.setLevel(logging.DEBUG)
|
||||
stream_handler = logging.StreamHandler()
|
||||
stream_handler.formatter = logging.Formatter("{asctime}\t{name}\t{levelname}\t{message}", style="{")
|
||||
core_logger.addHandler(stream_handler)
|
||||
core_logger.debug("Logging setup complete.")
|
||||
self._init_sentry()
|
||||
log.info(f"Running constellation server on {address}:{port}...")
|
||||
uvicorn.run(self.starlette, host=address, port=port)
|
|
@ -1,37 +0,0 @@
|
|||
import typing
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
if typing.TYPE_CHECKING:
|
||||
from .constellation import Constellation
|
||||
|
||||
|
||||
class Star:
|
||||
tables: set = {}
|
||||
|
||||
def __init__(self, constellation: "Constellation"):
|
||||
self.constellation: "Constellation" = constellation
|
||||
|
||||
async def page(self, request: Request) -> Response:
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def alchemy(self):
|
||||
return self.constellation.alchemy
|
||||
|
||||
@property
|
||||
def Session(self):
|
||||
return self.constellation.alchemy._Session
|
||||
|
||||
@property
|
||||
def session_acm(self):
|
||||
return self.constellation.alchemy.session_acm
|
||||
|
||||
|
||||
class PageStar(Star):
|
||||
path: str = NotImplemented
|
||||
|
||||
methods: typing.List[str] = ["GET"]
|
||||
|
||||
|
||||
class ExceptionStar(Star):
|
||||
error: typing.Union[typing.Type[Exception], int]
|
Loading…
Reference in a new issue