Skip to content

Commit 265d299

Browse files
author
Kolea Plesco
committed
Notice Publisher tests sync
1 parent 3f96ce4 commit 265d299

17 files changed

Lines changed: 294 additions & 63 deletions

dags/worker_single_notice_process_orchestrator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ def _validate_transformed_rdf_manifestation(**context_args):
217217
notice_repository=notice_repository,
218218
mapping_suite_repository=mapping_suite_repository)
219219
validate_xpath_coverage_notice_by_id(notice_id=notice_id, mapping_suite_identifier=mapping_suite_id,
220-
mapping_suite_repository=mapping_suite_repository,
221-
mongodb_client=mongodb_client)
220+
notice_repository=notice_repository,
221+
mapping_suite_repository=mapping_suite_repository)
222222
push_dag_downstream(NOTICE_ID, notice_id)
223223
push_dag_downstream(MAPPING_SUITE_ID, mapping_suite_id)
224224

infra/sftp/docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ services:
88
environment:
99
- SFTP_USER=${SFTP_USER}
1010
- SFTP_PASSWORD=${SFTP_PASSWORD}
11+
- SFTP_PORT=${SFTP_PORT}
1112
command: ${SFTP_USER}:${SFTP_PASSWORD}:::upload
1213
ports:
13-
- ${SFTP_PORT:-2235}:22
14+
- ${SFTP_PORT:-2235}:${SFTP_PORT}
1415
networks:
1516
- sftp-net
1617
- proxy-net

ted_sws/__init__.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,31 @@ def TED_API_URL(self) -> str:
156156
return EnvConfigResolver().config_resolve()
157157

158158

159+
class SFTPConfig:
160+
@property
161+
def SFTP_HOST(self) -> str:
162+
return EnvConfigResolver().config_resolve()
163+
164+
@property
165+
def SFTP_PORT(self) -> int:
166+
v = EnvConfigResolver().config_resolve()
167+
return int(v) if v is not None else 22
168+
169+
@property
170+
def SFTP_USER(self) -> str:
171+
return EnvConfigResolver().config_resolve()
172+
173+
@property
174+
def SFTP_PASSWORD(self)->str:
175+
return EnvConfigResolver().config_resolve()
176+
177+
@property
178+
def SFTP_PATH(self)->str:
179+
return EnvConfigResolver().config_resolve()
180+
181+
159182
class TedConfigResolver(MongoDBConfig, RMLMapperConfig, XMLProcessorConfig, ELKConfig, LoggingConfig,
160-
GitHubArtefacts, API, AllegroConfig, TedAPIConfig):
183+
GitHubArtefacts, API, AllegroConfig, TedAPIConfig, SFTPConfig):
161184
"""
162185
This class resolve the secrets of the ted-sws project.
163186
"""
Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,29 @@
1-
from abc import ABC
2-
from ted_sws.core.model.notice import Notice
1+
from enum import Enum
32

3+
from ted_sws.notice_publisher.adapters.notice_publisher_abc import NoticePublisherABC
4+
from ted_sws.notice_publisher.adapters.sftp_notice_publisher import SFTPNoticePublisher
5+
from ted_sws import config
46

5-
class NoticePublisherABC(ABC):
6-
def upload(self, source_path, remote_path=None) -> bool:
7-
pass
7+
8+
class Publisher(Enum):
9+
SFTP = "sftp"
10+
11+
12+
DEFAULT_PUBLISHER = Publisher.SFTP
13+
14+
15+
class NoticePublisherFactory:
16+
@classmethod
17+
def get_publisher(cls, publisher: Publisher = DEFAULT_PUBLISHER, **kwargs) -> NoticePublisherABC:
18+
"""Factory Method to return the needed Publisher, based on """
19+
20+
if publisher == Publisher.SFTP:
21+
hostname = kwargs['hostname'] if 'hostname' in kwargs else config.SFTP_HOST
22+
username = kwargs['username'] if 'username' in kwargs else config.SFTP_USER
23+
password = kwargs['password'] if 'password' in kwargs else config.SFTP_PASSWORD
24+
port = kwargs['port'] if 'port' in kwargs else config.SFTP_PORT
25+
remote_path = kwargs['remote_path'] if 'remote_path' in kwargs else None
26+
if not hostname or not username or not password:
27+
raise Exception("SFTPPublisher mandatory init parameters: (host, username, password)")
28+
return SFTPNoticePublisher(hostname=hostname, username=username, password=password, port=port,
29+
remote_path=remote_path)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from abc import ABC
2+
3+
4+
class NoticePublisherABC(ABC):
5+
def connect(self, **kwargs) -> bool:
6+
"""
7+
8+
:param kwargs:
9+
:return:
10+
"""
11+
12+
def publish(self, **kwargs) -> bool:
13+
"""
14+
15+
:param kwargs:
16+
:return:
17+
"""
18+
19+
def disconnect(self, **kwargs) -> bool:
20+
"""
21+
22+
:param kwargs:
23+
:return:
24+
"""

