[{'Text': '"""S3 helpers for GBS source file archival.\n\nEvery stored file for a Source lands in ``pt-research-sources``:\n\n- ``s3://bucket/key`` (same account): ``copy_object`` into the sources bucket.\n- ``http(s)://...``: GET then ``put_object`` into the sources bucket.\n\nIAM uses the normal AWS credential chain.\n"""\n\nfrom __future__ import annotations\n\nimport re\nimport uuid\nfrom urllib.parse import quote\n\nimport boto3\nimport httpx\nfrom botocore.exceptions import ClientError\n\nS3_REGION = "eu-west-1"\nSOURCES_BUCKET = "pt-research-sources"\n\n\ndef _client():\n    return boto3.client("s3", region_name=S3_REGION)\n\n\ndef parse_s3_uri(uri: str) -> tuple[str, str]:\n    """Parse ``s3://bucket/object-key``. Key may contain slashes.\n\n    Raises RuntimeError if malformed.\n    """\n    u = uri.strip()\n    if not u.lower().startswith("s3://"):\n        raise RuntimeError("expected an s3:// URI")\n    rest = u[5:]\n    slash = rest.find("/")\n    if slash < 0:\n        raise RuntimeError("s3 URI must include a key, e.g. s3://my-bucket/path/to/file.pdf")\n    bucket = rest[:slash].strip()\n    key = rest[slash + 1 :].lstrip("/")\n    if not bucket or not key:\n        raise RuntimeError("s3 URI must have non-empty bucket and key")\n    return bucket, key\n\n\ndef copy_to_sources_bucket(*, source_bucket: str, source_key: str, dest_key: str) -> str:\n    """Copy an object into ``SOURCES_BUCKET`` at ``dest_key``. Returns permanent HTTPS URL."""\n    try:\n        cli = _client()\n        cli.copy_object(\n            Bucket=SOURCES_BUCKET,\n            Key=dest_key,\n            CopySource={"Bucket": source_bucket, "Key": source_key},\n        )\n        return _object_https_url(bucket=SOURCES_BUCKET, key=dest_key)\n    except ClientError as e:\n        raise RuntimeError(\n            f"S3 copy_object failed from {source_bucket!r}/{source_key!r} "\n'}]