From 118d249ce3775ea5d8bb5ae8e635e6503fff3331 Mon Sep 17 00:00:00 2001 From: Aidan Rowe Date: Wed, 25 Jan 2023 21:04:09 +1000 Subject: [PATCH] refactor so estimate release is configurable --- .../estimate_release/estimate_release.py | 20 ++-- flexget/plugins/input/discover.py | 44 ++------ flexget/tests/test_discover.py | 100 +++++++++++++++++- 3 files changed, 119 insertions(+), 45 deletions(-) diff --git a/flexget/components/estimate_release/estimate_release.py b/flexget/components/estimate_release/estimate_release.py index 132fa821c5..3f9ff49a2d 100644 --- a/flexget/components/estimate_release/estimate_release.py +++ b/flexget/components/estimate_release/estimate_release.py @@ -22,9 +22,8 @@ def init_estimators(manager) -> None: """Prepare the list of available estimator plugins.""" - estimators = { - p.name.replace('est_', ''): p for p in plugin.get_plugins(interface=ESTIMATOR_INTERFACE) - } + for provider in plugin.get_plugins(interface=ESTIMATOR_INTERFACE): + estimators[provider.name.replace('est_', '')] = provider logger.debug('setting default estimators to {}', list(estimators.keys())) @@ -35,6 +34,9 @@ class EstimateRelease: for various things (series, movies). """ + def __init__(self): + self.task_estimate_config = {} + @property def schema(self) -> Dict[str, Any]: """Create schema for that allows configuring estimator providers and @@ -83,12 +85,16 @@ def get_estimators(self) -> List[PluginInfo]: """ if "providers" in self.task_estimate_config: # Map task configured providers to plugin instance map - task_estimators = [ - estimators[p].instance.estimate for p in self.task_estimate_config['providers'] - ] + try: + task_estimators = [ + estimators[p].instance.estimate for p in self.task_estimate_config['providers'] + ] + except KeyError as error: + logger.error(f"invalid provider plugin given: {error}") + raise else: # Use all loaded estimator plugins - task_estimators = [e.instance.estimate for e in estimators] + task_estimators = [e.instance.estimate for e in estimators.values()] return sorted( task_estimators, diff --git a/flexget/plugins/input/discover.py b/flexget/plugins/input/discover.py index 6e584bc1e4..146d6979c7 100644 --- a/flexget/plugins/input/discover.py +++ b/flexget/plugins/input/discover.py @@ -86,18 +86,9 @@ class Discover: }, 'interval': {'type': 'string', 'format': 'interval', 'default': '5 hours'}, 'release_estimations': { - 'oneOf': [ - { - 'type': 'string', - 'default': 'strict', - 'enum': ['loose', 'strict', 'ignore', 'smart'], - }, - { - 'type': 'object', - 'properties': {'optimistic': {'type': 'string', 'format': 'interval'}}, - 'required': ['optimistic'], - }, - ] + 'type': 'string', + 'default': 'strict', + 'enum': ['loose', 'strict', 'ignore', 'smart'], }, 'limit': {'type': 'integer', 'minimum': 1}, }, @@ -190,21 +181,21 @@ def estimated(self, entries, estimation_mode): data_exists = estimation['data_exists'] if est_date is None: - if estimation_mode['mode'] == 'strict': + if estimation_mode == 'strict': logger.debug('No release date could be determined for {}', entry['title']) entry.reject('has no release date') entry.complete() - elif estimation_mode['mode'] == 'smart' and data_exists: + elif estimation_mode == 'smart' and data_exists: logger.debug( - 'No release date could be determined for {}, but exists data', + 'No release date could be determined for {}, but data exists', entry['title'], ) entry.reject('exists but has no release date') entry.complete() - elif estimation_mode['mode'] == 'smart' and not data_exists: + elif estimation_mode == 'smart' and not data_exists: logger.debug( 'Discovering because mode is \'{}\' and no data is found for entry', - estimation_mode['mode'], + estimation_mode, ) result.append(entry) else: @@ -216,16 +207,6 @@ def estimated(self, entries, estimation_mode): if datetime.datetime.now() >= est_date: logger.debug('{} has been released at {}', entry['title'], est_date) result.append(entry) - elif datetime.datetime.now() >= est_date - parse_timedelta( - estimation_mode['optimistic'] - ): - logger.debug( - '{} will be released at {}. Ignoring release estimation because estimated release date is in less than {}', - entry['title'], - est_date, - estimation_mode['optimistic'], - ) - result.append(entry) else: entry.reject('has not been released') entry.complete() @@ -292,13 +273,6 @@ def interval_expired(self, config, task, entries): return result def on_task_input(self, task, config): - config.setdefault('release_estimations', {}) - if not isinstance(config['release_estimations'], dict): - config['release_estimations'] = {'mode': config['release_estimations']} - - config['release_estimations'].setdefault('mode', 'strict') - config['release_estimations'].setdefault('optimistic', '0 days') - task.no_entries_ok = True entries = aggregate_inputs(task, config['what']) logger.verbose('Discovering {} titles ...', len(entries)) @@ -310,7 +284,7 @@ def on_task_input(self, task, config): # TODO: the entries that are estimated should be given priority over expiration entries = self.interval_expired(config, task, entries) estimation_mode = config['release_estimations'] - if estimation_mode['mode'] != 'ignore': + if estimation_mode != 'ignore': entries = self.estimated(entries, estimation_mode) return self.execute_searches(config, entries, task) diff --git a/flexget/tests/test_discover.py b/flexget/tests/test_discover.py index 1ac845efd9..873076bcdc 100644 --- a/flexget/tests/test_discover.py +++ b/flexget/tests/test_discover.py @@ -1,4 +1,7 @@ from datetime import datetime, timedelta +from unittest.mock import MagicMock, patch + +import pytest from flexget import plugin from flexget.entry import Entry @@ -30,14 +33,14 @@ def search(self, task, entry, config=None): plugin.register(SearchPlugin, 'test_search', interfaces=['search'], api_ver=2) -class EstRelease: +class FakeEstimator: """Fake release estimate plugin. Just returns 'est_release' entry field.""" def estimate(self, entry): return entry.get('est_release') -plugin.register(EstRelease, 'test_release', interfaces=['estimate_release'], api_ver=2) +plugin.register(FakeEstimator, 'fake_estimator', interfaces=['estimate_release'], api_ver=2) class TestDiscover: @@ -225,7 +228,7 @@ class TestEmitSeriesInDiscover: begin: s02e01 identified_by: ep season_packs: yes - max_reruns: 0 + max_reruns: 0 """ def test_next_series_episodes_rerun(self, execute_task): @@ -282,3 +285,94 @@ def test_next_series_seasons_with_completed_seasons(self, execute_task): ) task = execute_task('test_next_series_seasons') assert task.find_entry(title='My Show 2 S03') + + +class TestEstimateReleaseViaDiscover: + """Suite of tests focusing on the configuration of the estimate_release + plugin. + """ + + config = """ + tasks: + test_estimates: + discover: + interval: 0 seconds + what: + - mock: + - title: Foo + from: + - test_search: yes + """ + + def test_default_release_date_modifier(self, execute_task, manager): + """Test that the default release_date_modifier value of '0 days' + results in only matching entries released in the past. + """ + mock_config = manager.config['tasks']['test_estimates']['discover']['what'][0]['mock'] + # It should not be searched before the release date + mock_config[0]['est_release'] = { + 'data_exists': True, + 'entity_date': (datetime.now() + timedelta(days=1)), + } + task = execute_task('test_estimates') + assert len(task.entries) == 0 + # It should be searched after the release date + mock_config[0]['est_release'] = {'data_exists': True, 'entity_date': datetime.now()} + task = execute_task('test_estimates') + assert len(task.entries) == 1 + + def test_release_date_modifier_positive(self, execute_task, manager): + """Test that providing a 'positive' offset value for the + estimate_release config results in matching entries that have been + released far enough in the past. + """ + manager.config['tasks']['test_estimates']['estimate_release'] = {"offset": '7 days'} + discover_config = manager.config['tasks']['test_estimates']['discover'] + mock_config = discover_config['what'][0]['mock'] + mock_config[0]['est_release'] = { + 'data_exists': True, + 'entity_date': datetime.now(), + } + task = execute_task('test_estimates') + assert len(task.entries) == 0 + mock_config[0]['est_release'] = { + 'data_exists': True, + 'entity_date': (datetime.now() - timedelta(days=7)), + } + task = execute_task('test_estimates') + assert len(task.entries) == 1 + + def test_release_date_modifier_negative(self, execute_task, manager): + """Test that providing a 'negative' offset value for the + estimate_release config results in matching entries that have a release + date in the future. + """ + manager.config['tasks']['test_estimates']['estimate_release'] = {"offset": '-7 days'} + discover_config = manager.config['tasks']['test_estimates']['discover'] + mock_config = discover_config['what'][0]['mock'] + mock_config[0]['est_release'] = { + 'data_exists': True, + 'entity_date': datetime.now() + timedelta(days=5), + } + task = execute_task('test_estimates') + assert len(task.entries) == 1 + mock_config[0]['est_release'] = { + 'data_exists': True, + 'entity_date': (datetime.now() + timedelta(days=9)), + } + task = execute_task('test_estimates') + assert len(task.entries) == 0 + + def test_provider_override_invalid(self, execute_task, manager): + """Test that an invalid provider results in an exception being raised.""" + manager.config['tasks']['test_estimates']['estimate_release'] = { + "providers": ['does-not-exist'] + } + discover_config = manager.config['tasks']['test_estimates']['discover'] + mock_config = discover_config['what'][0]['mock'] + mock_config[0]['est_release'] = { + 'data_exists': True, + 'entity_date': datetime.now() + timedelta(days=5), + } + with pytest.raises(Exception): + execute_task('test_estimates')