ted_sws/notice_publisher/adapters/sftp_notice_publisher.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import pysftp
22

3-
from ted_sws.core.model.notice import Notice
4-
from ted_sws.notice_publisher.adapters.notice_publisher import NoticePublisherABC
3+
from ted_sws.notice_publisher.adapters.notice_publisher_abc import NoticePublisherABC
4+
from ted_sws import config
55

66

77
class SFTPNoticePublisher(NoticePublisherABC):
8-
def __init__(self, hostname, username, password, port=22, remote_path=None):
8+
connection: pysftp.Connection = None
9+
default_host = config.SFTP_HOST
10+
default_user = config.SFTP_USER
11+
default_pass = config.SFTP_PASSWORD
12+
default_port = config.SFTP_PORT
13+
14+
def __init__(self, hostname=default_host, username=default_user, password=default_pass, port=default_port,
15+
remote_path=None):
916
"""Constructor Method"""
1017
# Set connection object to None (initial value)
11-
self.connection = None
1218
self.hostname = hostname
1319
self.username = username
1420
self.password = password
21+
self.port = port or self.default_port
1522
self.remote_path = remote_path
16-
self.port = port
1723

1824
def connect(self):
1925
"""Connects to the sftp server and returns the sftp connection object"""
@@ -33,6 +39,9 @@ def disconnect(self):
3339
"""Closes the sftp connection"""
3440
self.connection.close()
3541

