mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-27 13:34:28 +00:00
MORE MORE MORE DOCS
This commit is contained in:
parent
7583f1330f
commit
b2c28f1735
14 changed files with 186 additions and 43 deletions
|
@ -7,3 +7,6 @@ royalnet.audio
|
||||||
|
|
||||||
.. automodule:: royalnet.audio
|
.. automodule:: royalnet.audio
|
||||||
:members:
|
:members:
|
||||||
|
:private-members:
|
||||||
|
:undoc-members:
|
||||||
|
|
||||||
|
|
|
@ -4,5 +4,8 @@ royalnet.bots
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
.. automodule:: royalnet.bots
|
.. automodule:: royalnet.bots
|
||||||
:members:
|
:members:
|
||||||
|
:private-members:
|
||||||
|
:undoc-members:
|
||||||
|
|
|
@ -7,3 +7,5 @@ royalnet.commands
|
||||||
|
|
||||||
.. automodule:: royalnet.commands
|
.. automodule:: royalnet.commands
|
||||||
:members:
|
:members:
|
||||||
|
:private-members:
|
||||||
|
:special-members:
|
||||||
|
|
11
docs/conf.py
11
docs/conf.py
|
@ -32,6 +32,17 @@ extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.intersphi
|
||||||
intersphinx_mapping = {'python': ('https://docs.python.org/3.7', None),
|
intersphinx_mapping = {'python': ('https://docs.python.org/3.7', None),
|
||||||
'discord': ('https://discordpy.readthedocs.io/en/latest/', None)}
|
'discord': ('https://discordpy.readthedocs.io/en/latest/', None)}
|
||||||
|
|
||||||
|
|
||||||
|
def skip(app, what, name, obj, would_skip, options):
|
||||||
|
if name == "__init__":
|
||||||
|
return False
|
||||||
|
return would_skip
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.connect("autodoc-skip-member", skip)
|
||||||
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ royalnet.database
|
||||||
|
|
||||||
.. automodule:: royalnet.database
|
.. automodule:: royalnet.database
|
||||||
:members:
|
:members:
|
||||||
|
:private-members:
|
||||||
|
:undoc-members:
|
||||||
|
|
||||||
|
|
||||||
Tables
|
Tables
|
||||||
|
@ -14,3 +16,5 @@ Tables
|
||||||
|
|
||||||
.. automodule:: royalnet.database.tables
|
.. automodule:: royalnet.database.tables
|
||||||
:members:
|
:members:
|
||||||
|
:private-members:
|
||||||
|
:undoc-members:
|
||||||
|
|
|
@ -7,3 +7,6 @@ royalnet.network
|
||||||
|
|
||||||
.. automodule:: royalnet.network
|
.. automodule:: royalnet.network
|
||||||
:members:
|
:members:
|
||||||
|
:private-members:
|
||||||
|
:undoc-members:
|
||||||
|
|
||||||
|
|
|
@ -7,3 +7,6 @@ royalnet.utils
|
||||||
|
|
||||||
.. automodule:: royalnet.utils
|
.. automodule:: royalnet.utils
|
||||||
:members:
|
:members:
|
||||||
|
:private-members:
|
||||||
|
:undoc-members:
|
||||||
|
|
||||||
|
|
|
@ -10,36 +10,52 @@ class PlayMode:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Create a new PlayMode and initialize the generator inside."""
|
"""Create a new PlayMode and initialize the generator inside."""
|
||||||
self.now_playing: typing.Optional[RoyalPCMAudio] = None
|
self.now_playing: typing.Optional[RoyalPCMAudio] = None
|
||||||
self.generator: typing.AsyncGenerator = self.generate_generator()
|
self.generator: typing.AsyncGenerator = self._generate_generator()
|
||||||
|
|
||||||
async def next(self):
|
async def next(self) -> typing.Optional[RoyalPCMAudio]:
|
||||||
|
"""Get the next :py:class:`royalnet.audio.RoyalPCMAudio` from the list and advance it.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The next :py:class:`royalnet.audio.RoyalPCMAudio`."""
|
||||||
return await self.generator.__anext__()
|
return await self.generator.__anext__()
|
||||||
|
|
||||||
def videos_left(self) -> typing.Union[int, float]:
|
def videos_left(self) -> typing.Union[int, float]:
|
||||||
"""Return the number of videos left in the PlayMode.
|
"""Return the number of videos left in the PlayMode.
|
||||||
|
|
||||||
Usually returns an :py:class:`int`, but may return :py:obj:`math.inf` if the PlayMode is infinite."""
|
Returns:
|
||||||
|
Usually a :py:class:`int`, but may return also :py:obj:`math.inf` if the PlayMode is infinite."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
async def generate_generator(self):
|
async def _generate_generator(self):
|
||||||
"""Get the next :py:class:`royalnet.audio.RoyalPCMAudio` from the list and advance it."""
|
"""Factory function for an async generator that changes the ``now_playing`` property either to a :py:class:`discord.audio.RoyalPCMAudio` or to ``None``, then yields the value it changed it to.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
The :py:class:`royalnet.audio.RoyalPCMAudio` to be played next."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
# This is needed to make the coroutine an async generator
|
# This is needed to make the coroutine an async generator
|
||||||
# noinspection PyUnreachableCode
|
# noinspection PyUnreachableCode
|
||||||
yield NotImplemented
|
yield NotImplemented
|
||||||
|
|
||||||
def add(self, item):
|
def add(self, item: RoyalPCMAudio) -> None:
|
||||||
"""Add a new :py:class:`royalnet.audio.RoyalPCMAudio` to the PlayMode."""
|
"""Add a new :py:class:`royalnet.audio.RoyalPCMAudio` to the PlayMode.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item: The item to add to the PlayMode."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self) -> None:
|
||||||
"""Delete all :py:class:`royalnet.audio.RoyalPCMAudio` contained inside this PlayMode."""
|
"""Delete all :py:class:`royalnet.audio.RoyalPCMAudio` contained inside this PlayMode."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class Playlist(PlayMode):
|
class Playlist(PlayMode):
|
||||||
"""A video list. :py:class:`royalnet.audio.RoyalPCMAudio` played are removed from the list."""
|
"""A video list. :py:class:`royalnet.audio.RoyalPCMAudio` played are removed from the list."""
|
||||||
|
|
||||||
def __init__(self, starting_list: typing.List[RoyalPCMAudio] = None):
|
def __init__(self, starting_list: typing.List[RoyalPCMAudio] = None):
|
||||||
|
"""Create a new Playlist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
starting_list: A list of items with which the Playlist will be created."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if starting_list is None:
|
if starting_list is None:
|
||||||
starting_list = []
|
starting_list = []
|
||||||
|
@ -48,7 +64,7 @@ class Playlist(PlayMode):
|
||||||
def videos_left(self) -> typing.Union[int, float]:
|
def videos_left(self) -> typing.Union[int, float]:
|
||||||
return len(self.list)
|
return len(self.list)
|
||||||
|
|
||||||
async def generate_generator(self):
|
async def _generate_generator(self):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
next_video = self.list.pop(0)
|
next_video = self.list.pop(0)
|
||||||
|
@ -60,10 +76,10 @@ class Playlist(PlayMode):
|
||||||
if self.now_playing is not None:
|
if self.now_playing is not None:
|
||||||
self.now_playing.delete()
|
self.now_playing.delete()
|
||||||
|
|
||||||
def add(self, item):
|
def add(self, item) -> None:
|
||||||
self.list.append(item)
|
self.list.append(item)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self) -> None:
|
||||||
while self.list:
|
while self.list:
|
||||||
self.list.pop(0).delete()
|
self.list.pop(0).delete()
|
||||||
self.now_playing.delete()
|
self.now_playing.delete()
|
||||||
|
@ -71,7 +87,12 @@ class Playlist(PlayMode):
|
||||||
|
|
||||||
class Pool(PlayMode):
|
class Pool(PlayMode):
|
||||||
"""A :py:class:`royalnet.audio.RoyalPCMAudio` pool. :py:class:`royalnet.audio.RoyalPCMAudio` are selected in random order and are not repeated until every song has been played at least once."""
|
"""A :py:class:`royalnet.audio.RoyalPCMAudio` pool. :py:class:`royalnet.audio.RoyalPCMAudio` are selected in random order and are not repeated until every song has been played at least once."""
|
||||||
|
|
||||||
def __init__(self, starting_pool: typing.List[RoyalPCMAudio] = None):
|
def __init__(self, starting_pool: typing.List[RoyalPCMAudio] = None):
|
||||||
|
"""Create a new Pool.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
starting_pool: A list of items the Pool will be created from."""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if starting_pool is None:
|
if starting_pool is None:
|
||||||
starting_pool = []
|
starting_pool = []
|
||||||
|
@ -81,7 +102,7 @@ class Pool(PlayMode):
|
||||||
def videos_left(self) -> typing.Union[int, float]:
|
def videos_left(self) -> typing.Union[int, float]:
|
||||||
return math.inf
|
return math.inf
|
||||||
|
|
||||||
async def generate_generator(self):
|
async def _generate_generator(self):
|
||||||
while True:
|
while True:
|
||||||
if not self.pool:
|
if not self.pool:
|
||||||
self.now_playing = None
|
self.now_playing = None
|
||||||
|
@ -94,12 +115,12 @@ class Pool(PlayMode):
|
||||||
self.now_playing = next_video
|
self.now_playing = next_video
|
||||||
yield next_video
|
yield next_video
|
||||||
|
|
||||||
def add(self, item):
|
def add(self, item) -> None:
|
||||||
self.pool.append(item)
|
self.pool.append(item)
|
||||||
self._pool_copy.append(item)
|
self._pool_copy.append(item)
|
||||||
random.shuffle(self._pool_copy)
|
random.shuffle(self._pool_copy)
|
||||||
|
|
||||||
def delete(self):
|
def delete(self) -> None:
|
||||||
for item in self.pool:
|
for item in self.pool:
|
||||||
item.delete()
|
item.delete()
|
||||||
self.pool = None
|
self.pool = None
|
||||||
|
|
|
@ -8,26 +8,35 @@ class RoyalPCMAudio(AudioSource):
|
||||||
"""A discord-compatible :py:class:`discord.AudioSource` that keeps data in a file instead of in memory."""
|
"""A discord-compatible :py:class:`discord.AudioSource` that keeps data in a file instead of in memory."""
|
||||||
|
|
||||||
def __init__(self, rpf: "RoyalPCMFile"):
|
def __init__(self, rpf: "RoyalPCMFile"):
|
||||||
"""Create a :py:class:`discord.audio.RoyalPCMAudio` from a :ref:`discord.audio.RoyalPCMFile`. Not recommended, use """
|
"""Create a :py:class:`discord.audio.RoyalPCMAudio` from a :py:class:`royalnet.audio.RoyalPCMFile`.
|
||||||
|
|
||||||
|
Warning:
|
||||||
|
Not recommended, use :py:func:`royalnet.audio.RoyalPCMAudio.create_from_url` or :py:func:`royalnet.audio.RoyalPCMAudio.create_from_ytsearch` instead."""
|
||||||
self.rpf: "RoyalPCMFile" = rpf
|
self.rpf: "RoyalPCMFile" = rpf
|
||||||
self._file = open(self.rpf.audio_filename, "rb")
|
self._file = open(self.rpf.audio_filename, "rb")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_from_url(url: str) -> typing.List["RoyalPCMAudio"]:
|
def create_from_url(url: str) -> typing.List["RoyalPCMAudio"]:
|
||||||
"""Download a file with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMAudio`.
|
"""Download a file with youtube_dl and create a list of RoyalPCMAudios.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
url: The url of the file to download."""
|
url: The url of the file to download.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A :py:class:`list` of RoyalPCMAudios, each corresponding to a downloaded video."""
|
||||||
rpf_list = RoyalPCMFile.create_from_url(url)
|
rpf_list = RoyalPCMFile.create_from_url(url)
|
||||||
return [RoyalPCMAudio(rpf) for rpf in rpf_list]
|
return [RoyalPCMAudio(rpf) for rpf in rpf_list]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_from_ytsearch(search: str, amount: int = 1) -> typing.List["RoyalPCMAudio"]:
|
def create_from_ytsearch(search: str, amount: int = 1) -> typing.List["RoyalPCMAudio"]:
|
||||||
"""Search a string on YouTube and download the first ``amount`` number of videos, then download those with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMAudio`.
|
"""Search a string on YouTube and download the first ``amount`` number of videos, then download those with youtube_dl and create a list of RoyalPCMAudios.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
search: The string to search on YouTube.
|
search: The string to search on YouTube.
|
||||||
amount: The number of videos to download."""
|
amount: The number of videos to download.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A :py:class:`list` of RoyalPCMAudios, each corresponding to a downloaded video."""
|
||||||
rpf_list = RoyalPCMFile.create_from_ytsearch(search, amount)
|
rpf_list = RoyalPCMFile.create_from_ytsearch(search, amount)
|
||||||
return [RoyalPCMAudio(rpf) for rpf in rpf_list]
|
return [RoyalPCMAudio(rpf) for rpf in rpf_list]
|
||||||
|
|
||||||
|
|
|
@ -21,12 +21,12 @@ class RoyalPCMFile(YtdlFile):
|
||||||
# Set the time to generate the filename
|
# Set the time to generate the filename
|
||||||
self._time = time.time()
|
self._time = time.time()
|
||||||
# Ensure the file doesn't already exist
|
# Ensure the file doesn't already exist
|
||||||
if os.path.exists(self._ytdl_filename) or os.path.exists(self.audio_filename):
|
if os.path.exists(self.ytdl_filename) or os.path.exists(self.audio_filename):
|
||||||
raise FileExistsError("Can't overwrite file")
|
raise FileExistsError("Can't overwrite file")
|
||||||
# Overwrite the new ytdl_args
|
# Overwrite the new ytdl_args
|
||||||
self.ytdl_args = {**self.ytdl_args, **ytdl_args}
|
self.ytdl_args = {**self.ytdl_args, **ytdl_args}
|
||||||
log.info(f"Now downloading {info.webpage_url}")
|
log.info(f"Now downloading {info.webpage_url}")
|
||||||
super().__init__(info, outtmpl=self._ytdl_filename, **self.ytdl_args)
|
super().__init__(info, outtmpl=self.ytdl_filename, **self.ytdl_args)
|
||||||
# Find the audio_filename with a regex (should be video.opus)
|
# Find the audio_filename with a regex (should be video.opus)
|
||||||
log.info(f"Converting {self.video_filename}...")
|
log.info(f"Converting {self.video_filename}...")
|
||||||
# Convert the video to pcm
|
# Convert the video to pcm
|
||||||
|
@ -50,25 +50,48 @@ class RoyalPCMFile(YtdlFile):
|
||||||
"""Download a file with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMFile`.
|
"""Download a file with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMFile`.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
url: The url of the file to download."""
|
url: The url of the file to download.
|
||||||
|
ytdl_args: Extra arguments to be passed to YoutubeDL while downloading.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A :py:class:`list` of RoyalPCMAudios, each corresponding to a downloaded video."""
|
||||||
info_list = YtdlInfo.create_from_url(url)
|
info_list = YtdlInfo.create_from_url(url)
|
||||||
return [RoyalPCMFile(info, **ytdl_args) for info in info_list]
|
return [RoyalPCMFile(info, **ytdl_args) for info in info_list]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_from_ytsearch(search: str, amount: int = 1, **ytdl_args) -> typing.List["RoyalPCMFile"]:
|
def create_from_ytsearch(search: str, amount: int = 1, **ytdl_args) -> typing.List["RoyalPCMFile"]:
|
||||||
"""Search a string on YouTube and download the first ``amount`` number of videos, then download those with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMFile`."""
|
"""Search a string on YouTube and download the first ``amount`` number of videos, then download those with youtube_dl and create a list of :py:class:`discord.audio.RoyalPCMFile`.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
search: The string to search on YouTube.
|
||||||
|
amount: The number of videos to download.
|
||||||
|
ytdl_args: Extra arguments to be passed to YoutubeDL while downloading.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A :py:class:`list` of RoyalPCMFiles, each corresponding to a downloaded video."""
|
||||||
url = f"ytsearch{amount}:{search}"
|
url = f"ytsearch{amount}:{search}"
|
||||||
info_list = YtdlInfo.create_from_url(url)
|
info_list = YtdlInfo.create_from_url(url)
|
||||||
return [RoyalPCMFile(info, **ytdl_args) for info in info_list]
|
return [RoyalPCMFile(info, **ytdl_args) for info in info_list]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _ytdl_filename(self):
|
def ytdl_filename(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
The name of the downloaded video file, as a :py:class:`str`.
|
||||||
|
|
||||||
|
Warning:
|
||||||
|
It's going to be deleted as soon as the :py:func:`discord.audio.RoyalPCMFile.__init__` function has completed, so it's probably not going to be very useful...
|
||||||
|
"""
|
||||||
return f"./downloads/{safefilename(self.info.title)}-{safefilename(str(int(self._time)))}.ytdl"
|
return f"./downloads/{safefilename(self.info.title)}-{safefilename(str(int(self._time)))}.ytdl"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def audio_filename(self):
|
def audio_filename(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns:
|
||||||
|
The name of the downloaded and PCM-converted audio file."""
|
||||||
return f"./downloads/{safefilename(self.info.title)}-{safefilename(str(int(self._time)))}.pcm"
|
return f"./downloads/{safefilename(self.info.title)}-{safefilename(str(int(self._time)))}.pcm"
|
||||||
|
|
||||||
def delete_audio_file(self):
|
def delete_audio_file(self):
|
||||||
|
"""Delete the PCM-converted audio file."""
|
||||||
log.info(f"Deleting {self.audio_filename}")
|
log.info(f"Deleting {self.audio_filename}")
|
||||||
os.remove(self.audio_filename)
|
os.remove(self.audio_filename)
|
||||||
|
|
|
@ -15,6 +15,8 @@ class InterruptDownload(DownloaderError):
|
||||||
|
|
||||||
|
|
||||||
class YtdlFile:
|
class YtdlFile:
|
||||||
|
"""A wrapper around a youtube_dl downloaded file."""
|
||||||
|
|
||||||
ytdl_args = {
|
ytdl_args = {
|
||||||
"logger": log, # Log messages to a logging.Logger instance.
|
"logger": log, # Log messages to a logging.Logger instance.
|
||||||
"quiet": True, # Do not print messages to stdout.
|
"quiet": True, # Do not print messages to stdout.
|
||||||
|
@ -22,7 +24,6 @@ class YtdlFile:
|
||||||
"no_warnings": True, # Do not print out anything for warnings.
|
"no_warnings": True, # Do not print out anything for warnings.
|
||||||
}
|
}
|
||||||
|
|
||||||
"""A wrapper around a youtube_dl downloaded file."""
|
|
||||||
def __init__(self, info: "YtdlInfo", outtmpl="%(title)s-%(id)s.%(ext)s", **ytdl_args):
|
def __init__(self, info: "YtdlInfo", outtmpl="%(title)s-%(id)s.%(ext)s", **ytdl_args):
|
||||||
self.info: "YtdlInfo" = info
|
self.info: "YtdlInfo" = info
|
||||||
self.video_filename: str
|
self.video_filename: str
|
||||||
|
@ -57,7 +58,11 @@ class YtdlFile:
|
||||||
class YtdlInfo:
|
class YtdlInfo:
|
||||||
"""A wrapper around youtube_dl extracted info."""
|
"""A wrapper around youtube_dl extracted info."""
|
||||||
|
|
||||||
def __init__(self, info):
|
def __init__(self, info: typing.Dict[str, typing.Any]):
|
||||||
|
"""Create a YtdlInfo from the dict returned by the :py:func:`youtube_dl.YoutubeDL.extract_info` function.
|
||||||
|
|
||||||
|
Warning:
|
||||||
|
Does not download the info, for that use :py:func:`royalnet.audio.YtdlInfo.create_from_url`."""
|
||||||
self.id: typing.Optional[str] = info.get("id")
|
self.id: typing.Optional[str] = info.get("id")
|
||||||
self.uploader: typing.Optional[str] = info.get("uploader")
|
self.uploader: typing.Optional[str] = info.get("uploader")
|
||||||
self.uploader_id: typing.Optional[str] = info.get("uploader_id")
|
self.uploader_id: typing.Optional[str] = info.get("uploader_id")
|
||||||
|
|
|
@ -2,42 +2,70 @@ from ..error import RoyalnetError
|
||||||
|
|
||||||
|
|
||||||
class Message:
|
class Message:
|
||||||
|
"""A message sent through the Royalnet."""
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__}>"
|
return f"<{self.__class__.__name__}>"
|
||||||
|
|
||||||
def raise_on_error(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class IdentifySuccessfulMessage(Message):
|
class IdentifySuccessfulMessage(Message):
|
||||||
pass
|
"""The Royalnet identification step was successful."""
|
||||||
|
|
||||||
|
|
||||||
class ServerErrorMessage(Message):
|
class ServerErrorMessage(Message):
|
||||||
|
"""Something went wrong in the connection to the :py:class:`royalnet.network.RoyalnetServer`."""
|
||||||
def __init__(self, reason):
|
def __init__(self, reason):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.reason = reason
|
self.reason = reason
|
||||||
|
|
||||||
|
|
||||||
class InvalidSecretEM(ServerErrorMessage):
|
class InvalidSecretEM(ServerErrorMessage):
|
||||||
pass
|
"""The sent secret was incorrect.
|
||||||
|
|
||||||
|
This message terminates connection to the :py:class:`royalnet.network.RoyalnetServer`."""
|
||||||
|
|
||||||
|
|
||||||
class InvalidPackageEM(ServerErrorMessage):
|
class InvalidPackageEM(ServerErrorMessage):
|
||||||
pass
|
"""The sent :py:class:`royalnet.network.Package` was invalid."""
|
||||||
|
|
||||||
|
|
||||||
class InvalidDestinationEM(InvalidPackageEM):
|
class InvalidDestinationEM(InvalidPackageEM):
|
||||||
|
"""The :py:class:`royalnet.network.Package` destination was invalid or not found."""
|
||||||
|
|
||||||
|
|
||||||
|
class Reply(Message):
|
||||||
|
"""A reply to a request sent through the Royalnet."""
|
||||||
|
|
||||||
|
def raise_on_error(self) -> None:
|
||||||
|
"""If the reply is an error, raise an error, otherwise, do nothing.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
A :py:exc:`RoyalnetError`, if the Reply is an error, otherwise, nothing."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class RequestSuccessful(Reply):
|
||||||
|
"""The sent request was successful."""
|
||||||
|
|
||||||
|
def raise_on_error(self) -> None:
|
||||||
|
"""If the reply is an error, raise an error, otherwise, do nothing.
|
||||||
|
|
||||||
|
Does nothing."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RequestSuccessful(Message):
|
class RequestError(Reply):
|
||||||
pass
|
"""The sent request wasn't successful."""
|
||||||
|
|
||||||
|
|
||||||
class RequestError(Message):
|
|
||||||
def __init__(self, exc: Exception):
|
def __init__(self, exc: Exception):
|
||||||
|
"""Create a RequestError.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
exc: The exception that caused the error in the request."""
|
||||||
self.exc: Exception = exc
|
self.exc: Exception = exc
|
||||||
|
|
||||||
def raise_on_error(self):
|
def raise_on_error(self) -> None:
|
||||||
|
"""If the reply is an error, raise an error, otherwise, do nothing.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Always raises a :py:exc:`royalnet.error.RoyalnetError`, containing the exception that caused the error."""
|
||||||
raise RoyalnetError(exc=self.exc)
|
raise RoyalnetError(exc=self.exc)
|
||||||
|
|
|
@ -3,20 +3,42 @@ import uuid
|
||||||
|
|
||||||
|
|
||||||
class Package:
|
class Package:
|
||||||
|
"""A Royalnet package, the data type with which a :py:class:`royalnet.network.RoyalnetLink` communicates with a :py:class:`royalnet.network.RoyalnetServer` or another link. """
|
||||||
|
|
||||||
def __init__(self, data, destination: str, source: str, *, source_conv_id: str = None, destination_conv_id: str = None):
|
def __init__(self, data, destination: str, source: str, *, source_conv_id: str = None, destination_conv_id: str = None):
|
||||||
|
"""Create a Package.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
data: The data that should be sent. Usually a :py:class:`royalnet.network.Message`.
|
||||||
|
destination: The ``link_type`` of the destination node, or alternatively, the ``nid`` of the node. Can also be the ``NULL`` value to send the message to nobody.
|
||||||
|
source: The ``nid`` of the node that created this Package.
|
||||||
|
source_conv_id: The conversation id of the node that created this package. Akin to the sequence number on IP packets.
|
||||||
|
destination_conv_id: The conversation id of the node that this Package is a reply to."""
|
||||||
|
# TODO: something is not right in these type hints. Check them.
|
||||||
self.data = data
|
self.data = data
|
||||||
self.destination: str = destination
|
self.destination: str = destination
|
||||||
self.source = source
|
self.source: str = source
|
||||||
self.source_conv_id = source_conv_id or str(uuid.uuid4())
|
self.source_conv_id: str = source_conv_id or str(uuid.uuid4())
|
||||||
self.destination_conv_id = destination_conv_id
|
self.destination_conv_id: str = destination_conv_id
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Package to {self.destination}: {self.data.__class__.__name__}>"
|
return f"<Package to {self.destination}: {self.data.__class__.__name__}>"
|
||||||
|
|
||||||
def reply(self, data) -> "Package":
|
def reply(self, data) -> "Package":
|
||||||
|
"""Reply to this Package with another Package.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
data: The data that should be sent. Usually a :py:class:`royalnet.network.Message`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The reply Package."""
|
||||||
return Package(data, self.source, self.destination,
|
return Package(data, self.source, self.destination,
|
||||||
source_conv_id=str(uuid.uuid4()),
|
source_conv_id=str(uuid.uuid4()),
|
||||||
destination_conv_id=self.source_conv_id)
|
destination_conv_id=self.source_conv_id)
|
||||||
|
|
||||||
def pickle(self):
|
def pickle(self) -> bytes:
|
||||||
|
""":py:mod:`pickle` this Package.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The pickled package in form of bytes."""
|
||||||
return pickle.dumps(self)
|
return pickle.dumps(self)
|
||||||
|
|
|
@ -85,7 +85,13 @@ class RoyalnetServer:
|
||||||
self._loop.create_task(self.route_package(package))
|
self._loop.create_task(self.route_package(package))
|
||||||
|
|
||||||
def find_destination(self, package: Package) -> typing.List[ConnectedClient]:
|
def find_destination(self, package: Package) -> typing.List[ConnectedClient]:
|
||||||
"""Find a list of destinations for the sent packages"""
|
"""Find a list of destinations for the package.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
package: The package to find the destination of.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A :py:class:`list` of :py:class:`ConnectedClients` to send the package to."""
|
||||||
# Parse destination
|
# Parse destination
|
||||||
# Is it nothing?
|
# Is it nothing?
|
||||||
if package.destination == "NULL":
|
if package.destination == "NULL":
|
||||||
|
|
Loading…
Reference in a new issue