diff --git a/pyproject.toml b/pyproject.toml index 2b4edfb4..bf573702 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,54 +1,48 @@ [tool.poetry] -name = "royalnet" -version = "5.1a1" -description = "A multipurpose bot and web framework" -authors = ["Stefano Pigozzi "] -license = "AGPL-3.0+" -readme = "README.md" -homepage = "https://github.com/Steffo99/royalnet" -documentation = "https://gh.steffo.eu/royalnet/" -classifiers = [ - "Development Status :: 3 - Alpha", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.7", - "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)" -] + name = "royalnet" + version = "5.1a1" + description = "A multipurpose bot and web framework" + authors = ["Stefano Pigozzi "] + license = "AGPL-3.0+" + readme = "README.md" + homepage = "https://github.com/Steffo99/royalnet" + documentation = "https://gh.steffo.eu/royalnet/" + classifiers = [ + "Development Status :: 3 - Alpha", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.7", + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)" + ] # Library dependencies [tool.poetry.dependencies] -python = "^3.8" - -dateparser = "^0.7.2" - -python_telegram_bot = {version="^12.2.0", optional=true} - -discord_py = {git = "https://github.com/Rapptz/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 - -ffmpeg_python = {version="~0.2.0", optional=true} -youtube_dl = {version="*", optional=true} - -sqlalchemy = {version="^1.3.10", optional=true} - -psycopg2 = {version="^2.8.4", optional=true} # Requires quite a bit of stuff http://initd.org/psycopg/docs/install.html#install-from-source -psycopg2_binary = {version="^2.8.4", optional=true} # Prebuilt alternative to psycopg2, not recommended - -starlette = {version="0.12.13", optional=true} + python = "^3.8" + dateparser = "^0.7.2" + python_telegram_bot = {version="^12.2.0", optional=true} + discord_py = {git = "https://github.com/Rapptz/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 + ffmpeg_python = {version="~0.2.0", optional=true} + youtube_dl = {version="*", optional=true} + sqlalchemy = {version="^1.3.10", optional=true} + psycopg2 = {version="^2.8.4", optional=true} # Requires quite a bit of stuff http://initd.org/psycopg/docs/install.html#install-from-source + psycopg2_binary = {version="^2.8.4", optional=true} # Prebuilt alternative to psycopg2, not recommended + starlette = {version="^0.12.13", optional=true} + sentry_sdk = {version="~0.13.2", optional=true} # Optional dependencies [tool.poetry.extras] -telegram = ["python_telegram_bot"] -discord = ["discord_py", "pynacl"] -alchemy_easy = ["sqlalchemy", "psycopg2_binary"] -alchemy_hard = ["sqlalchemy", "psycopg2"] -bard = ["ffmpeg_python", "youtube_dl"] -constellation = ["starlette"] + telegram = ["python_telegram_bot"] + discord = ["discord_py", "pynacl"] + alchemy_easy = ["sqlalchemy", "psycopg2_binary"] + alchemy_hard = ["sqlalchemy", "psycopg2"] + bard = ["ffmpeg_python", "youtube_dl"] + constellation = ["starlette"] + sentry = ["sentry_sdk"] # Development dependencies [tool.poetry.dev-dependencies] -pytest = "^5.2.2" - + # There are none [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" + requires = ["poetry>=0.12"] + build-backend = "poetry.masonry.api" diff --git a/royalnet/constellation/constellation.py b/royalnet/constellation/constellation.py index bbbb23dc..25d6e46c 100644 --- a/royalnet/constellation/constellation.py +++ b/royalnet/constellation/constellation.py @@ -35,12 +35,26 @@ class Constellation: exc_stars = [] self.secrets_name: str = secrets_name + """The secrets_name this Constellation is currently using.""" + + self.running: bool = False + """Is the Constellation currently running?""" log.info(f"Creating Starlette in {'Debug' if __debug__ else 'Production'} mode...") self.starlette = Starlette(debug=debug) + """The :class:`Starlette` app.""" - log.info(f"Creating Alchemy with Tables: {' '.join([table.__name__ for table in tables])}") + log.debug("Finding required Tables...") + tables = set() + for SelectedPageStar in page_stars: + tables = tables.union(SelectedPageStar.tables) + for SelectedExcStar in exc_stars: + tables = tables.union(SelectedExcStar.tables) + log.debug(f"Found Tables: {' '.join([table.__name__ for table in tables])}") + + log.info(f"Creating Alchemy...") self.alchemy: royalnet.database.Alchemy = royalnet.database.Alchemy(database_uri=database_uri, tables=tables) + """The :class:`Alchemy: of this Constellation.""" log.info("Registering PageStars...") for SelectedPageStar in page_stars: @@ -82,17 +96,25 @@ class Constellation: if sentry_dsn: # noinspection PyUnreachableCode if __debug__: - release = "DEV" + release = f"Dev" else: - release = royalnet.version.semantic - log.info(f"Sentry: enabled (Royalnet {release})") + release = f"{royalnet.version.semantic}" + log.debug("Initializing Sentry...") sentry_sdk.init(sentry_dsn, integrations=[AioHttpIntegration(), SqlalchemyIntegration(), LoggingIntegration(event_level=None)], release=release) + log.info(f"Sentry: enabled (Royalnet {release})") else: log.info("Sentry: disabled") # Run the server - log.info(f"Running constellation server on {address}:{port}...") - uvicorn.run(self.starlette, host=address, port=port) + log.info(f"Running Constellation on {address}:{port}...") + self.running = True + try: + uvicorn.run(self.starlette, host=address, port=port) + finally: + self.running = False + + def __repr__(self): + return f"<{self.__class__.__qualname__}: {'running' if self.running else 'inactive'}>" diff --git a/royalnet/constellation/star.py b/royalnet/constellation/star.py index e606a729..05aff582 100644 --- a/royalnet/constellation/star.py +++ b/royalnet/constellation/star.py @@ -36,6 +36,9 @@ class Star: """A shortcut for the session :func:`asynccontextmanager` of the :class:`Constellation`.""" return self.constellation.alchemy.session_acm + 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``). @@ -64,6 +67,9 @@ class PageStar(Star): """ + 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 @@ -79,3 +85,6 @@ class ExceptionStar(Star): 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.""" + + def __repr__(self): + return f"<{self.__class__.__qualname__}: handles {self.error}>" diff --git a/royalnet/utils/__init__.py b/royalnet/utils/__init__.py index 9627780d..c66a77b9 100644 --- a/royalnet/utils/__init__.py +++ b/royalnet/utils/__init__.py @@ -5,7 +5,7 @@ from .escaping import telegram_escape, discord_escape from .safeformat import safeformat from .classdictjanitor import cdj from .sleepuntil import sleep_until -from .formatters import andformat, plusformat, fileformat, ytdldateformat, numberemojiformat, splitstring, ordinalformat +from .formatters import andformat, plusformat, underscorize, ytdldateformat, numberemojiformat, splitstring, ordinalformat from .urluuid import to_urluuid, from_urluuid __all__ = [ @@ -16,7 +16,7 @@ __all__ = [ "plusformat", "andformat", "plusformat", - "fileformat", + "underscorize", "ytdldateformat", "numberemojiformat", "telegram_escape", diff --git a/royalnet/utils/formatters.py b/royalnet/utils/formatters.py index 8d94648f..5b90818c 100644 --- a/royalnet/utils/formatters.py +++ b/royalnet/utils/formatters.py @@ -2,16 +2,29 @@ import typing import re -def andformat(l: typing.List[str], middle=", ", final=" and ") -> str: - """Convert a :py:class:`list` to a :py:class:`str` by adding ``final`` between the last two elements and ``middle`` between the others. +def andformat(l: typing.Collection[str], middle=", ", final=" and ") -> str: + """Convert a iterable (such as a :class:`list`) to a :class:`str` by adding ``final`` between the last two elements and ``middle`` between the others. - Parameters: - l: the input :py:class:`list`. - middle: the :py:class:`str` to be added between the middle elements. - final: the :py:class:`str` to be added between the last two elements. + Args: + l: the input iterable. + middle: the :class:`str` to be added between the middle elements. + final: the :class:`str` to be added between the last two elements. Returns: - The resulting :py:class:`str`.""" + The resulting :py:class:`str`. + + Examples: + :: + + >>> andformat(["Steffo", "Kappa", "Proto"]) + "Steffo, Kappa and Proto" + + >>> andformat(["Viktya", "Sensei", "Cate"], final=" e ") + "Viktya, Sensei e Cate" + + >>> andformat(["Paltri", "Spaggia", "Gesù", "Mallllco"], middle="+", final="+") + "Paltri+Spaggia+Gesù+Mallllco" + """ result = "" for index, item in enumerate(l): result += item @@ -22,42 +35,45 @@ def andformat(l: typing.List[str], middle=", ", final=" and ") -> str: return result -def plusformat(i: int, empty_if_zero: bool = False) -> str: - """Convert an :py:class:`int` to a :py:class:`str`, prepending a ``+`` if it's greater than 0. +def underscorize(string: str) -> str: + """Replace all non-word characters in a :class:`str` with underscores. - Parameters: - i: the :py:class:`int` to convert. - empty_if_zero: Return an empty string if ``i`` is zero. - - Returns: - The resulting :py:class:`str`.""" - if i == 0 and empty_if_zero: - return "" - if i > 0: - return f"+{i}" - return str(i) - - -def fileformat(string: str) -> str: - """Ensure a string can be used as a filename by replacing all non-word characters with underscores. + It is particularly useful when you want to use random strings from the Internet as filenames. Parameters: string: the input string. Returns: - A valid filename string.""" + The resulting string. + + Example: + :: + >>> underscorize("LE EPIC PRANK [GONE WRONG!?!?]") + "LE EPIC PRANK _GONE WRONG_____" + + """ return re.sub(r"\W", "_", string) def ytdldateformat(string: typing.Optional[str], separator: str = "-") -> str: - """Convert the weird date string returned by ``youtube-dl`` into the ``YYYY-MM-DD`` format. + """Convert the date :class:`str` returned by :mod:`youtube-dl` into the ``YYYY-MM-DD`` format. Parameters: - string: the input string, in the ``YYYYMMDD`` format. + string: the input string, in the ``YYYYMMDD`` format used by :mod:`youtube_dl`. separator: the string to add between the years, the months and the days. Defaults to ``-``. Returns: - The resulting string, in the format ``YYYY-MM-DD`` format.""" + The resulting string in the new format. + + Example: + :: + >>> ytdldateformat("20111111") + "2011-11-11" + + >>> ytdldateformat("20200202", separator=".") + "2020.02.02" + + """ if string is None: return "" return f"{string[0:4]}{separator}{string[4:6]}{separator}{string[6:8]}"