42+
def publish(self, source_path, remote_path=None) -> bool:
43+
return self.upload(source_path, remote_path)
44+
3645
def upload(self, source_path, remote_path=None) -> bool:
3746
"""
3847
Uploads the notice's METS manifestation to the sftp server remote path.
@@ -48,3 +57,10 @@ def upload(self, source_path, remote_path=None) -> bool:
4857
return True
4958
except Exception as err:
5059
raise Exception(err)
60+
61+
def remove(self, remote_path) -> bool:
62+
try:
63+
self.connection.unlink(remote_path)
64+
return True
65+
except Exception as err:
66+
raise Exception(err)
Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,80 @@
1-
from ted_sws.notice_publisher.adapters.notice_publisher import NoticePublisherABC
2-
from ted_sws.notice_publisher.adapters.sftp_notice_publisher import SFTPNoticePublisher
3-
from ted_sws.core.model.notice import Notice, NoticeStatus
4-
from ted_sws.data_manager.adapters.notice_repository import NoticeRepositoryABC
51
import base64
62
import tempfile
3+
from pathlib import Path
4+
from typing import Union
5+
6+
from ted_sws import config
7+
from ted_sws.core.model.notice import Notice, NoticeStatus, UnsupportedStatusTransition
8+
from ted_sws.data_manager.adapters.notice_repository import NoticeRepositoryABC
9+
from ted_sws.notice_packager.adapters.archiver import ARCHIVE_DEFAULT_FORMAT
10+
from ted_sws.notice_publisher.adapters.notice_publisher import NoticePublisherFactory
11+
from ted_sws.notice_publisher.adapters.notice_publisher_abc import NoticePublisherABC
12+
13+
14+
class NoticePublishBuilder:
15+
def __init__(self, notice: Union[Notice, str], notice_repository: NoticeRepositoryABC,
16+
notice_publisher: NoticePublisherABC, remote_path: str = None):
17+
self.notice_repository = notice_repository
18+
if isinstance(notice, str):
19+
notice_id = notice
20+
notice: Notice = self.notice_repository.get(reference=notice_id)
21+
if notice is None:
22+
raise Exception(f"Notice {notice_id} could not be found.")
23+
24+
self.notice = notice
25+
self.check_publish_eligibility()
726

27+
self.notice_publisher = notice_publisher
28+
self.remote_path = remote_path
829

9-
def publish_notice_package(notice: Notice, notice_publisher: NoticePublisherABC, notice_repository: NoticeRepositoryABC,
10-
remote_path=None):
11-
mets_manifestation = notice.mets_manifestation
12-
if not mets_manifestation:
13-
raise ValueError("Notice does not have a METS manifestation to be published.")
30+
def check_publish_eligibility(self):
31+
if self.notice.status != NoticeStatus.ELIGIBLE_FOR_PUBLISHING:
32+
raise UnsupportedStatusTransition(
33+
f"Notice {self.notice.ted_id} is not Eligible for Publishing. Status: {self.notice.status}")
1434

15-
package_content = base64.b64decode(bytes(mets_manifestation.object_data), validate=True)
35+
def package_content(self) -> bytes:
36+
mets_manifestation = self.notice.mets_manifestation
37+
if not mets_manifestation or not mets_manifestation.object_data:
38+
raise ValueError("Notice does not have a METS manifestation to be published.")
1639

17-
source_file = tempfile.NamedTemporaryFile()
18-
source_file.write(package_content)
40+
package_content = base64.b64decode(bytes(mets_manifestation.object_data, encoding='utf-8'), validate=True)
41+
return package_content
1942

20-
if notice_publisher.upload(source_file, remote_path):
21-
notice.update_status_to(NoticeStatus.PUBLISHED)
22-
notice_repository.update(notice)
23-
else:
24-
raise Exception(f"Notice {notice.ted_id} could not be published.")
43+
def build_remote_path(self) -> str:
44+
remote_path = self.remote_path
45+
if remote_path is None:
46+
remote_path = f"{config.SFTP_PATH}/{self.notice.ted_id}"
47+
if ARCHIVE_DEFAULT_FORMAT == "zip":
48+
remote_path += '.zip'
49+
return remote_path
2550

51+
def publish(self):
52+
source_file = tempfile.NamedTemporaryFile()
53+
source_file.write(self.package_content())
2654

27-
def publish_notice(notice: Notice, notice_repository: NoticeRepositoryABC, hostname, username, password,
28-
remote_path=None, port=22):
29-
notice_publisher = SFTPNoticePublisher(hostname=hostname, username=username, password=password, port=port,
30-
remote_path=remote_path)
55+
try:
56+
if self.notice_publisher.publish(source_path=Path(source_file.name), remote_path=self.build_remote_path()):
57+
self.notice.update_status_to(NoticeStatus.PUBLISHED)
58+
self.notice_repository.update(self.notice)
59+
except Exception as e:
60+
raise Exception(f"Notice {self.notice.ted_id} could not be published: " + str(e))
61+
62+
return self.notice.status == NoticeStatus.PUBLISHED
63+
64+
65+
def publish_notice(notice: Union[Notice, str], notice_publisher: NoticePublisherABC,
66+
notice_repository: NoticeRepositoryABC, remote_path=None) -> bool:
67+
publish_builder = NoticePublishBuilder(notice, notice_repository, notice_publisher, remote_path)
68+
return publish_builder.publish()
69+
70+
71+
def publish_single_notice(notice: Union[Notice, str], notice_repository: NoticeRepositoryABC, hostname, username,
72+
password, port=None, remote_path=None) -> bool:
73+
notice_publisher: NoticePublisherABC = NoticePublisherFactory.get_publisher(hostname=hostname, username=username,
74+
password=password, port=port,
75+
remote_path=remote_path)
3176
notice_publisher.connect()
32-
publish_notice_package(notice, notice_publisher, notice_repository)
77+
notice_published: bool = publish_notice(notice, notice_publisher, notice_repository, remote_path)
3378
notice_publisher.disconnect()
34-
pass
79+
80+
return notice_published

ted_sws/notice_validator/adapters/validation_summary_runner.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from typing import List
22

33
from jinja2 import Environment, PackageLoader
4-
from pymongo import MongoClient
54

65
from ted_sws.core.model.manifestation import ValidationSummaryReport, XMLManifestationValidationSummaryReport, \
76
RDFManifestationValidationSummaryReport, XPATHCoverageSummaryReport, XPATHCoverageSummaryResult, \
@@ -123,8 +122,6 @@ class ValidationSummaryRunner:
123122
Runs Validation Summary
124123
"""
125124

