1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-27 13:34:28 +00:00

Rework constellation, somehow without breaking almost anything

This commit is contained in:
Steffo 2019-12-10 00:21:52 +01:00
parent b44b5fc587
commit 5d47c7c510
5 changed files with 158 additions and 20 deletions

View file

@ -0,0 +1,52 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
</code_scheme>
</component>

11
.idea/dataSources.xml Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="scaleway.steffo.eu" uuid="fe046003-9eda-4855-a2da-ae3a0de3aacd">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://scaleway.steffo.eu:5432/royalnet</jdbc-url>
</data-source>
</component>
</project>

View file

@ -7,6 +7,7 @@ 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 .star import PageStar, ExceptionStar
from ..utils import init_logging
try: try:
import uvicorn import uvicorn
@ -51,7 +52,8 @@ class Constellation:
herald_cfg: Dict[str, Any], herald_cfg: Dict[str, Any],
packs_cfg: Dict[str, Any], packs_cfg: Dict[str, Any],
constellation_cfg: Dict[str, Any], constellation_cfg: Dict[str, Any],
**_): logging_cfg: Dict[str, Any]
):
if Starlette is None: if Starlette is None:
raise ImportError("`constellation` extra is not installed") raise ImportError("`constellation` extra is not installed")
@ -87,9 +89,19 @@ class Constellation:
self.alchemy = ra.Alchemy(alchemy_cfg["database_url"], tables) self.alchemy = ra.Alchemy(alchemy_cfg["database_url"], tables)
log.info(f"Alchemy: {self.alchemy}") log.info(f"Alchemy: {self.alchemy}")
# Logging
self._logging_cfg: Dict[str, Any] = logging_cfg
"""The logging config for the :class:`Constellation` is stored to initialize the logger when the first page is
requested, as disabling the :mod:`uvicorn` logging also disables all logging in the process in general."""
# Herald # Herald
self.herald: Optional[rh.Link] = None self.herald: Optional[rh.Link] = None
"""The :class:`Link` object connecting the :class:`Constellation` to the rest of the herald network.""" """The :class:`Link` object connecting the :class:`Constellation` to the rest of the herald network.
As is the case with the logging module, it will be started on the first request received by the
:class:`Constellation`, as the event loop won't be available before that."""
self._herald_cfg: Dict[str, Any] = herald_cfg
"""The herald config for the :class:`Constellation` is stored to initialize the :class:`rh.Herald` later."""
self.herald_task: Optional[aio.Task] = None self.herald_task: Optional[aio.Task] = None
"""A reference to the :class:`aio.Task` that runs the :class:`rh.Link`.""" """A reference to the :class:`aio.Task` that runs the :class:`rh.Link`."""
@ -120,8 +132,7 @@ class Constellation:
elif not herald_cfg["enabled"]: elif not herald_cfg["enabled"]:
log.info("Herald: disabled") log.info("Herald: disabled")
else: else:
self.init_herald(herald_cfg) log.info(f"Herald: will be enabled on first request")
log.info(f"Herald: enabled")
# Register PageStars and ExceptionStars # Register PageStars and ExceptionStars
for pack_name in packs: for pack_name in packs:
@ -151,6 +162,11 @@ class Constellation:
self.port: int = constellation_cfg["port"] self.port: int = constellation_cfg["port"]
"""The port on which the :class:`Constellation` will listen for connection on.""" """The port on which the :class:`Constellation` will listen for connection on."""
self.loop: Optional[aio.AbstractEventLoop] = None
"""The event loop of the :class:`Constellation`.
Because of how :mod:`uvicorn` runs, it will stay :const:`None` until the first page is requested."""
# TODO: is this a good idea? # TODO: is this a good idea?
def interface_factory(self) -> Type[rc.CommandInterface]: def interface_factory(self) -> Type[rc.CommandInterface]:
"""Create the :class:`rc.CommandInterface` class for the :class:`Constellation`.""" """Create the :class:`rc.CommandInterface` class for the :class:`Constellation`."""
@ -241,29 +257,52 @@ class Constellation:
log.debug(f"Registering: {SelectedEvent.__qualname__} -> {SelectedEvent.name}") log.debug(f"Registering: {SelectedEvent.__qualname__} -> {SelectedEvent.name}")
self.events[SelectedEvent.name] = event self.events[SelectedEvent.name] = event
def _first_page_check(self):
if self.loop is None:
self.loop = aio.get_running_loop()
self.init_herald(self._herald_cfg)
self.loop.create_task(self.herald.run())
init_logging(self._logging_cfg)
def _page_star_wrapper(self, page_star: PageStar):
async def f(request):
self._first_page_check()
log.info(f"Running {page_star}")
return await page_star.page(request)
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__}")
try: try:
page_star_instance = SelectedPageStar(constellation=self, config=pack_cfg) page_star_instance = SelectedPageStar(interface=self.Interface(pack_cfg))
except Exception as e: except Exception as e:
log.error(f"Skipping: " log.error(f"Skipping: "
f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.") f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.")
ru.sentry_exc(e) ru.sentry_exc(e)
continue continue
self.starlette.add_route(page_star_instance.path, page_star_instance.page, page_star_instance.methods) 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]): def register_exc_stars(self, exc_stars: List[Type[ExceptionStar]], pack_cfg: Dict[str, Any]):
for SelectedPageStar in exc_stars: for SelectedExcStar in exc_stars:
log.debug(f"Registering: {SelectedPageStar.error} -> {SelectedPageStar.__qualname__}") log.debug(f"Registering: {SelectedExcStar.error} -> {SelectedExcStar.__qualname__}")
try: try:
page_star_instance = SelectedPageStar(constellation=self, config=pack_cfg) exc_star_instance = SelectedExcStar(interface=self.Interface(pack_cfg))
except Exception as e: except Exception as e:
log.error(f"Skipping: " log.error(f"Skipping: "
f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.") f"{SelectedExcStar.__qualname__} - {e.__class__.__qualname__} in the initialization.")
ru.sentry_exc(e) ru.sentry_exc(e)
continue continue
self.starlette.add_exception_handler(page_star_instance.error, page_star_instance.page) 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}/...")
@ -300,7 +339,8 @@ class Constellation:
constellation = cls(alchemy_cfg=alchemy_cfg, constellation = cls(alchemy_cfg=alchemy_cfg,
herald_cfg=herald_cfg, herald_cfg=herald_cfg,
packs_cfg=packs_cfg, packs_cfg=packs_cfg,
constellation_cfg=constellation_cfg) constellation_cfg=constellation_cfg,
logging_cfg=logging_cfg)
# Run the server # Run the server
constellation.run_blocking() constellation.run_blocking()

View file

@ -1,6 +1,7 @@
from typing import * from typing import *
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import Response from starlette.responses import Response
from royalnet.commands import CommandInterface
if TYPE_CHECKING: if TYPE_CHECKING:
from .constellation import Constellation from .constellation import Constellation
@ -10,9 +11,8 @@ class Star:
"""A Star is a class representing a part of the website. """A Star is a class representing a part of the website.
It shouldn't be used directly: please use :class:`PageStar` and :class:`ExceptionStar` instead!""" It shouldn't be used directly: please use :class:`PageStar` and :class:`ExceptionStar` instead!"""
def __init__(self, config: Dict[str, Any], constellation: "Constellation"): def __init__(self, interface: CommandInterface):
self.config: Dict[str, Any] = config self.interface: CommandInterface = interface
self.constellation: "Constellation" = constellation
async def page(self, request: Request) -> Response: async def page(self, request: Request) -> Response:
"""The function generating the :class:`~starlette.Response` to a web :class:`~starlette.Request`. """The function generating the :class:`~starlette.Response` to a web :class:`~starlette.Request`.
@ -20,21 +20,31 @@ class Star:
If it raises an error, the corresponding :class:`ExceptionStar` will be used to handle the request instead.""" If it raises an error, the corresponding :class:`ExceptionStar` will be used to handle the request instead."""
raise NotImplementedError() raise NotImplementedError()
@property
def constellation(self) -> "Constellation":
"""A shortcut for the :class:`Constellation`."""
return self.interface.constellation
@property @property
def alchemy(self): def alchemy(self):
"""A shortcut for the :class:`~royalnet.alchemy.Alchemy` of the :class:`Constellation`.""" """A shortcut for the :class:`~royalnet.alchemy.Alchemy` of the :class:`Constellation`."""
return self.constellation.alchemy return self.interface.constellation.alchemy
# noinspection PyPep8Naming # noinspection PyPep8Naming
@property @property
def Session(self): def Session(self):
"""A shortcut for the :class:`~royalnet.alchemy.Alchemy` :class:`Session` of the :class:`Constellation`.""" """A shortcut for the :class:`~royalnet.alchemy.Alchemy` :class:`Session` of the :class:`Constellation`."""
return self.constellation.alchemy.Session return self.interface.constellation.alchemy.Session
@property @property
def session_acm(self): def session_acm(self):
"""A shortcut for :func:`.alchemy.session_acm` of the :class:`Constellation`.""" """A shortcut for :func:`.alchemy.session_acm` of the :class:`Constellation`."""
return self.constellation.alchemy.session_acm return self.interface.constellation.alchemy.session_acm
@property
def config(self) -> Dict[str, Any]:
"""A shortcut for the Pack configuration of the :class:`Constellation`."""
return self.interface.config
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__qualname__}>" return f"<{self.__class__.__qualname__}>"

View file

@ -9,7 +9,32 @@ except ImportError:
l: logging.Logger = logging.getLogger(__name__) l: logging.Logger = logging.getLogger(__name__)
# From https://stackoverflow.com/a/56810619/4334568
def reset_logging():
manager = logging.root.manager
manager.disabled = logging.NOTSET
for logger in manager.loggerDict.values():
if isinstance(logger, logging.Logger):
logger.setLevel(logging.NOTSET)
logger.propagate = True
logger.disabled = False
logger.filters.clear()
handlers = logger.handlers.copy()
for handler in handlers:
# Copied from `logging.shutdown`.
try:
handler.acquire()
handler.flush()
handler.close()
except (OSError, ValueError):
pass
finally:
handler.release()
logger.removeHandler(handler)
def init_logging(logging_cfg: Dict[str, Any]): def init_logging(logging_cfg: Dict[str, Any]):
reset_logging()
loggers_cfg = logging_cfg["Loggers"] loggers_cfg = logging_cfg["Loggers"]
for logger_name in loggers_cfg: for logger_name in loggers_cfg:
if logger_name == "root": if logger_name == "root":
@ -23,7 +48,7 @@ def init_logging(logging_cfg: Dict[str, Any]):
stream_handler.formatter = coloredlogs.ColoredFormatter(logging_cfg["log_format"], style="{") stream_handler.formatter = coloredlogs.ColoredFormatter(logging_cfg["log_format"], style="{")
else: else:
stream_handler.formatter = logging.Formatter(logging_cfg["log_format"], style="{") stream_handler.formatter = logging.Formatter(logging_cfg["log_format"], style="{")
if len(logging.root.handlers) < 1: logging.root.handlers.clear()
logging.root.addHandler(stream_handler) logging.root.addHandler(stream_handler)
l.debug("Logging: ready") l.debug("Logging: ready")