1
Fork 0
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:
Steffo 2020-02-04 18:27:05 +01:00
parent c3e77fa5e6
commit 353b94f1ba
13 changed files with 164 additions and 238 deletions

View file

@ -1,19 +1 @@
"""A Pack that is imported by default by all Royalnet instances.""" """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",
]

View file

@ -7,10 +7,5 @@ available_page_stars = [
ApiRoyalnetVersionStar, ApiRoyalnetVersionStar,
] ]
# Enter the ExceptionStars of your Pack here!
available_exception_stars = [
]
# Don't change this, it should automatically generate __all__ # 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]

View file

@ -1,15 +1,11 @@
import royalnet import royalnet.version as rv
from starlette.requests import Request from royalnet.constellation import ApiStar
from starlette.responses import *
from royalnet.constellation import PageStar
class ApiRoyalnetVersionStar(PageStar): class ApiRoyalnetVersionStar(ApiStar):
path = "/api/royalnet/version" path = "/api/royalnet/version"
async def page(self, request: Request) -> JSONResponse: async def api(self, data: dict) -> dict:
return JSONResponse({ return {
"version": { "semantic": rv.semantic
"semantic": royalnet.__version__,
} }
})

View 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

View file

@ -15,13 +15,17 @@ You can install them with: ::
""" """
from .constellation import Constellation from .constellation import Constellation
from .star import Star, PageStar, ExceptionStar from .star import Star
from .shoot import shoot from .pagestar import PageStar
from .apistar import ApiStar
from .jsonapi import api_response, api_success, api_error
__all__ = [ __all__ = [
"Constellation", "Constellation",
"Star", "Star",
"PageStar", "PageStar",
"ExceptionStar", "ApiStar",
"shoot", "api_response",
"api_success",
"api_error",
] ]

View 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()

View file

@ -8,7 +8,7 @@ import royalnet.alchemy as ra
import royalnet.herald as rh import royalnet.herald as rh
import royalnet.utils as ru import royalnet.utils as ru
import royalnet.commands as rc import royalnet.commands as rc
from .star import PageStar, ExceptionStar from .pagestar import PageStar
from ..utils import init_logging from ..utils import init_logging
@ -42,7 +42,12 @@ class Constellation:
for pack_name in pack_names: for pack_name in pack_names:
log.debug(f"Importing pack: {pack_name}") log.debug(f"Importing pack: {pack_name}")
try: 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: except ImportError as e:
log.error(f"Error during the import of {pack_name}: {e}") log.error(f"Error during the import of {pack_name}: {e}")
log.info(f"Packs: {len(packs)} imported") log.info(f"Packs: {len(packs)} imported")
@ -60,7 +65,7 @@ class Constellation:
tables = set() tables = set()
for pack in packs.values(): for pack in packs.values():
try: try:
tables = tables.union(pack.available_tables) tables = tables.union(pack["tables"].available_tables)
except AttributeError: except AttributeError:
log.warning(f"Pack `{pack}` does not have the `available_tables` attribute.") log.warning(f"Pack `{pack}` does not have the `available_tables` attribute.")
continue continue
@ -99,7 +104,7 @@ class Constellation:
pack = packs[pack_name] pack = packs[pack_name]
pack_cfg = packs_cfg.get(pack_name, {}) pack_cfg = packs_cfg.get(pack_name, {})
try: try:
events = pack.available_events events = pack["events"].available_events
except AttributeError: except AttributeError:
log.warning(f"Pack `{pack}` does not have the `available_events` attribute.") log.warning(f"Pack `{pack}` does not have the `available_events` attribute.")
else: else:
@ -118,17 +123,11 @@ class Constellation:
pack = packs[pack_name] pack = packs[pack_name]
pack_cfg = packs_cfg.get(pack_name, {}) pack_cfg = packs_cfg.get(pack_name, {})
try: try:
page_stars = pack.available_page_stars page_stars = pack["stars"].available_page_stars
except AttributeError: except AttributeError:
log.warning(f"Pack `{pack}` does not have the `available_page_stars` attribute.") log.warning(f"Pack `{pack}` does not have the `available_page_stars` attribute.")
else: else:
self.register_page_stars(page_stars, pack_cfg) 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"PageStars: {len(self.starlette.routes)} stars")
log.info(f"ExceptionStars: {len(self.starlette.exception_handlers)} 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 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]): def register_page_stars(self, page_stars: List[Type[PageStar]], pack_cfg: Dict[str, Any]):
for SelectedPageStar in page_stars: for SelectedPageStar in page_stars:
log.debug(f"Registering: {SelectedPageStar.path} -> {SelectedPageStar.__qualname__}") log.debug(f"Registering: {SelectedPageStar.path} -> {SelectedPageStar.__qualname__}")
@ -279,18 +270,6 @@ class Constellation:
continue continue
self.starlette.add_route(*self._page_star_wrapper(page_star_instance)) 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): def run_blocking(self):
log.info(f"Running Constellation on https://{self.address}:{self.port}/...") log.info(f"Running Constellation on https://{self.address}:{self.port}/...")
loop: aio.AbstractEventLoop = aio.get_event_loop() loop: aio.AbstractEventLoop = aio.get_event_loop()

View 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)

View 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}>"

View file

@ -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)

View file

@ -48,63 +48,3 @@ class Star:
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__qualname__}>" 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}>"

View file

@ -19,7 +19,12 @@ def run(config_filename, file_format):
packs = {} packs = {}
for pack_name in pack_names: for pack_name in pack_names:
try: 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: except ImportError as e:
p(f"Skipping `{pack_name}`: {e}", err=True) p(f"Skipping `{pack_name}`: {e}", err=True)
continue continue
@ -30,7 +35,7 @@ def run(config_filename, file_format):
lines = [] lines = []
try: try:
commands = pack.available_commands commands = pack["commands"].available_commands
except AttributeError: except AttributeError:
p(f"Pack `{pack}` does not have the `available_commands` attribute.", err=True) p(f"Pack `{pack}` does not have the `available_commands` attribute.", err=True)
continue continue
@ -41,93 +46,6 @@ def run(config_filename, file_format):
for line in lines: for line in lines:
p(line) 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: else:
raise click.ClickException("Unknown format") raise click.ClickException("Unknown format")

View file

@ -7,7 +7,7 @@ from sqlalchemy.schema import Table
from royalnet.commands import * from royalnet.commands import *
import royalnet.utils as ru import royalnet.utils as ru
import royalnet.alchemy as ra import royalnet.alchemy as ra
import royalnet.backpack as rb import royalnet.backpack.tables as rbt
import royalnet.herald as rh import royalnet.herald as rh
import traceback import traceback
@ -20,7 +20,7 @@ class Serf:
Discord).""" Discord)."""
interface_name = NotImplemented interface_name = NotImplemented
_master_table: type = rb.tables.User _master_table: type = rbt.User
_identity_table: type = NotImplemented _identity_table: type = NotImplemented
_identity_column: str = NotImplemented _identity_column: str = NotImplemented
@ -39,7 +39,12 @@ class Serf:
for pack_name in pack_names: for pack_name in pack_names:
log.debug(f"Importing pack: {pack_name}") log.debug(f"Importing pack: {pack_name}")
try: 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: except ImportError as e:
log.error(f"{e.__class__.__name__} during the import of {pack_name}:\n" log.error(f"{e.__class__.__name__} during the import of {pack_name}:\n"
f"{traceback.format_exception(*sys.exc_info())}") f"{traceback.format_exception(*sys.exc_info())}")
@ -68,7 +73,7 @@ class Serf:
tables = set() tables = set()
for pack in packs.values(): for pack in packs.values():
try: try:
tables = tables.union(pack.available_tables) tables = tables.union(pack["tables"].available_tables)
except AttributeError: except AttributeError:
log.warning(f"Pack `{pack}` does not have the `available_tables` attribute.") log.warning(f"Pack `{pack}` does not have the `available_tables` attribute.")
continue continue
@ -95,13 +100,13 @@ class Serf:
pack = packs[pack_name] pack = packs[pack_name]
pack_cfg = packs_cfg.get(pack_name, {}) pack_cfg = packs_cfg.get(pack_name, {})
try: try:
events = pack.available_events events = pack["events"].available_events
except AttributeError: except AttributeError:
log.warning(f"Pack `{pack}` does not have the `available_events` attribute.") log.warning(f"Pack `{pack}` does not have the `available_events` attribute.")
else: else:
self.register_events(events, pack_cfg) self.register_events(events, pack_cfg)
try: try:
commands = pack.available_commands commands = pack["commands"].available_commands
except AttributeError: except AttributeError:
log.warning(f"Pack `{pack}` does not have the `available_commands` attribute.") log.warning(f"Pack `{pack}` does not have the `available_commands` attribute.")
else: else: