Merge pull request #1166 from Hestia-Homes/main

Pashub fetcher dynamically instantiates sharepoint client based on received sharepoint site
This commit is contained in:
Daniel Roth 2026-06-04 10:45:08 +01:00 committed by GitHub
commit d65d7dcf51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 63 additions and 9 deletions

View file

@ -76,6 +76,8 @@ on:
required: false
TF_VAR_social_housing_wave_3_sharepoint_id:
required: false
TF_VAR_eco_sharepoint_id:
required: false
TF_VAR_pashub_email:
required: false
TF_VAR_pashub_password:
@ -159,6 +161,7 @@ jobs:
TF_VAR_osmosis_acd_sharepoint_id: ${{ secrets.TF_VAR_osmosis_acd_sharepoint_id }}
TF_VAR_private_pay_sharepoint_id: ${{ secrets.TF_VAR_private_pay_sharepoint_id }}
TF_VAR_social_housing_wave_3_sharepoint_id: ${{ secrets.TF_VAR_social_housing_wave_3_sharepoint_id }}
TF_VAR_eco_sharepoint_id: ${{ secrets.TF_VAR_eco_sharepoint_id }}
TF_VAR_pashub_email: ${{ secrets.TF_VAR_pashub_email }}
TF_VAR_pashub_password: ${{ secrets.TF_VAR_pashub_password }}
TF_VAR_pashub_coordination_email: ${{ secrets.TF_VAR_pashub_coordination_email }}
@ -210,6 +213,7 @@ jobs:
TF_VAR_osmosis_acd_sharepoint_id: ${{ secrets.TF_VAR_osmosis_acd_sharepoint_id }}
TF_VAR_private_pay_sharepoint_id: ${{ secrets.TF_VAR_private_pay_sharepoint_id }}
TF_VAR_social_housing_wave_3_sharepoint_id: ${{ secrets.TF_VAR_social_housing_wave_3_sharepoint_id }}
TF_VAR_eco_sharepoint_id: ${{ secrets.TF_VAR_eco_sharepoint_id }}
TF_VAR_pashub_email: ${{ secrets.TF_VAR_pashub_email }}
TF_VAR_pashub_password: ${{ secrets.TF_VAR_pashub_password }}
TF_VAR_pashub_coordination_email: ${{ secrets.TF_VAR_pashub_coordination_email }}

View file

@ -448,6 +448,7 @@ jobs:
TF_VAR_osmosis_acd_sharepoint_id: ${{ secrets.OSMOSIS_ACD_SHAREPOINT_ID }}
TF_VAR_private_pay_sharepoint_id: ${{ secrets.PRIVATE_PAY_SHAREPOINT_ID }}
TF_VAR_social_housing_wave_3_sharepoint_id: ${{ secrets.SOCIAL_HOUSING_WAVE_3_SHAREPOINT_ID }}
TF_VAR_eco_sharepoint_id: ${{ secrets.TF_VAR_eco_sharepoint_id }}
TF_VAR_pashub_email: ${{ secrets.PASHUB_EMAIL }}
TF_VAR_pashub_password: ${{ secrets.PASHUB_PASSWORD }}
TF_VAR_pashub_coordination_email: ${{ secrets.PASHUB_COORDINATION_EMAIL }}

View file

@ -82,6 +82,7 @@ class Settings(BaseSettings):
OSMOSIS_ACD_SHAREPOINT_ID: Optional[str] = None
PRIVATE_PAY_SHAREPOINT_ID: Optional[str] = None
SOCIAL_HOUSING_WAVE_3_SHAREPOINT_ID: Optional[str] = None
ECO_SHAREPOINT_ID: Optional[str] = None
OPENAI_API_KEY: Optional[str] = None
# Pas Hub

View file

