"""HTTP clients for ensure-artists dependencies.""" from typing import TypedDict from src import config class TransferJob(TypedDict): """Subset of transfer job fields required by this lambda.""" destination_vendor_id: int originating_artist_id: int | None class TransferProduct(TypedDict): """Subset of product snapshot fields required by this lambda.""" release_id: int source_artist_id: int | None destination_artist_id: int | None source_video_artist_id: int | None destination_video_artist_id: int | None class DestinationArtistUpdate(TypedDict): """PATCH payload item for ows-project-manager.""" release_id: int destination_artist_id: int destination_video_artist_id: int | None class SetDestinationArtistsResult(TypedDict): """Subset of PATCH result required by this lambda.""" updated_count: int def _expect_json_dict( response_name: str, job_id: int, status_code: int, body: str, payload: object ) -> dict[object, object]: if status_code != 200: raise RuntimeError(f"{response_name} failed for job {job_id}: status={status_code} body={body}") if not isinstance(payload, dict): raise RuntimeError( f"{response_name} returned unexpected payload type for job {job_id}: {type(payload).__name__}" ) return payload def _expect_optional_int(value: object, field: str, response_name: str, job_id: int) -> int | None: if value is None: return None if type(value) is not int: raise RuntimeError(f"{response_name} returned invalid '{field}' for job {job_id}: {value!r}") return value def get_transfer_job(job_id: int) -> TransferJob: """GET /transfer/job/{job_id} on ows-project-manager.""" response = config.ows_client.get("ows-project-manager", path=f"/transfer/job/{job_id}") try: payload = response.json() except ValueError as exc: raise RuntimeError(f"get_transfer_job returned non-JSON response for job {job_id}") from exc payload_dict = _expect_json_dict("get_transfer_job", job_id, response.status_code, response.text, payload) destination_vendor_id = payload_dict.get("destination_vendor_id") if type(destination_vendor_id) is not int: raise RuntimeError( f"get_transfer_job returned invalid 'destination_vendor_id' for job {job_id}: {destination_vendor_id!r}" ) originating_artist_id = payload_dict.get("originating_artist_id") if originating_artist_id is not None and type(originating_artist_id) is not int: raise RuntimeError( f"get_transfer_job returned invalid 'originating_artist_id' for job {job_id}: {originating_artist_id!r}" ) return {"destination_vendor_id": destination_vendor_id, "originating_artist_id": originating_artist_id} def get_transfer_job_products(job_id: int, destination_vendor_id: int) -> list[TransferProduct]: """GET /transfer/job/{job_id}/products on ows-project-manager.""" response = config.ows_client.get( "ows-project-manager", path=f"/transfer/job/{job_id}/products", headers={ "Grass-Account-Type": "vendor", "Grass-Account-Id": str(destination_vendor_id), }, ) if response.status_code != 200: raise RuntimeError( f"get_transfer_job_products failed for job {job_id}: status={response.status_code} body={response.text}" ) try: payload = response.json() except ValueError as exc: raise RuntimeError(f"get_transfer_job_products returned non-JSON response for job {job_id}") from exc if not isinstance(payload, list): raise RuntimeError( f"get_transfer_job_products returned unexpected payload type for job {job_id}: {type(payload).__name__}" ) products: list[TransferProduct] = [] for index, item in enumerate(payload): if not isinstance(item, dict): raise RuntimeError( f"get_transfer_job_products returned non-object item at index {index} for job {job_id}: {item!r}" ) release_id = item.get("release_id") if type(release_id) is not int: raise RuntimeError( f"get_transfer_job_products returned invalid 'release_id' at index {index} for job {job_id}: {release_id!r}" ) products.append( { "release_id": release_id, "source_artist_id": _expect_optional_int( item.get("source_artist_id"), "source_artist_id", "get_transfer_job_products", job_id ), "destination_artist_id": _expect_optional_int( item.get("destination_artist_id"), "destination_artist_id", "get_transfer_job_products", job_id, ), "source_video_artist_id": _expect_optional_int( item.get("source_video_artist_id"), "source_video_artist_id", "get_transfer_job_products", job_id, ), "destination_video_artist_id": _expect_optional_int( item.get("destination_video_artist_id"), "destination_video_artist_id", "get_transfer_job_products", job_id, ), } ) return products def bulk_ensure_artists(source_artist_ids: list[int], destination_vendor_id: int) -> dict[str, int]: """POST /artist/bulk-ensure on ows-artist.""" response = config.ows_client.post( "ows-artist", path="/artist/bulk-ensure", headers={ "Grass-Account-Type": "vendor", "Grass-Account-Id": str(destination_vendor_id), }, json={ "source_artist_ids": source_artist_ids, "destination_vendor_id": destination_vendor_id, }, ) try: payload = response.json() except ValueError as exc: raise RuntimeError( f"bulk_ensure_artists returned non-JSON response for destination vendor {destination_vendor_id}" ) from exc payload_dict = _expect_json_dict( "bulk_ensure_artists", destination_vendor_id, response.status_code, response.text, payload, ) mapping_payload = payload_dict.get("mapping") if not isinstance(mapping_payload, dict): raise RuntimeError( f"bulk_ensure_artists returned invalid 'mapping' for destination vendor {destination_vendor_id}: " f"{mapping_payload!r}" ) mapping: dict[str, int] = {} for source_artist_id, destination_artist_id in mapping_payload.items(): if not isinstance(source_artist_id, str) or type(destination_artist_id) is not int: raise RuntimeError( "bulk_ensure_artists returned invalid mapping entry for destination " f"vendor {destination_vendor_id}: {source_artist_id!r} -> {destination_artist_id!r}" ) mapping[source_artist_id] = destination_artist_id return mapping def update_transfer_job(job_id: int, destination_artist_id: int) -> None: """PATCH /transfer/job/{job_id} on ows-project-manager to set destination_artist_id.""" response = config.ows_client.patch( "ows-project-manager", path=f"/transfer/job/{job_id}", json={"destination_artist_id": destination_artist_id}, ) if response.status_code != 200: raise RuntimeError( f"update_transfer_job failed for job {job_id}: status={response.status_code} body={response.text}" ) def set_destination_artists(job_id: int, updates: list[DestinationArtistUpdate]) -> SetDestinationArtistsResult: """PATCH /transfer/job/{job_id}/products on ows-project-manager.""" response = config.ows_client.patch( "ows-project-manager", path=f"/transfer/job/{job_id}/products", json={"updates": updates}, ) try: payload = response.json() except ValueError as exc: raise RuntimeError(f"set_destination_artists returned non-JSON response for job {job_id}") from exc payload_dict = _expect_json_dict("set_destination_artists", job_id, response.status_code, response.text, payload) updated_count = payload_dict.get("updated_count") if type(updated_count) is not int: raise RuntimeError( f"set_destination_artists returned invalid 'updated_count' for job {job_id}: {updated_count!r}" ) return {"updated_count": updated_count}