From acdcd4c3c2ea6888b1276ee86361c9180a3389a3 Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Tue, 12 Dec 2023 17:57:30 -0800 Subject: [PATCH] goodbye pluginpriority --- src/ytdl_sub/config/plugin.py | 37 ------- src/ytdl_sub/config/plugin_mapping.py | 99 +++++++++++++++++++ src/ytdl_sub/config/plugin_operation.py | 8 ++ src/ytdl_sub/config/preset.py | 14 +-- src/ytdl_sub/config/preset_options.py | 9 +- src/ytdl_sub/downloaders/url/downloader.py | 5 - src/ytdl_sub/downloaders/url/validators.py | 2 +- src/ytdl_sub/plugins/audio_extract.py | 2 +- src/ytdl_sub/plugins/embed_thumbnail.py | 2 - src/ytdl_sub/plugins/file_convert.py | 7 +- src/ytdl_sub/plugins/internal/view.py | 2 - src/ytdl_sub/plugins/regex.py | 8 +- src/ytdl_sub/plugins/split_by_chapters.py | 2 +- src/ytdl_sub/plugins/subtitles.py | 2 +- .../subscriptions/subscription_download.py | 28 +++--- 15 files changed, 135 insertions(+), 92 deletions(-) create mode 100644 src/ytdl_sub/config/plugin_operation.py diff --git a/src/ytdl_sub/config/plugin.py b/src/ytdl_sub/config/plugin.py index 0dd1f8b0..1e57c6a8 100644 --- a/src/ytdl_sub/config/plugin.py +++ b/src/ytdl_sub/config/plugin.py @@ -1,6 +1,5 @@ from abc import ABC from abc import abstractmethod -from enum import Enum from typing import Dict from typing import Generic from typing import List @@ -15,39 +14,6 @@ from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.ytdl_additions.enhanced_download_archive import DownloadArchiver from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive - -class PluginPriority: - """ - Defines priority for plugins, 0 is highest priority - """ - - MODIFY_ENTRY_SPLIT = 9 - - # If modify_entry priority is >= to this value, run after split - MODIFY_ENTRY_AFTER_SPLIT = 10 - - # if post_process is >= to this value, run after file_convert - POST_PROCESS_AFTER_FILE_CONVERT = 10 - - MODIFY_ENTRY_FIRST = 0 - - def __init__( - self, modify_entry_metadata: int = 5, modify_entry: int = 5, post_process: int = 5 - ): - self.modify_entry_metadata = modify_entry_metadata - self.modify_entry = modify_entry - self.post_process = post_process - - @property - def modify_entry_after_split(self) -> bool: - """ - Returns - ------- - True if the plugin should modify an entry after a potential split. False otherwise. - """ - return self.modify_entry >= PluginPriority.MODIFY_ENTRY_AFTER_SPLIT - - # pylint: disable=no-self-use,unused-argument @@ -56,7 +22,6 @@ class BasePlugin(DownloadArchiver, Generic[TOptionsValidator], ABC): Shared code amongst all SourcePlugins (downloaders) and Plugins (post-download modification) """ - priority: PluginPriority = PluginPriority() plugin_options_type: Type[TOptionsValidator] def __init__( @@ -147,8 +112,6 @@ class SplitPlugin(Plugin[TOptionsValidator], Generic[TOptionsValidator], ABC): Plugin that splits entries into zero or more entries """ - priority = PluginPriority(modify_entry_metadata=PluginPriority.MODIFY_ENTRY_SPLIT) - @abstractmethod def split(self, entry: Entry) -> List[Tuple[Entry, FileMetadata]]: """ diff --git a/src/ytdl_sub/config/plugin_mapping.py b/src/ytdl_sub/config/plugin_mapping.py index 5a446a26..1553844f 100644 --- a/src/ytdl_sub/config/plugin_mapping.py +++ b/src/ytdl_sub/config/plugin_mapping.py @@ -1,8 +1,15 @@ +import copy from typing import Dict +from typing import Iterable from typing import List +from typing import Optional +from typing import Tuple from typing import Type from ytdl_sub.config.plugin import Plugin +from ytdl_sub.config.plugin import SplitPlugin +from ytdl_sub.config.plugin_operation import PluginOperation +from ytdl_sub.config.preset_options import OptionsValidator from ytdl_sub.plugins.audio_extract import AudioExtractPlugin from ytdl_sub.plugins.chapters import ChaptersPlugin from ytdl_sub.plugins.date_range import DateRangePlugin @@ -45,6 +52,98 @@ class PluginMapping: "throttle_protection": ThrottleProtectionPlugin, } + # All other plugins are added after the defined ordered ones + _ORDER_MODIFY_ENTRY_METADATA: List[Type[Plugin]] = [ + ThrottleProtectionPlugin, + SubtitlesPlugin, + # add all others + ] + + _ORDER_MODIFY_ENTRY: List[Type[Plugin]] = [ + AudioExtractPlugin, + FileConvertPlugin, + SplitByChaptersPlugin, + RegexPlugin, + # add all others + ] + + _ORDER_POST_PROCESS: List[Type[Plugin]] = [ + EmbedThumbnailPlugin, + ] + + @classmethod + def _order_by( + cls, plugin_types: List[Type[Plugin]], operation: PluginOperation + ) -> List[Type[Plugin]]: + if operation == PluginOperation.MODIFY_ENTRY_METADATA: + ordering = cls._ORDER_MODIFY_ENTRY_METADATA + elif operation == PluginOperation.MODIFY_ENTRY: + ordering = cls._ORDER_MODIFY_ENTRY + elif operation == PluginOperation.POST_PROCESS: + ordering = cls._ORDER_POST_PROCESS + else: + raise ValueError("PluginOperation does not support ordering") + + ordered_plugin_operations: List[Type[Plugin]] = [] + for pl_type in reversed(ordering): + for plugin_type in plugin_types: + if plugin_type == pl_type: + ordered_plugin_operations.insert(0, plugin_type) + else: + ordered_plugin_operations.append(plugin_type) + + return ordered_plugin_operations + + @classmethod + def order_options_by( + cls, zipped: List[Tuple[Type[Plugin], OptionsValidator]], operation: PluginOperation + ) -> List[OptionsValidator]: + ordered_types: List[Type[Plugin]] = cls._order_by( + plugin_types=[val[0] for val in zipped], operation=operation + ) + + ordered_options: List[OptionsValidator] = [] + for plugin_type, plugin_options in zipped: + sorted_idx = ordered_types.index(plugin_type) + ordered_options.insert(sorted_idx, plugin_options) + + return ordered_options + + @classmethod + def _is_modified_after_split(cls, plugin: Plugin) -> bool: + if type(plugin) not in cls._ORDER_MODIFY_ENTRY: + return True + return cls._ORDER_MODIFY_ENTRY.index(type(plugin)) > cls._ORDER_MODIFY_ENTRY.index( + SplitByChaptersPlugin + ) + + @classmethod + def order_plugins_by( + cls, plugins: List[Plugin], operation: PluginOperation, before_split: Optional[bool] = None + ) -> List[Plugin]: + ordered_types: List[Type[Plugin]] = cls._order_by( + plugin_types=[type(plugin) for plugin in plugins], operation=operation + ) + + ordered_plugins: List[Plugin] = [] + for plugin in plugins: + sorted_idx = ordered_types.index(type(plugin)) + ordered_plugins.insert(sorted_idx, plugin) + + if before_split is None: + return ordered_plugins + + # Remove the split plugin if differentiating + ordered_plugins = [ + plugin for plugin in ordered_plugins if not isinstance(plugin, SplitPlugin) + ] + if before_split is False: + return [ + plugin for plugin in ordered_plugins if not cls._is_modified_after_split(plugin) + ] + else: # before_split is True + return [plugin for plugin in ordered_plugins if cls._is_modified_after_split(plugin)] + @classmethod def plugins(cls) -> List[str]: """ diff --git a/src/ytdl_sub/config/plugin_operation.py b/src/ytdl_sub/config/plugin_operation.py new file mode 100644 index 00000000..2c187f30 --- /dev/null +++ b/src/ytdl_sub/config/plugin_operation.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class PluginOperation(Enum): + DOWNLOADER = -1 + MODIFY_ENTRY_METADATA = 0 + MODIFY_ENTRY = 1 + POST_PROCESS = 2 diff --git a/src/ytdl_sub/config/preset.py b/src/ytdl_sub/config/preset.py index b1ba4347..e7e23713 100644 --- a/src/ytdl_sub/config/preset.py +++ b/src/ytdl_sub/config/preset.py @@ -16,9 +16,9 @@ from ytdl_sub.config.config_validator import ConfigValidator from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.plugin import Plugin from ytdl_sub.config.plugin_mapping import PluginMapping +from ytdl_sub.config.plugin_operation import PluginOperation from ytdl_sub.config.preset_options import OptionsValidator from ytdl_sub.config.preset_options import OutputOptions -from ytdl_sub.config.preset_options import PluginOperation from ytdl_sub.config.preset_options import TOptionsValidator from ytdl_sub.config.preset_options import YTDLOptions from ytdl_sub.downloaders.url.validators import MultiUrlValidator @@ -83,13 +83,13 @@ class PresetPlugins: self.plugin_options.append(plugin_options) return self - def zipped(self) -> Iterable[Tuple[Type[Plugin], OptionsValidator]]: + def zipped(self) -> List[Tuple[Type[Plugin], OptionsValidator]]: """ Returns ------- Plugin and PluginOptions zipped """ - return zip(self.plugin_types, self.plugin_options) + return list(zip(self.plugin_types, self.plugin_options)) def get(self, plugin_type: Type[TOptionsValidator]) -> Optional[TOptionsValidator]: """ @@ -211,8 +211,8 @@ class Preset(_PresetShell): script.add(ScriptUtils.add_dummy_variables(added_variables)) unresolved_variables -= added_variables - for _, plugin_options in sorted( - self.plugins.zipped(), key=lambda pl: pl[0].priority.modify_entry_metadata + for plugin_options in PluginMapping.order_options_by( + self.plugins.zipped(), PluginOperation.MODIFY_ENTRY_METADATA ): added_variables = plugin_options.added_source_variables( unresolved_variables=unresolved_variables @@ -223,8 +223,8 @@ class Preset(_PresetShell): unresolved_variables -= added_variables _ = script.resolve(unresolvable=unresolved_variables, update=True) - for _, plugin_options in sorted( - self.plugins.zipped(), key=lambda pl: pl[0].priority.modify_entry + for plugin_options in PluginMapping.order_options_by( + self.plugins.zipped(), PluginOperation.MODIFY_ENTRY ): added_variables = plugin_options.added_source_variables( unresolved_variables=unresolved_variables diff --git a/src/ytdl_sub/config/preset_options.py b/src/ytdl_sub/config/preset_options.py index b5bf3502..6eb88054 100644 --- a/src/ytdl_sub/config/preset_options.py +++ b/src/ytdl_sub/config/preset_options.py @@ -1,13 +1,12 @@ from abc import ABC -from enum import Enum from typing import Any from typing import Dict -from typing import List from typing import Optional from typing import Set from typing import TypeVar from ytdl_sub.config.defaults import DEFAULT_DOWNLOAD_ARCHIVE_NAME +from ytdl_sub.config.plugin_operation import PluginOperation from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.validators.file_path_validators import OverridesStringFormatterFilePathValidator from ytdl_sub.validators.file_path_validators import StringFormatterFileNameValidator @@ -21,12 +20,6 @@ from ytdl_sub.validators.validators import LiteralDictValidator from ytdl_sub.validators.validators import Validator -class PluginOperation(Enum): - DOWNLOADER = -1 - MODIFY_ENTRY_METADATA = 0 - MODIFY_ENTRY = 1 - - # pylint: disable=no-self-use # pylint: disable=unused-argument class OptionsValidator(Validator, ABC): diff --git a/src/ytdl_sub/downloaders/url/downloader.py b/src/ytdl_sub/downloaders/url/downloader.py index 77d299a2..23cd5400 100644 --- a/src/ytdl_sub/downloaders/url/downloader.py +++ b/src/ytdl_sub/downloaders/url/downloader.py @@ -12,7 +12,6 @@ from typing import Tuple from yt_dlp.utils import RejectedVideoReached from ytdl_sub.config.overrides import Overrides -from ytdl_sub.config.plugin import PluginPriority from ytdl_sub.downloaders.source_plugin import SourcePlugin from ytdl_sub.downloaders.source_plugin import SourcePluginExtension from ytdl_sub.downloaders.url.validators import MultiUrlValidator @@ -40,8 +39,6 @@ class URLDownloadState: class UrlDownloaderThumbnailPlugin(SourcePluginExtension): - priority = PluginPriority(modify_entry=0) - def __init__( self, options: MultiUrlValidator, @@ -145,8 +142,6 @@ class UrlDownloaderThumbnailPlugin(SourcePluginExtension): class UrlDownloaderCollectionVariablePlugin(SourcePluginExtension): - priority = PluginPriority(modify_entry_metadata=0) - def __init__( self, options: MultiUrlValidator, diff --git a/src/ytdl_sub/downloaders/url/validators.py b/src/ytdl_sub/downloaders/url/validators.py index 40840cfa..e3727440 100644 --- a/src/ytdl_sub/downloaders/url/validators.py +++ b/src/ytdl_sub/downloaders/url/validators.py @@ -4,8 +4,8 @@ from typing import Dict from typing import Optional from typing import Set +from ytdl_sub.config.plugin_operation import PluginOperation from ytdl_sub.config.preset_options import OptionsValidator -from ytdl_sub.config.preset_options import PluginOperation from ytdl_sub.validators.strict_dict_validator import StrictDictValidator from ytdl_sub.validators.string_formatter_validators import DictFormatterValidator from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator diff --git a/src/ytdl_sub/plugins/audio_extract.py b/src/ytdl_sub/plugins/audio_extract.py index c0b975e8..8f80ed18 100644 --- a/src/ytdl_sub/plugins/audio_extract.py +++ b/src/ytdl_sub/plugins/audio_extract.py @@ -5,8 +5,8 @@ from typing import Optional from typing import Set from ytdl_sub.config.plugin import Plugin +from ytdl_sub.config.plugin_operation import PluginOperation from ytdl_sub.config.preset_options import OptionsDictValidator -from ytdl_sub.config.preset_options import PluginOperation from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES as v diff --git a/src/ytdl_sub/plugins/embed_thumbnail.py b/src/ytdl_sub/plugins/embed_thumbnail.py index 4886f707..5946d550 100644 --- a/src/ytdl_sub/plugins/embed_thumbnail.py +++ b/src/ytdl_sub/plugins/embed_thumbnail.py @@ -4,7 +4,6 @@ from typing import Optional import mediafile from ytdl_sub.config.plugin import Plugin -from ytdl_sub.config.plugin import PluginPriority from ytdl_sub.config.preset_options import OptionsValidator from ytdl_sub.entries.entry import Entry from ytdl_sub.utils.ffmpeg import FFMPEG @@ -33,7 +32,6 @@ class EmbedThumbnailOptions(BoolValidator, OptionsValidator): class EmbedThumbnailPlugin(Plugin[EmbedThumbnailOptions]): plugin_options_type = EmbedThumbnailOptions - priority = PluginPriority(post_process=PluginPriority.POST_PROCESS_AFTER_FILE_CONVERT) @property def _embed_thumbnail(self) -> bool: diff --git a/src/ytdl_sub/plugins/file_convert.py b/src/ytdl_sub/plugins/file_convert.py index 91862c5c..2f53f471 100644 --- a/src/ytdl_sub/plugins/file_convert.py +++ b/src/ytdl_sub/plugins/file_convert.py @@ -6,9 +6,8 @@ from typing import Set from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.plugin import Plugin -from ytdl_sub.config.plugin import PluginPriority +from ytdl_sub.config.plugin_operation import PluginOperation from ytdl_sub.config.preset_options import OptionsDictValidator -from ytdl_sub.config.preset_options import PluginOperation from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES as v from ytdl_sub.utils.exceptions import FileNotDownloadedException @@ -127,10 +126,6 @@ class FileConvertOptions(OptionsDictValidator): class FileConvertPlugin(Plugin[FileConvertOptions]): plugin_options_type = FileConvertOptions - # Perform this after regex - priority: PluginPriority = PluginPriority( - modify_entry=PluginPriority.MODIFY_ENTRY_AFTER_SPLIT + 1 - ) def __init__( self, diff --git a/src/ytdl_sub/plugins/internal/view.py b/src/ytdl_sub/plugins/internal/view.py index 940452eb..1aecd189 100644 --- a/src/ytdl_sub/plugins/internal/view.py +++ b/src/ytdl_sub/plugins/internal/view.py @@ -3,7 +3,6 @@ from typing import Optional from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.plugin import Plugin -from ytdl_sub.config.plugin import PluginPriority from ytdl_sub.config.preset_options import OptionsDictValidator from ytdl_sub.entries.entry import Entry from ytdl_sub.utils.file_handler import FileMetadata @@ -20,7 +19,6 @@ class ViewOptions(OptionsDictValidator): class ViewPlugin(Plugin[ViewOptions]): plugin_options_type = ViewOptions - priority: PluginPriority = PluginPriority(modify_entry=PluginPriority.MODIFY_ENTRY_AFTER_SPLIT) _MAX_LINE_WIDTH: int = 80 diff --git a/src/ytdl_sub/plugins/regex.py b/src/ytdl_sub/plugins/regex.py index 9442854a..e53a9873 100644 --- a/src/ytdl_sub/plugins/regex.py +++ b/src/ytdl_sub/plugins/regex.py @@ -7,9 +7,8 @@ from typing import Set from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.plugin import Plugin -from ytdl_sub.config.plugin import PluginPriority +from ytdl_sub.config.plugin_operation import PluginOperation from ytdl_sub.config.preset_options import OptionsDictValidator -from ytdl_sub.config.preset_options import PluginOperation from ytdl_sub.entries.entry import Entry from ytdl_sub.script.parser import parse from ytdl_sub.script.utils.exceptions import ScriptVariableNotResolved @@ -227,7 +226,7 @@ class RegexOptions(OptionsDictValidator): ) -> bool: if input_variable_name in unresolved_variables: return False - for capture_group_default in regex_options.capture_group_defaults: + for capture_group_default in regex_options.capture_group_defaults or []: parsed_default = parse(capture_group_default.format_string) if parsed_default.variables.issubset(unresolved_variables): return False @@ -261,9 +260,6 @@ class RegexOptions(OptionsDictValidator): class RegexPlugin(Plugin[RegexOptions]): plugin_options_type = RegexOptions - priority = PluginPriority( - modify_entry=PluginPriority.MODIFY_ENTRY_AFTER_SPLIT + 0, - ) def __init__( self, diff --git a/src/ytdl_sub/plugins/split_by_chapters.py b/src/ytdl_sub/plugins/split_by_chapters.py index c2266560..6c24c5df 100644 --- a/src/ytdl_sub/plugins/split_by_chapters.py +++ b/src/ytdl_sub/plugins/split_by_chapters.py @@ -10,8 +10,8 @@ from typing import Tuple from yt_dlp.utils import sanitize_filename from ytdl_sub.config.plugin import SplitPlugin +from ytdl_sub.config.plugin_operation import PluginOperation from ytdl_sub.config.preset_options import OptionsDictValidator -from ytdl_sub.config.preset_options import PluginOperation from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.variables.kwargs import CHAPTERS from ytdl_sub.entries.variables.kwargs import SPLIT_BY_CHAPTERS_PARENT_ENTRY diff --git a/src/ytdl_sub/plugins/subtitles.py b/src/ytdl_sub/plugins/subtitles.py index fba43ea8..356cf398 100644 --- a/src/ytdl_sub/plugins/subtitles.py +++ b/src/ytdl_sub/plugins/subtitles.py @@ -5,8 +5,8 @@ from typing import Optional from typing import Set from ytdl_sub.config.plugin import Plugin +from ytdl_sub.config.plugin_operation import PluginOperation from ytdl_sub.config.preset_options import OptionsDictValidator -from ytdl_sub.config.preset_options import PluginOperation from ytdl_sub.downloaders.ytdl_options_builder import YTDLOptionsBuilder from ytdl_sub.entries.entry import Entry from ytdl_sub.entries.script.variable_definitions import VARIABLES as v diff --git a/src/ytdl_sub/subscriptions/subscription_download.py b/src/ytdl_sub/subscriptions/subscription_download.py index 312fb46f..6686df55 100644 --- a/src/ytdl_sub/subscriptions/subscription_download.py +++ b/src/ytdl_sub/subscriptions/subscription_download.py @@ -9,6 +9,8 @@ from typing import Optional from ytdl_sub.config.plugin import Plugin from ytdl_sub.config.plugin import SplitPlugin +from ytdl_sub.config.plugin_mapping import PluginMapping +from ytdl_sub.config.plugin_operation import PluginOperation from ytdl_sub.downloaders.info_json.info_json_downloader import InfoJsonDownloader from ytdl_sub.downloaders.info_json.info_json_downloader import InfoJsonDownloaderOptions from ytdl_sub.downloaders.source_plugin import SourcePlugin @@ -206,7 +208,9 @@ class SubscriptionDownload(BaseSubscription, ABC): @classmethod def _preprocess_entry(cls, plugins: List[Plugin], entry: Entry) -> Optional[Entry]: maybe_entry: Optional[Entry] = entry - for plugin in sorted(plugins, key=lambda _plugin: _plugin.priority.modify_entry_metadata): + for plugin in PluginMapping.order_plugins_by( + plugins, PluginOperation.MODIFY_ENTRY_METADATA + ): if (maybe_entry := plugin.modify_entry_metadata(maybe_entry)) is None: return None @@ -216,7 +220,7 @@ class SubscriptionDownload(BaseSubscription, ABC): self, plugins: List[Plugin], dry_run: bool, entry: Entry, entry_metadata: FileMetadata ): # Post-process the entry with all plugins - for plugin in sorted(plugins, key=lambda _plugin: _plugin.priority.post_process): + for plugin in PluginMapping.order_plugins_by(plugins, PluginOperation.POST_PROCESS): optional_plugin_entry_metadata = plugin.post_process_entry(entry) if optional_plugin_entry_metadata: entry_metadata.extend(optional_plugin_entry_metadata) @@ -236,7 +240,7 @@ class SubscriptionDownload(BaseSubscription, ABC): entry_: Optional[Entry] = entry # First, modify the entry with all plugins - for plugin in sorted(plugins, key=lambda _plugin: _plugin.priority.modify_entry): + for plugin in PluginMapping.order_plugins_by(plugins, PluginOperation.MODIFY_ENTRY): # Break if it is None, it is indicated to not process any further if (entry_ := plugin.modify_entry(entry_)) is None: break @@ -253,18 +257,10 @@ class SubscriptionDownload(BaseSubscription, ABC): ) -> None: entry_: Optional[Entry] = entry - plugins_pre_split = sorted( - [plugin for plugin in plugins if not plugin.priority.modify_entry_after_split], - key=lambda _plugin: _plugin.priority.modify_entry, - ) - - plugins_post_split = sorted( - [plugin for plugin in plugins if plugin.priority.modify_entry_after_split], - key=lambda _plugin: _plugin.priority.modify_entry, - ) - # First, modify the entry with pre_split plugins - for plugin in plugins_pre_split: + for plugin in PluginMapping.order_plugins_by( + plugins, PluginOperation.MODIFY_ENTRY, before_split=True + ): # Break if it is None, it is indicated to not process any further if (entry_ := plugin.modify_entry(entry_)) is None: break @@ -274,7 +270,9 @@ class SubscriptionDownload(BaseSubscription, ABC): for split_entry, split_entry_metadata in split_plugin.split(entry=entry_): split_entry_: Optional[Entry] = split_entry - for plugin in plugins_post_split: + for plugin in PluginMapping.order_plugins_by( + plugins, PluginOperation.MODIFY_ENTRY, before_split=False + ): # Return if it is None, it is indicated to not process any further. # Break out of the plugin loop if (split_entry_ := plugin.modify_entry(split_entry_)) is None: