mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice 3/6 of the postcode_splitter refactor (Hestia-Homes/Model#1101). Introduces a thin typed infrastructure layer wrapping boto3 for the AWS side of the splitter. S3Client/SqsClient are bucket-/queue-bound byte adapters; CsvS3Client subclasses S3Client to round-trip CSV row dicts via the existing parse_s3_uri helper in utils/s3.py; Address2UprnQueueClient subclasses SqsClient to publish the typed {task_id, sub_task_id, s3_uri} fan-out body the downstream consumer expects. moto[s3,sqs] is pulled into test.requirements.txt and the new tests/infrastructure/ suite exercises each client against the moto backend (S3 round-trip, CSV round-trip, SQS send + body inspection, typed publish + body inspection). pyright --strict is clean on the new modules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
31 lines
1.1 KiB
Python
31 lines
1.1 KiB
Python
from typing import Any
|
|
|
|
|
|
class S3Client:
|
|
"""Thin typed wrapper around a boto3 S3 client bound to a single bucket.
|
|
|
|
The class is deliberately small: it exposes only the byte-level
|
|
operations needed by the wider infrastructure layer. Serialisation
|
|
(CSV, JSON, etc.) lives in subclasses such as :class:`CsvS3Client`.
|
|
"""
|
|
|
|
def __init__(self, boto_s3_client: Any, bucket: str) -> None:
|
|
self._client = boto_s3_client
|
|
self._bucket = bucket
|
|
|
|
@property
|
|
def bucket(self) -> str:
|
|
return self._bucket
|
|
|
|
def get_object(self, key: str) -> bytes:
|
|
"""Return the raw bytes stored at ``key`` in this client's bucket."""
|
|
response: dict[str, Any] = self._client.get_object(
|
|
Bucket=self._bucket, Key=key
|
|
)
|
|
body: bytes = response["Body"].read()
|
|
return body
|
|
|
|
def put_object(self, key: str, body: bytes) -> str:
|
|
"""Write ``body`` to ``key`` and return the canonical ``s3://`` URI."""
|
|
self._client.put_object(Bucket=self._bucket, Key=key, Body=body)
|
|
return f"s3://{self._bucket}/{key}"
|