Skip to content

Commit

Permalink
Merge pull request #4 from S3-Platform-Inc/sync/3-update-sdk-to-v0210
Browse files Browse the repository at this point in the history
sync(plugin): updated from a template (#3)
  • Loading branch information
CuberHuber authored Dec 20, 2024
2 parents 09a1b99 + cad66f1 commit df7215c
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 134 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ readme = "README.md"

[tool.poetry.dependencies]
python = "^3.11"
s3p-sdk = "^0.2.7"
s3p-sdk = "0.2.10"
pytz = "^2024.2"
dateparser = "^1.2.0"

Expand Down
65 changes: 43 additions & 22 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,15 @@ git fetch --all
```shell
git merge template/main --allow-unrelated-histories
```
3. Обновить код плагина и тестов при необходимости.
3. При возникновении конфликтов, нужно принять все изменения из template, а затем подстраивать свой код.
> [!NOTE]
> После синхронизации с шаблоном, нужно обновить зависимости.
```shell
poetry install
# или, при ошибке установки можно обновить записимость вручную.
poetry add s3p-sdk@[relevant version]
```
4. Обновить код плагина и тестов при необходимости.


## Требования к разработке плагина
Expand Down Expand Up @@ -211,45 +219,58 @@ 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/<uniq plugin name>/config.py):
:param:max_count_documents int - максимальное число документов, которые должен собирать парсер.
Остальные параметры могут не передаваться в конструктор класса. Они могут быть добавлены по желанию разработчика парсера. (Требуюется указать эти параметры в src/<uniq plugin name>/config.py).
Но, стоит учитывать правило "все, что может быть параметризовано - должно быть параметризовано".
Other parameters can be added at the discretion of the parser developer.
(These parameters should be specified in src/<uniq plugin name>/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
self._wait = WebDriverWait(self._driver, timeout=20)

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

```

18 changes: 10 additions & 8 deletions src/s3p_plugin_parser_techcrunch/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
trigger,
MiddlewareConfig,
modules,
payload
payload, RestrictionsConfig
)
from s3p_sdk.plugin.types import SOURCE
from s3p_sdk.module import (
Expand All @@ -19,7 +19,13 @@
reference='techcrunch', # уникальное имя источника
type=SOURCE, # Тип источника (SOURCE, ML, PIPELINE)
files=['techcrunch.py', ], # Список файлов, которые будут использоваться в плагине (эти файлы будут сохраняться в платформе)
is_localstorage=False
is_localstorage=False,
restrictions=RestrictionsConfig(
maximum_materials=None,
to_last_material=None,
from_date=datetime.datetime(2024, 8, 1),
to_date=None,
)
),
task=TaskConfig(
trigger=trigger.TriggerConfig(
Expand All @@ -30,8 +36,7 @@
middleware=MiddlewareConfig(
modules=[
modules.TimezoneSafeControlConfig(order=1, is_critical=True),
modules.FilterOnlyNewDocumentWithDB(order=2, is_critical=True),
modules.SaveDocument(order=3, is_critical=True),
modules.SaveOnlyNewDocuments(order=2, is_critical=True),
],
bus=None,
),
Expand All @@ -41,10 +46,7 @@
entry=payload.entry.EntryConfig(
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'),
payload.entry.ModuleParamConfig(key='web_driver', module_name=WebDriver, bus=True),
]
)
)
Expand Down
17 changes: 12 additions & 5 deletions src/s3p_plugin_parser_techcrunch/techcrunch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import dateparser
import time

from s3p_sdk.exceptions.parser import S3PPluginParserFinish, S3PPluginParserOutOfRestrictionException
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 s3p_sdk.types.plugin_restrictions import FROM_DATE
from selenium.common import NoSuchElementException
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.common.by import By
Expand All @@ -18,9 +20,8 @@ class Techcrunch(S3PParserBase):
HOST = "https://techcrunch.com/category/fintech/"
utc = pytz.UTC

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, web_driver: WebDriver, restrictions: S3PPluginRestrictions):
super().__init__(refer, plugin, restrictions)

# Тут должны быть инициализированы свойства, характерные для этого парсера. Например: WebDriver
self._driver = web_driver
Expand Down Expand Up @@ -106,7 +107,13 @@ def _parse(self):
published=pub_date,
loaded=None,
)
self._find(document)
try:
self._find(document)
except S3PPluginParserOutOfRestrictionException as e:
if e.restriction == FROM_DATE:
self.logger.debug(f'Document is out of date range `{self._restriction.from_date}`')
raise S3PPluginParserFinish(self._plugin,
f'Document is out of date range `{self._restriction.from_date}`', e)
self._driver.close()
self._driver.switch_to.window(self._driver.window_handles[0])

Expand Down
88 changes: 0 additions & 88 deletions src/s3p_plugin_parser_techcrunch/template_payload.py

This file was deleted.

30 changes: 25 additions & 5 deletions tests/config/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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):
"""Проверка наличия файлов плагина"""
Expand Down Expand Up @@ -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):
"""Проверка наличия файлов плагина"""
Expand Down Expand Up @@ -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)
Loading

0 comments on commit df7215c

Please sign in to comment.