import re import io from datetime import datetime, date from typing import List, Optional import config from services.s3_client import S3Client from workers.base_worker import BaseWorker from utils import db_utils, encoding from workers.prs.models import PRSDataFile from models.projects_import_info import ProjectsImportInfo FILE_REGEX = r"^prs\/Decibel_(?PMPS_budget|PRS_po|Sub_Project|mps_category|mps_group)_(?P.*)\.txt" FILE_ORDER = { "sub_project": 1, "mps_category": 2, "mps_group": 3, "mps_budget": 4, "prs_po": 5, } TABLE_NAME_MAPS = { "sub_project": "RawPRSProjects", "mps_category": "RawPRSBudgetCategory", "mps_group": "RawPRSBudgetGroup", "mps_budget": "RawPRSBudgets", "prs_po": "RawPRSPurchaseOrders", } class PRSDataImportError(Exception): details: str def __init__(self, details: str): self.details = details class PRSDataImporter(BaseWorker): client: S3Client worker_name = "PRSDataImporter" def should_log_exceptions(self): return True def execute(self): self.logger.info("PRS Data import", "Started") self.client = S3Client(config.SFTP_BUCKET) last_import = ProjectsImportInfo.get_imported_prs_date() or date.min aws_files = self.get_file_list() most_recent_files = sorted(aws_files, key=lambda x: x.timestamp, reverse=True)[:5] files_to_import = list(filter(lambda x: x.timestamp.date() > last_import, most_recent_files)) if not files_to_import: files_names = ", ".join([file.file_name for file in aws_files]) error_details = f"No data files found since {last_import}. Got: {files_names}" self.logger.error("PRS Data import error", error_details) raise PRSDataImportError(error_details) for item in files_to_import: file_object = self.client.get_object(item.file_name, encoding.UTF8).replace("\n\r\n", "\n") with io.StringIO(file_object) as file: if item.is_budget_taxonomy_file(): db_utils.update_data_from(file=file, table_name=TABLE_NAME_MAPS[item.type.lower()]) else: db_utils.import_data_from(file=file, table_name=TABLE_NAME_MAPS[item.type.lower()]) ProjectsImportInfo.update_imported_prs_date() self.logger.success("PRS Data import", "Finished!") def get_file_list(self) -> List[PRSDataFile]: files = self.client.get_objects_list("prs/") data = [self.__map_from_file(file["Key"]) for file in files] data = [item for item in data if item is not None] data.sort(key=lambda x: FILE_ORDER[x.type.lower()]) return data def __map_from_file(self, file_name: str) -> Optional[PRSDataFile]: match = re.compile(FILE_REGEX, re.IGNORECASE).match(file_name) if not match: if not file_name.endswith(".trg"): self.logger.error("PRS Data file error", f"Invalid file name: {file_name}") return None type = match.group("type") timestamp = match.group("timestamp") try: timestamp = datetime.strptime(timestamp, "%Y%m%d") except ValueError: self.logger.error("PRS Data file error", f"Invalid date format: {timestamp}") return None return PRSDataFile(type, timestamp, file_name)