[{'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            f"to {SOURCES_BUCKET!r}/{dest_key!r}: {e}"\n        ) from e\n\n\ndef download_from_url(url: str) -> tuple[bytes, str]:\n    """Download file from HTTP(S) URL. Returns (body_bytes, content_type).\n\n    Raises RuntimeError on failure.\n    """\n    try:\n        with httpx.Client(timeout=120.0, follow_redirects=True) as client:\n            r = client.get(url)\n            r.raise_for_status()\n            ct = (\n                r.headers.get("content-type", "application/octet-stream")\n                .split(";")[0]\n                .strip()\n            )\n            return r.content, ct\n    except httpx.HTTPError as e:\n        raise RuntimeError(f"Failed to download from URL: {e}") from e\n\n\ndef _safe_filename(name: str) -> str:\n    base = name.replace("\\\\", "/").rsplit("/", 1)[-1] or "file"\n    base = re.sub(r"[^a-zA-Z0-9._-]+", "_", base).strip("_") or "file"\n    return base[:200]\n\n\ndef _object_https_url(*, bucket: str, key: str) -> str:\n    encoded = quote(key, safe="/")\n    return f"https://{bucket}.s3.{S3_REGION}.amazonaws.com/{encoded}"\n\n\ndef upload_to_sources_bucket(body: bytes, content_type: str, dest_key: str) -> str:\n    """Upload bytes to SOURCES_BUCKET. Returns permanent HTTPS URL."""\n    bucket = SOURCES_BUCKET\n    try:\n        cli = _client()\n        cli.put_object(\n            Bucket=bucket,\n            Key=dest_key,\n            Body=body,\n            ContentType=content_type,\n        )\n        return _object_https_url(bucket=bucket, key=dest_key)\n    except ClientError as e:\n        raise RuntimeError(f"S3 put_object failed bucket={bucket!r} key={dest_key!r}: {e}") from e\n\n\ndef build_sources_dest_key(\n    *,\n    operator_id: str,\n    year: int,\n    quarter: int,\n    original_filename: str,\n) -> str:\n    uid = uuid.uuid4().hex[:12]\n    safe = _safe_filename(original_filename)\n    return f"sources/{operator_id}/{year}Q{quarter}/{uid}_{safe}"\n'}]