@ -40,10 +40,6 @@ def handler(body: Dict[str, Any], context: Any) -> List[str]:
if (not pashub_email) or (not pashub_password):
raise ValueError("Pas Hub credentials not provided")
sharepoint_client = DomnaSharepointClient(
sharepoint_location=DomnaSites.SOCIAL_HOUSING_WAVE_3
)
if coordination_hub_email and coordination_hub_password:
_coord_email, _coord_password = (
coordination_hub_email,
@ -57,6 +53,16 @@ def handler(body: Dict[str, Any], context: Any) -> List[str]:
payload = PashubToAraTriggerRequest.model_validate(body)
logger.debug("Successfully validated request body")
sharepoint_client: Optional[DomnaSharepointClient] = None
if payload.sharepoint_site is not None:
try:
resolved_site = DomnaSites[payload.sharepoint_site]
sharepoint_client = DomnaSharepointClient(sharepoint_location=resolved_site)
except KeyError:
logger.warning(
f"Unrecognised sharepoint_site '{payload.sharepoint_site}'; skipping SharePoint upload"
)
service = PashubService(
pashub_client=get_pashub_client(pashub_email, pashub_password),
sharepoint_client=sharepoint_client,

View file

@ -38,7 +38,7 @@ class PashubService:
def __init__(
self,
pashub_client: PashubClient,
sharepoint_client: DomnaSharepointClient,
sharepoint_client: Optional[DomnaSharepointClient],
s3_bucket: str,
coordination_client_factory: Optional[Callable[[], PashubClient]] = None,
) -> None:
@ -111,7 +111,7 @@ class PashubService:
default_file_type=FileTypeEnum.OTHER.value,
)
if request.sharepoint_link and request.address:
if self._sharepoint_client and request.sharepoint_link and request.address:
folder_name = request.address.split("|")[0].strip()
folders = self._sharepoint_client.get_folders_in_path(request.sharepoint_link)
match = next(
@ -207,6 +207,7 @@ class PashubService:
property_folder_path: str,
files: List[DownloadedFile],
) -> None:
assert self._sharepoint_client is not None
for df in files:
filename = os.path.basename(df.file_path)
try:

View file

@ -14,6 +14,8 @@ class PashubToAraTriggerRequest(BaseModel):
hubspot_listing_id: Optional[int] = None
hubspot_deal_id: Optional[str] = None
sharepoint_site: Optional[str] = None
get_other_files: bool = False
@property

View file

@ -616,6 +616,33 @@ def test_sharepoint_skips_upload_when_folder_not_found() -> None:
mock_logger.warning.assert_called()
def test_sharepoint_skips_upload_when_sharepoint_client_is_none() -> None:
# Arrange
mock_client = MagicMock(spec=PashubClient)
mock_client.get_uprn_by_job_id.return_value = None
mock_client.get_evidence_files_by_job_id.return_value = make_downloaded(
core=["/tmp/core.pdf"]
)
service = PashubService(
pashub_client=mock_client,
sharepoint_client=None,
s3_bucket="test-bucket",
)
# Act — should not raise AttributeError on None._sharepoint_client
with patch("backend.pashub_fetcher.pashub_service.os.remove"):
result = service.run(
make_request(
sharepoint_link="Retrofit/Properties",
address="123 Main St | deal",
)
)
# Assert
assert result == ["/tmp/core.pdf"]
def test_run_warns_and_continues_when_site_notes_parsing_fails() -> None:
mock_client = MagicMock(spec=PashubClient)
mock_client.get_uprn_by_job_id.return_value = None

View file

@ -52,12 +52,16 @@ EXCEL_PATH: str = os.path.join(
"local_run_02-06-2026/ECO_Approach_Coordination_Design_KN.xlsx",
)
SHAREPOINT_PROPERTIES_FOLDER: str = ""
SHAREPOINT_PROPERTIES_FOLDER: str = (
"Housing Associations/- Client Shared Folders/Abri/Abri Property Folders (Full PAS Info)"
)
SHAREPOINT_SITE: str = "ECO"
def _build_requests(excel_path: str) -> list[PashubToAraTriggerRequest]:
wb = load_workbook(excel_path, data_only=True)
ws = wb.worksheets[0]
ws = wb.worksheets[1]
headers: dict[str, int] = {}
for col in range(1, ws.max_column + 1):
@ -102,6 +106,7 @@ def _build_requests(excel_path: str) -> list[PashubToAraTriggerRequest]:
address=address,
deal_stage=deal_stage,
sharepoint_link=SHAREPOINT_PROPERTIES_FOLDER or None,
sharepoint_site=SHAREPOINT_SITE,
)
)
@ -123,7 +128,7 @@ def main() -> None:
for request in trigger_requests:
action: str = "DRY RUN" if DRY_RUN else "SENDING"
logger.info(
f"[{action}] deal_id={request.hubspot_deal_id} pashub_link={request.pashub_link}"
f"[{action}] deal_id={request.hubspot_deal_id} pashub_link={request.pashub_link} sharepoint_link={request.sharepoint_link}"
)
if not DRY_RUN:

View file

@ -47,6 +47,7 @@ module "lambda" {
OSMOSIS_ACD_SHAREPOINT_ID = var.osmosis_acd_sharepoint_id
PRIVATE_PAY_SHAREPOINT_ID = var.private_pay_sharepoint_id
SOCIAL_HOUSING_WAVE_3_SHAREPOINT_ID = var.social_housing_wave_3_sharepoint_id
ECO_SHAREPOINT_ID = var.eco_sharepoint_id
PASHUB_EMAIL = var.pashub_email
PASHUB_PASSWORD = var.pashub_password
PASHUB_COORDINATION_EMAIL = var.pashub_coordination_email

View file

@ -92,6 +92,11 @@ variable "social_housing_wave_3_sharepoint_id" {
sensitive = true
}
variable "eco_sharepoint_id" {
type = string
sensitive = true
}
variable "pashub_email" {
type = string
sensitive = true

View file

@ -9,3 +9,4 @@ class DomnaSites(Enum):
OSMOSIS_ACD = os.getenv("OSMOSIS_ACD_SHAREPOINT_ID")
PRIVATE_PAY = os.getenv("PRIVATE_PAY_SHAREPOINT_ID")
SOCIAL_HOUSING_WAVE_3 = os.getenv("SOCIAL_HOUSING_WAVE_3_SHAREPOINT_ID")
ECO = os.getenv("ECO_SHAREPOINT_ID")