126-
mongodb_client: MongoClient
127-
128125
def __init__(self):
129126
pass
130127

ted_sws/notice_validator/services/validation_summary_runner.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from pymongo import MongoClient
2-
31
from ted_sws.core.model.manifestation import ValidationSummaryReport
42
from ted_sws.core.model.notice import Notice
53
from ted_sws.data_manager.adapters.notice_repository import NoticeRepository
@@ -29,9 +27,7 @@ def validation_summary_report_notice(notice: Notice):
2927
notice.validation_summary = report_builder.generate_report()
3028

3129

32-
def validation_summary_report_notice_by_id(notice_id: str,
33-
mongodb_client: MongoClient):
34-
notice_repository = NoticeRepository(mongodb_client=mongodb_client)
30+
def validation_summary_report_notice_by_id(notice_id: str, notice_repository: NoticeRepository):
3531
notice = notice_repository.get(reference=notice_id)
3632
if notice is None:
3733
raise ValueError(f'Notice, with {notice_id} id, was not found')

ted_sws/notice_validator/services/xpath_coverage_runner.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from typing import List
22

3-
from pymongo import MongoClient
4-
53
from ted_sws.core.model.manifestation import XPATHCoverageValidationReport
64
from ted_sws.core.model.notice import Notice
75
from ted_sws.core.model.transform import MappingSuite
@@ -27,10 +25,10 @@ def generate_report(self) -> XPATHCoverageValidationReport:
2725
def coverage_notice_xpath_report(notices: List[Notice], mapping_suite_id,
2826
conceptual_mappings_file_path: PATH_TYPE = None,
2927
coverage_runner: CoverageRunner = None, xslt_transformer=None,
30-
mongodb_client: MongoClient = None) -> XPATHCoverageValidationReport:
28+
notice_repository: NoticeRepository = None) -> XPATHCoverageValidationReport:
3129
if not coverage_runner:
3230
coverage_runner = CoverageRunner(mapping_suite_id, conceptual_mappings_file_path, xslt_transformer,
33-
mongodb_client)
31+
notice_repository.mongodb_client)
3432
report: XPATHCoverageValidationReport = coverage_runner.coverage_notice_xpath(notices, mapping_suite_id)
3533
return report
3634

@@ -43,24 +41,23 @@ def xpath_coverage_html_report(report: XPATHCoverageValidationReport) -> str:
4341
return CoverageRunner.html_report(report)
4442

4543

46-
def validate_xpath_coverage_notice(notice: Notice, mapping_suite: MappingSuite, mongodb_client: MongoClient):
44+
def validate_xpath_coverage_notice(notice: Notice, mapping_suite: MappingSuite, notice_repository: NoticeRepository):
4745
xpath_coverage_report = coverage_notice_xpath_report(notices=[notice],
4846
mapping_suite_id=mapping_suite.identifier,
49-
mongodb_client=mongodb_client)
47+
notice_repository=notice_repository)
5048
report_builder = XPATHCoverageReportBuilder(xpath_coverage_report=xpath_coverage_report)
5149
notice.set_xml_validation(xml_validation=report_builder.generate_report())
5250

5351

5452
def validate_xpath_coverage_notice_by_id(notice_id: str, mapping_suite_identifier: str,
5553
mapping_suite_repository: MappingSuiteRepositoryABC,
56-
mongodb_client: MongoClient):
57-
notice_repository = NoticeRepository(mongodb_client=mongodb_client)
54+
notice_repository: NoticeRepository):
5855
notice = notice_repository.get(reference=notice_id)
5956
if notice is None:
6057
raise ValueError(f'Notice, with {notice_id} id, was not found')
6158

6259
mapping_suite = mapping_suite_repository.get(reference=mapping_suite_identifier)
6360
if mapping_suite is None:
6461
raise ValueError(f'Mapping suite, with {mapping_suite_identifier} id, was not found')
65-
validate_xpath_coverage_notice(notice=notice, mapping_suite=mapping_suite, mongodb_client=mongodb_client)
62+
validate_xpath_coverage_notice(notice=notice, mapping_suite=mapping_suite, notice_repository=notice_repository)
6663
notice_repository.update(notice=notice)

0 commit comments

Comments
 (0)