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."""
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,
]
# 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]

View file

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

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 .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",
]

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

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):
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 = {}
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")

View file

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