mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 19:44:20 +00:00
A lot of progress on a lot of things
This commit is contained in:
parent
c3e77fa5e6
commit
353b94f1ba
13 changed files with 164 additions and 238 deletions
|
@ -1,19 +1 @@
|
|||
"""A Pack that is imported by default by all Royalnet instances."""
|
||||
|
||||
from . import commands, tables, stars, events
|
||||
from .commands import available_commands
|
||||
from .tables import available_tables
|
||||
from .stars import available_page_stars, available_exception_stars
|
||||
from .events import available_events
|
||||
|
||||
__all__ = [
|
||||
"commands",
|
||||
"tables",
|
||||
"stars",
|
||||
"events",
|
||||
"available_commands",
|
||||
"available_tables",
|
||||
"available_page_stars",
|
||||
"available_exception_stars",
|
||||
"available_events",
|
||||
]
|
||||
|
|
|
@ -7,10 +7,5 @@ available_page_stars = [
|
|||
ApiRoyalnetVersionStar,
|
||||
]
|
||||
|
||||
# Enter the ExceptionStars of your Pack here!
|
||||
available_exception_stars = [
|
||||
|
||||
]
|
||||
|
||||
# Don't change this, it should automatically generate __all__
|
||||
__all__ = [star.__name__ for star in [*available_page_stars, *available_exception_stars]]
|
||||
__all__ = [star.__name__ for star in available_page_stars]
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
import royalnet
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import *
|
||||
from royalnet.constellation import PageStar
|
||||
import royalnet.version as rv
|
||||
from royalnet.constellation import ApiStar
|
||||
|
||||
|
||||
class ApiRoyalnetVersionStar(PageStar):
|
||||
class ApiRoyalnetVersionStar(ApiStar):
|
||||
path = "/api/royalnet/version"
|
||||
|
||||
async def page(self, request: Request) -> JSONResponse:
|
||||
return JSONResponse({
|
||||
"version": {
|
||||
"semantic": royalnet.__version__,
|
||||
}
|
||||
})
|
||||
async def api(self, data: dict) -> dict:
|
||||
return {
|
||||
"semantic": rv.semantic
|
||||
}
|
||||
|
|
28
royalnet/backpack/tables/tokens.py
Normal file
28
royalnet/backpack/tables/tokens.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import datetime
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy.orm import *
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
|
||||
|
||||
class Token:
|
||||
__tablename__ = "tokens"
|
||||
|
||||
@declared_attr
|
||||
def token(self):
|
||||
return Column(String, primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def user_id(self):
|
||||
return Column(Integer, ForeignKey("users.uid"), nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def user(self):
|
||||
return relationship("User", backref="tokens")
|
||||
|
||||
@declared_attr
|
||||
def expiration(self):
|
||||
return Column(DateTime, nullable=False)
|
||||
|
||||
@property
|
||||
def expired(self):
|
||||
return datetime.datetime.now() > self.expiration
|
|
@ -15,13 +15,17 @@ You can install them with: ::
|
|||
"""
|
||||
|
||||
from .constellation import Constellation
|
||||
from .star import Star, PageStar, ExceptionStar
|
||||
from .shoot import shoot
|
||||
from .star import Star
|
||||
from .pagestar import PageStar
|
||||
from .apistar import ApiStar
|
||||
from .jsonapi import api_response, api_success, api_error
|
||||
|
||||
__all__ = [
|
||||
"Constellation",
|
||||
"Star",
|
||||
"PageStar",
|
||||
"ExceptionStar",
|
||||
"shoot",
|
||||
"ApiStar",
|
||||
"api_response",
|
||||
"api_success",
|
||||
"api_error",
|
||||
]
|
||||
|
|
26
royalnet/constellation/apistar.py
Normal file
26
royalnet/constellation/apistar.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from typing import *
|
||||
from json import JSONDecodeError
|
||||
from abc import *
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse
|
||||
from .pagestar import PageStar
|
||||
from .jsonapi import api_error, api_success
|
||||
|
||||
|
||||
class ApiStar(PageStar, ABC):
|
||||
async def page(self, request: Request) -> JSONResponse:
|
||||
if request.query_params:
|
||||
data = request.query_params
|
||||
else:
|
||||
try:
|
||||
data = await request.json()
|
||||
except JSONDecodeError:
|
||||
data = {}
|
||||
try:
|
||||
response = await self.api(data)
|
||||
except Exception as e:
|
||||
return api_error(e)
|
||||
return api_success(response)
|
||||
|
||||
async def api(self, data: dict) -> dict:
|
||||
raise NotImplementedError()
|
|
@ -8,7 +8,7 @@ import royalnet.alchemy as ra
|
|||
import royalnet.herald as rh
|
||||
import royalnet.utils as ru
|
||||
import royalnet.commands as rc
|
||||
from .star import PageStar, ExceptionStar
|
||||
from .pagestar import PageStar
|
||||
from ..utils import init_logging
|
||||
|
||||
|
||||
|
@ -42,7 +42,12 @@ class Constellation:
|
|||
for pack_name in pack_names:
|
||||
log.debug(f"Importing pack: {pack_name}")
|
||||
try:
|
||||
packs[pack_name] = importlib.import_module(pack_name)
|
||||
packs[pack_name] = {
|
||||
"commands": importlib.import_module(f"{pack_name}.commands"),
|
||||
"events": importlib.import_module(f"{pack_name}.events"),
|
||||
"stars": importlib.import_module(f"{pack_name}.stars"),
|
||||
"tables": importlib.import_module(f"{pack_name}.tables"),
|
||||
}
|
||||
except ImportError as e:
|
||||
log.error(f"Error during the import of {pack_name}: {e}")
|
||||
log.info(f"Packs: {len(packs)} imported")
|
||||
|
@ -60,7 +65,7 @@ class Constellation:
|
|||
tables = set()
|
||||
for pack in packs.values():
|
||||
try:
|
||||
tables = tables.union(pack.available_tables)
|
||||
tables = tables.union(pack["tables"].available_tables)
|
||||
except AttributeError:
|
||||
log.warning(f"Pack `{pack}` does not have the `available_tables` attribute.")
|
||||
continue
|
||||
|
@ -99,7 +104,7 @@ class Constellation:
|
|||
pack = packs[pack_name]
|
||||
pack_cfg = packs_cfg.get(pack_name, {})
|
||||
try:
|
||||
events = pack.available_events
|
||||
events = pack["events"].available_events
|
||||
except AttributeError:
|
||||
log.warning(f"Pack `{pack}` does not have the `available_events` attribute.")
|
||||
else:
|
||||
|
@ -118,17 +123,11 @@ class Constellation:
|
|||
pack = packs[pack_name]
|
||||
pack_cfg = packs_cfg.get(pack_name, {})
|
||||
try:
|
||||
page_stars = pack.available_page_stars
|
||||
page_stars = pack["stars"].available_page_stars
|
||||
except AttributeError:
|
||||
log.warning(f"Pack `{pack}` does not have the `available_page_stars` attribute.")
|
||||
else:
|
||||
self.register_page_stars(page_stars, pack_cfg)
|
||||
try:
|
||||
exc_stars = pack.available_exception_stars
|
||||
except AttributeError:
|
||||
log.warning(f"Pack `{pack}` does not have the `available_exception_stars` attribute.")
|
||||
else:
|
||||
self.register_exc_stars(exc_stars, pack_cfg)
|
||||
log.info(f"PageStars: {len(self.starlette.routes)} stars")
|
||||
log.info(f"ExceptionStars: {len(self.starlette.exception_handlers)} stars")
|
||||
|
||||
|
@ -259,14 +258,6 @@ class Constellation:
|
|||
|
||||
return page_star.path, f, page_star.methods
|
||||
|
||||
def _exc_star_wrapper(self, exc_star: ExceptionStar):
|
||||
async def f(request):
|
||||
self._first_page_check()
|
||||
log.info(f"Running {exc_star}")
|
||||
return await exc_star.page(request)
|
||||
|
||||
return exc_star.error, f
|
||||
|
||||
def register_page_stars(self, page_stars: List[Type[PageStar]], pack_cfg: Dict[str, Any]):
|
||||
for SelectedPageStar in page_stars:
|
||||
log.debug(f"Registering: {SelectedPageStar.path} -> {SelectedPageStar.__qualname__}")
|
||||
|
@ -279,18 +270,6 @@ class Constellation:
|
|||
continue
|
||||
self.starlette.add_route(*self._page_star_wrapper(page_star_instance))
|
||||
|
||||
def register_exc_stars(self, exc_stars: List[Type[ExceptionStar]], pack_cfg: Dict[str, Any]):
|
||||
for SelectedExcStar in exc_stars:
|
||||
log.debug(f"Registering: {SelectedExcStar.error} -> {SelectedExcStar.__qualname__}")
|
||||
try:
|
||||
exc_star_instance = SelectedExcStar(interface=self.Interface(pack_cfg))
|
||||
except Exception as e:
|
||||
log.error(f"Skipping: "
|
||||
f"{SelectedExcStar.__qualname__} - {e.__class__.__qualname__} in the initialization.")
|
||||
ru.sentry_exc(e)
|
||||
continue
|
||||
self.starlette.add_exception_handler(*self._exc_star_wrapper(exc_star_instance))
|
||||
|
||||
def run_blocking(self):
|
||||
log.info(f"Running Constellation on https://{self.address}:{self.port}/...")
|
||||
loop: aio.AbstractEventLoop = aio.get_event_loop()
|
||||
|
|
32
royalnet/constellation/jsonapi.py
Normal file
32
royalnet/constellation/jsonapi.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from typing import *
|
||||
try:
|
||||
from starlette.responses import JSONResponse
|
||||
except ImportError:
|
||||
JSONResponse = None
|
||||
|
||||
|
||||
def api_response(data: dict, code: int, headers: dict = None) -> JSONResponse:
|
||||
if headers is None:
|
||||
headers = {}
|
||||
full_headers = {
|
||||
**headers,
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
}
|
||||
return JSONResponse(data, status_code=code, headers=full_headers)
|
||||
|
||||
|
||||
def api_success(data: dict) -> JSONResponse:
|
||||
result = {
|
||||
"success": True,
|
||||
"data": data
|
||||
}
|
||||
return api_response(result, code=200)
|
||||
|
||||
|
||||
def api_error(error: Exception, code: int = 500) -> JSONResponse:
|
||||
result = {
|
||||
"success": False,
|
||||
"error_type": error.__class__.__qualname__,
|
||||
"error_args": list(error.args)
|
||||
}
|
||||
return api_response(result, code=code)
|
34
royalnet/constellation/pagestar.py
Normal file
34
royalnet/constellation/pagestar.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from typing import *
|
||||
from .star import Star
|
||||
|
||||
|
||||
class PageStar(Star):
|
||||
"""A PageStar is a class representing a single route of the website (for example, ``/api/user/get``).
|
||||
|
||||
To create a new website route you should create a new class inheriting from this class with a function overriding
|
||||
:meth:`.page`, :attr:`.path` and optionally :attr:`.methods`."""
|
||||
|
||||
path: str = NotImplemented
|
||||
"""The route of the star.
|
||||
|
||||
Example:
|
||||
::
|
||||
|
||||
path: str = '/api/user/get'
|
||||
|
||||
"""
|
||||
|
||||
methods: List[str] = ["GET"]
|
||||
"""The HTTP methods supported by the Star, in form of a list.
|
||||
|
||||
By default, a Star only supports the ``GET`` method, but more can be added.
|
||||
|
||||
Example:
|
||||
::
|
||||
|
||||
methods: List[str] = ["GET", "POST", "PUT", "DELETE"]
|
||||
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__}: {self.path}>"
|
|
@ -1,13 +0,0 @@
|
|||
try:
|
||||
from starlette.responses import JSONResponse
|
||||
except ImportError:
|
||||
JSONResponse = None
|
||||
|
||||
|
||||
def shoot(code: int, description: str) -> JSONResponse:
|
||||
"""Create a error :class:`~starlette.response.JSONResponse` with the passed error code and description."""
|
||||
if JSONResponse is None:
|
||||
raise ImportError("'constellation' extra is not installed")
|
||||
return JSONResponse({
|
||||
"error": description
|
||||
}, status_code=code)
|
|
@ -48,63 +48,3 @@ class Star:
|
|||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__}>"
|
||||
|
||||
|
||||
class PageStar(Star):
|
||||
"""A PageStar is a class representing a single route of the website (for example, ``/api/user/get``).
|
||||
|
||||
To create a new website route you should create a new class inheriting from this class with a function overriding
|
||||
:meth:`.page` and changing the values of :attr:`.path` and optionally :attr:`.methods`."""
|
||||
path: str = NotImplemented
|
||||
"""The route of the star.
|
||||
|
||||
Example:
|
||||
::
|
||||
|
||||
path: str = '/api/user/get'
|
||||
|
||||
"""
|
||||
|
||||
methods: List[str] = ["GET"]
|
||||
"""The HTTP methods supported by the Star, in form of a list.
|
||||
|
||||
By default, a Star only supports the ``GET`` method, but more can be added.
|
||||
|
||||
Example:
|
||||
::
|
||||
|
||||
methods: List[str] = ["GET", "POST", "PUT", "DELETE"]
|
||||
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__}: {self.path}>"
|
||||
|
||||
|
||||
class ExceptionStar(Star):
|
||||
"""An ExceptionStar is a class that handles an :class:`Exception` raised by another star by returning a different
|
||||
response than the one originally intended.
|
||||
|
||||
The handled exception type is specified in the :attr:`.error`.
|
||||
|
||||
It can also handle standard webserver errors, such as ``404 Not Found``:
|
||||
to handle them, set :attr:`.error` to an :class:`int` of the corresponding error code.
|
||||
|
||||
To create a new exception handler you should create a new class inheriting from this class with a function
|
||||
overriding :meth:`.page` and changing the value of :attr:`.error`."""
|
||||
error: Union[Type[Exception], int]
|
||||
"""The error that should be handled by this star. It should be either a subclass of :exc:`Exception`,
|
||||
or the :class:`int` of an HTTP error code.
|
||||
|
||||
Examples:
|
||||
::
|
||||
|
||||
error: int = 404
|
||||
|
||||
::
|
||||
|
||||
error: Type[Exception] = ValueError
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__qualname__}: handles {self.error}>"
|
||||
|
|
|
@ -19,7 +19,12 @@ def run(config_filename, file_format):
|
|||
packs = {}
|
||||
for pack_name in pack_names:
|
||||
try:
|
||||
packs[pack_name] = importlib.import_module(pack_name)
|
||||
packs[pack_name] = {
|
||||
"commands": importlib.import_module(f"{pack_name}.commands"),
|
||||
"events": importlib.import_module(f"{pack_name}.events"),
|
||||
"stars": importlib.import_module(f"{pack_name}.stars"),
|
||||
"tables": importlib.import_module(f"{pack_name}.tables"),
|
||||
}
|
||||
except ImportError as e:
|
||||
p(f"Skipping `{pack_name}`: {e}", err=True)
|
||||
continue
|
||||
|
@ -30,7 +35,7 @@ def run(config_filename, file_format):
|
|||
lines = []
|
||||
|
||||
try:
|
||||
commands = pack.available_commands
|
||||
commands = pack["commands"].available_commands
|
||||
except AttributeError:
|
||||
p(f"Pack `{pack}` does not have the `available_commands` attribute.", err=True)
|
||||
continue
|
||||
|
@ -41,93 +46,6 @@ def run(config_filename, file_format):
|
|||
for line in lines:
|
||||
p(line)
|
||||
|
||||
elif file_format == "markdown":
|
||||
p("<!--This documentation was autogenerated with `python -m royalnet.generate -f markdown`.-->")
|
||||
p("")
|
||||
for pack_name in packs:
|
||||
pack = packs[pack_name]
|
||||
p(f"# `{pack_name}`")
|
||||
p("")
|
||||
if pack.__doc__:
|
||||
p(f"{pack.__doc__}")
|
||||
p("")
|
||||
|
||||
try:
|
||||
commands = pack.available_commands
|
||||
except AttributeError:
|
||||
p(f"Pack `{pack}` does not have the `available_commands` attribute.", err=True)
|
||||
else:
|
||||
p(f"## Commands")
|
||||
p("")
|
||||
for command in commands:
|
||||
p(f"### `{command.name}`")
|
||||
p("")
|
||||
p(f"{command.description}")
|
||||
p("")
|
||||
if command.__doc__:
|
||||
p(f"{command.__doc__}")
|
||||
p("")
|
||||
if len(command.aliases) > 0:
|
||||
p(f"> Aliases: {''.join(['`' + alias + '` ' for alias in command.aliases])}")
|
||||
p("")
|
||||
|
||||
try:
|
||||
events = pack.available_events
|
||||
except AttributeError:
|
||||
p(f"Pack `{pack}` does not have the `available_events` attribute.", err=True)
|
||||
else:
|
||||
p(f"## Events")
|
||||
p("")
|
||||
for event in events:
|
||||
p(f"### `{event.name}`")
|
||||
p("")
|
||||
if event.__doc__:
|
||||
p(f"{event.__doc__}")
|
||||
p("")
|
||||
|
||||
try:
|
||||
page_stars = pack.available_page_stars
|
||||
except AttributeError:
|
||||
p(f"Pack `{pack}` does not have the `available_page_stars` attribute.", err=True)
|
||||
else:
|
||||
p(f"## Page Stars")
|
||||
p("")
|
||||
for page_star in page_stars:
|
||||
p(f"### `{page_star.path}`")
|
||||
p("")
|
||||
if page_star.__doc__:
|
||||
p(f"{page_star.__doc__}")
|
||||
p("")
|
||||
|
||||
try:
|
||||
exc_stars = pack.available_exception_stars
|
||||
except AttributeError:
|
||||
p(f"Pack `{pack}` does not have the `available_exception_stars` attribute.", err=True)
|
||||
else:
|
||||
p(f"## Exception Stars")
|
||||
p("")
|
||||
for exc_star in exc_stars:
|
||||
p(f"### `{exc_star.error}`")
|
||||
p("")
|
||||
if exc_star.__doc__:
|
||||
p(f"{exc_star.__doc__}")
|
||||
p("")
|
||||
|
||||
try:
|
||||
tables = pack.available_tables
|
||||
except AttributeError:
|
||||
p(f"Pack `{pack}` does not have the `available_tables` attribute.", err=True)
|
||||
else:
|
||||
p(f"## Tables")
|
||||
p("")
|
||||
for table in tables:
|
||||
p(f"### `{table.__tablename__}`")
|
||||
p("")
|
||||
# TODO: list columns
|
||||
if table.__doc__:
|
||||
p(f"{table.__doc__}")
|
||||
p("")
|
||||
|
||||
else:
|
||||
raise click.ClickException("Unknown format")
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from sqlalchemy.schema import Table
|
|||
from royalnet.commands import *
|
||||
import royalnet.utils as ru
|
||||
import royalnet.alchemy as ra
|
||||
import royalnet.backpack as rb
|
||||
import royalnet.backpack.tables as rbt
|
||||
import royalnet.herald as rh
|
||||
import traceback
|
||||
|
||||
|
@ -20,7 +20,7 @@ class Serf:
|
|||
Discord)."""
|
||||
interface_name = NotImplemented
|
||||
|
||||
_master_table: type = rb.tables.User
|
||||
_master_table: type = rbt.User
|
||||
_identity_table: type = NotImplemented
|
||||
_identity_column: str = NotImplemented
|
||||
|
||||
|
@ -39,7 +39,12 @@ class Serf:
|
|||
for pack_name in pack_names:
|
||||
log.debug(f"Importing pack: {pack_name}")
|
||||
try:
|
||||
packs[pack_name] = importlib.import_module(pack_name)
|
||||
packs[pack_name] = {
|
||||
"commands": importlib.import_module(f"{pack_name}.commands"),
|
||||
"events": importlib.import_module(f"{pack_name}.events"),
|
||||
"stars": importlib.import_module(f"{pack_name}.stars"),
|
||||
"tables": importlib.import_module(f"{pack_name}.tables"),
|
||||
}
|
||||
except ImportError as e:
|
||||
log.error(f"{e.__class__.__name__} during the import of {pack_name}:\n"
|
||||
f"{traceback.format_exception(*sys.exc_info())}")
|
||||
|
@ -68,7 +73,7 @@ class Serf:
|
|||
tables = set()
|
||||
for pack in packs.values():
|
||||
try:
|
||||
tables = tables.union(pack.available_tables)
|
||||
tables = tables.union(pack["tables"].available_tables)
|
||||
except AttributeError:
|
||||
log.warning(f"Pack `{pack}` does not have the `available_tables` attribute.")
|
||||
continue
|
||||
|
@ -95,13 +100,13 @@ class Serf:
|
|||
pack = packs[pack_name]
|
||||
pack_cfg = packs_cfg.get(pack_name, {})
|
||||
try:
|
||||
events = pack.available_events
|
||||
events = pack["events"].available_events
|
||||
except AttributeError:
|
||||
log.warning(f"Pack `{pack}` does not have the `available_events` attribute.")
|
||||
else:
|
||||
self.register_events(events, pack_cfg)
|
||||
try:
|
||||
commands = pack.available_commands
|
||||
commands = pack["commands"].available_commands
|
||||
except AttributeError:
|
||||
log.warning(f"Pack `{pack}` does not have the `available_commands` attribute.")
|
||||
else:
|
||||
|
|
Loading…
Reference in a new issue