diff --git a/pyproject.toml b/pyproject.toml index 1871d16..832eabe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.11" -s3p-sdk = "^0.2.7" +s3p-sdk = "0.2.8" [tool.poetry.group.test.dependencies] diff --git a/readme.md b/readme.md index 9842b36..fc0fdd7 100644 --- a/readme.md +++ b/readme.md @@ -211,28 +211,30 @@ pytest -v Ниже приведен пример парсера с подробным описанием. ```python from s3p_sdk.plugin.payloads.parsers import S3PParserBase -from s3p_sdk.types import S3PRefer, S3PDocument, S3PPlugin +from s3p_sdk.types import S3PRefer, S3PPlugin, S3PPluginRestrictions +from s3p_sdk.exceptions.parser import S3PPluginParserOutOfRestrictionException, S3PPluginParserFinish +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.remote.webdriver import WebDriver class MyTemplateParser(S3PParserBase): """ - Парсер плагина, который использует `S3PParserBase` + Parser plugin that uses `S3PParserBase` """ - def __init__(self, refer: S3PRefer, plugin: S3PPlugin, web_driver: WebDriver, max_count_documents: int = None, last_document: S3PDocument = None): + def __init__(self, refer: S3PRefer, plugin: S3PPlugin, restrictions: S3PPluginRestrictions, web_driver: WebDriver): """ - Конструктор парсера плагина. + Constructor for the parser plugin. - Обязательные параметры (передаются платформой): - :param:refer S3PRefer - источник, который обрабатывает плагин. - :param:plugin S3PPlugin - метаданные плагина. + Required parameters (passed by the platform): + :param refer: S3PRefer - the source processed by the plugin. + :param plugin: S3PPlugin - plugin metadata. + :param restrictions: S3PPluginRestrictions - restrictions for parsing (maximum_materials, to_last_material, from_date, to_date). - Вариативные параметры (Требуюется указать эти параметры в src//config.py): - :param:max_count_documents int - максимальное число документов, которые должен собирать парсер. - - Остальные параметры могут не передаваться в конструктор класса. Они могут быть добавлены по желанию разработчика парсера. (Требуюется указать эти параметры в src//config.py). - Но, стоит учитывать правило "все, что может быть параметризовано - должно быть параметризовано". + Other parameters can be added at the discretion of the parser developer. + (These parameters should be specified in src//config.py). + However, it's worth considering the rule "everything that can be parameterized should be parameterized". """ - super().__init__(refer, plugin, max_count_documents, last_document) + super().__init__(refer, plugin, restrictions) # Тут должны быть инициализированы свойства, характерные для этого парсера. Например: WebDriver self._driver = web_driver @@ -240,16 +242,27 @@ class MyTemplateParser(S3PParserBase): def _parse(self) -> None: """ - Главные метод класса парсера, перегружающий метод класса `S3PParserBase`. + The main method of the parser class, overriding the method of the `S3PParserBase` class. - Этот метод будет вызван платформой при запуске парсера. - Это обязывает разработчика парсить источник в этом методе (безусловно, разработчик может создавать дополнительные методы внутри этого класса). + This method will be called by the platform when the parser is launched. + This obliges the developer to parse the source in this method + (of course, the developer can create additional methods within this class). """ for article in self.test_data(): - - # Метод self._find(:S3PDocument) вызывается при парсинге для того, чтобы отдать найденный документ платформе. - # Разработчик обязан использовать только этот метод при парсинге. - # Разработчику не нужно думать над тем, что происходит дальше. Платформа сама остановит работу парсера при выполнении ряда условий: собрано нужное число документов. - self._find(article) + try: + # The self._find(:S3PDocument) method is called during parsing to give the found document to the platform. + # The developer must use only this method when parsing. + # The developer doesn't need to think about what happens next. + # The platform itself will stop the parser's work when certain conditions are met: + # the required number of documents has been collected, date restrictions are met, or the last document is found. + self._find(article) + except S3PPluginParserFinish as e: + # Parsing is finished due to restrictions + raise e + except S3PPluginParserOutOfRestrictionException: + # Document is out of date range, continue to next material. + # You can also use a restriction exception to skip irrelevant materials later on. + continue + ``` diff --git a/src/s3_platform_plugin_template/config.py b/src/s3_platform_plugin_template/config.py index b4389b9..89a9b3b 100644 --- a/src/s3_platform_plugin_template/config.py +++ b/src/s3_platform_plugin_template/config.py @@ -7,7 +7,7 @@ trigger, MiddlewareConfig, modules, - payload + payload, RestrictionsConfig ) from s3p_sdk.plugin.types import SOURCE from s3p_sdk.module import ( @@ -19,7 +19,13 @@ reference='my-template-source', # уникальное имя источника type=SOURCE, # Тип источника (SOURCE, ML, PIPELINE) files=['template_payload.py', ], # Список файлов, которые будут использоваться в плагине (эти файлы будут сохраняться в платформе) - is_localstorage=False + is_localstorage=False, + restrictions=RestrictionsConfig( + maximum_materials=50, + to_last_material=None, + from_date=None, + to_date=None, + ) ), task=TaskConfig( trigger=trigger.TriggerConfig( @@ -44,7 +50,6 @@ method='content', params=[ payload.entry.ModuleParamConfig(key='driver', module_name=WebDriver, bus=True), - payload.entry.ConstParamConfig(key='max_count_documents', value=50), payload.entry.ConstParamConfig(key='url', value='url to the source page'), ] diff --git a/src/s3_platform_plugin_template/template_payload.py b/src/s3_platform_plugin_template/template_payload.py index d607f5d..7df0d72 100644 --- a/src/s3_platform_plugin_template/template_payload.py +++ b/src/s3_platform_plugin_template/template_payload.py @@ -2,7 +2,7 @@ import time from s3p_sdk.plugin.payloads.parsers import S3PParserBase -from s3p_sdk.types import S3PRefer, S3PDocument, S3PPlugin +from s3p_sdk.types import S3PRefer, S3PDocument, S3PPlugin, S3PPluginRestrictions from selenium.common import NoSuchElementException from selenium.webdriver.chrome.webdriver import WebDriver from selenium.webdriver.common.by import By @@ -15,9 +15,8 @@ class MyTemplateParser(S3PParserBase): A Parser payload that uses S3P Parser base class. """ - def __init__(self, refer: S3PRefer, plugin: S3PPlugin, web_driver: WebDriver, max_count_documents: int = None, - last_document: S3PDocument = None): - super().__init__(refer, plugin, max_count_documents, last_document) + def __init__(self, refer: S3PRefer, plugin: S3PPlugin, restrictions: S3PPluginRestrictions, web_driver: WebDriver): + super().__init__(refer, plugin, restrictions) # Тут должны быть инициализированы свойства, характерные для этого парсера. Например: WebDriver self._driver = web_driver @@ -25,6 +24,7 @@ def __init__(self, refer: S3PRefer, plugin: S3PPlugin, web_driver: WebDriver, ma def _parse(self) -> None: for article in self._test_data(): + time.sleep(0.5) self._find(article) def _test_data(self) -> list[S3PDocument]: @@ -32,10 +32,16 @@ def _test_data(self) -> list[S3PDocument]: S3PDocument(None, "title-test-1", None, None, 'web-link-test-1', None, None, datetime.datetime.now(), None), S3PDocument(None, "title-test-2", None, None, 'web-link-test-2', None, None, datetime.datetime.now(), None), S3PDocument(None, "title-test-3", None, None, 'web-link-test-3', None, None, datetime.datetime.now(), None), - S3PDocument(None, "title-test-4", None, None, 'web-link-test-4', None, None, datetime.datetime.now(), None) + S3PDocument(None, "title-test-4", None, None, 'web-link-test-4', None, None, datetime.datetime.now(), None), + S3PDocument(None, "title-test-5", None, None, 'web-link-test-5', None, None, datetime.datetime.now(), None), + S3PDocument(None, "title-test-6", None, None, 'web-link-test-6', None, None, datetime.datetime.now(), None), + S3PDocument(None, "title-test-7", None, None, 'web-link-test-7', None, None, datetime.datetime.now(), None), + S3PDocument(None, "title-test-8", None, None, 'web-link-test-8', None, None, datetime.datetime.now(), None), + S3PDocument(None, "title-test-9", None, None, 'web-link-test-9', None, None, datetime.datetime.now(), None), ] return out + def _example_parse_page(self, url: str) -> S3PDocument: doc = self._example_page_init(url) return doc diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 60d44b1..e495480 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -8,8 +8,9 @@ from tests.config.fixtures import fix_plugin_config, project_config from s3p_sdk.plugin.config import ( - PluginConfig, CoreConfig, TaskConfig, MiddlewareConfig, PayloadConfig, + PluginConfig, CoreConfig, TaskConfig, MiddlewareConfig, PayloadConfig, RestrictionsConfig ) +import s3p_sdk.module as s3p_module class PluginStructure: @@ -46,9 +47,12 @@ def test_config_plugin_structure(self, fix_plugin_config): _cplugin = fix_plugin_config.__dict__.get(PluginStructure.PLUGIN) assert isinstance(_cplugin.__dict__.get('reference'), str) - assert isinstance(_cplugin.__dict__.get('type'), str) and str(_cplugin.__dict__.get('type')) in (SOURCE, ML, PIPELINE) - assert isinstance(_cplugin.__dict__.get('files'), list) and all([isinstance(it, str) for it in _cplugin.__dict__.get('files')]) + assert isinstance(_cplugin.__dict__.get('type'), str) and str(_cplugin.__dict__.get('type')) in ( + SOURCE, ML, PIPELINE) + assert isinstance(_cplugin.__dict__.get('files'), list) and all( + [isinstance(it, str) for it in _cplugin.__dict__.get('files')]) assert isinstance(_cplugin.__dict__.get('is_localstorage'), bool) + assert isinstance(_cplugin.__dict__.get('restrictions'), RestrictionsConfig) def test_config_plugin_files(self, fix_plugin_config, project_config): """Проверка наличия файлов плагина""" @@ -76,8 +80,10 @@ def test_config_payload_entry_structure(self, fix_plugin_config, project_config) _pentry = fix_plugin_config.__dict__.get(PluginStructure.PAYLOAD).__dict__.get('entry') assert isinstance(_pentry.__dict__.get('method'), str) - assert _pentry.__dict__.get('method') == 'content', f"Метод запуска плагина {_pentry.__dict__.get('method')} не соответствуе значению по умолчанию `content`" - assert isinstance(_pentry.__dict__.get('params'), list) and all([isinstance(it, AbcParamConfig) for it in _pentry.__dict__.get('params')]) + assert _pentry.__dict__.get( + 'method') == 'content', f"Метод запуска плагина {_pentry.__dict__.get('method')} не соответствуе значению по умолчанию `content`" + assert isinstance(_pentry.__dict__.get('params'), list) and all( + [isinstance(it, AbcParamConfig) for it in _pentry.__dict__.get('params')]) def test_config_plugin_files(self, fix_plugin_config, project_config): """Проверка наличия файлов плагина""" @@ -111,3 +117,17 @@ def test_compare_entry_file_and_plugin_files(self, fix_plugin_config): _cplugin = fix_plugin_config.__dict__.get(PluginStructure.PLUGIN) assert _cpayload.__dict__.get('file') in _cplugin.__dict__.get('files') + + +@pytest.mark.pre_set +class TestConfigMiddleware: + + def test_modules_order(self, fix_plugin_config): + for i, module in enumerate(fix_plugin_config.middleware.modules): + assert module.order == i + 1, f"Module {module.name} should have order {i + 1}" + + def test_modules_key_params(self, fix_plugin_config): + for i, module in enumerate(fix_plugin_config.middleware.modules): + assert isinstance(module.order, int) + assert isinstance(module.name, str) + assert isinstance(module.is_critical, bool) diff --git a/tests/payload/test_plugin_run.py b/tests/payload/test_plugin_run.py index 2e3cf58..4dc74a2 100644 --- a/tests/payload/test_plugin_run.py +++ b/tests/payload/test_plugin_run.py @@ -14,7 +14,7 @@ from tests.config.fixtures import fix_plugin_config, project_config from tests.payload.fixtures import execute_timeout -from s3p_sdk.types import S3PRefer, S3PDocument, S3PPlugin +from s3p_sdk.types import S3PRefer, S3PDocument, S3PPlugin, S3PPluginRestrictions from s3p_sdk.plugin.types import SOURCE @@ -65,7 +65,7 @@ def run_payload(self, payload: Type[S3PParserBase], _plugin: S3PPlugin, driver: # !WARNING Требуется изменить путь до актуального парсера плагина from src.s3_platform_plugin_template.template_payload import MyTemplateParser if isinstance(payload, type(MyTemplateParser)): - _payload = payload(refer=refer, plugin=_plugin, web_driver=driver, max_count_documents=max_document, last_document=None) + _payload = payload(refer=refer, plugin=_plugin, restrictions=S3PPluginRestrictions(max_document, None, None, None), web_driver=driver) @execute_timeout(timeout) def execute() -> tuple[S3PDocument, ...]: