@@ -44,6 +44,8 @@ class DimMediaError(RuntimeError):
4444
4545@dataclass (frozen = True )
4646class JobConfig :
47+ """Job arguments that control input resolution and Delta output."""
48+
4749 job_name : str
4850 ingestion_run_id : str | None
4951 validation_report_uri : str | None
@@ -55,11 +57,15 @@ class JobConfig:
5557
5658@dataclass (frozen = True )
5759class RunInput :
60+ """The ingestion run and validation report selected for this execution."""
61+
5862 ingestion_run_id : str
5963 validation_report_uri : str
6064
6165
6266def configure_logging () -> None :
67+ """Send consistently formatted job messages to CloudWatch Logs."""
68+
6369 logging .basicConfig (
6470 level = logging .INFO ,
6571 format = "%(asctime)s %(levelname)s %(name)s %(message)s" ,
@@ -68,6 +74,8 @@ def configure_logging() -> None:
6874
6975
7076def parse_optional_argument (name : str , default : str | None = None ) -> str | None :
77+ """Read an optional --NAME value from the Glue job command line."""
78+
7179 flag = f"--{ name } "
7280 if flag not in sys .argv :
7381 return default
@@ -78,6 +86,8 @@ def parse_optional_argument(name: str, default: str | None = None) -> str | None
7886
7987
8088def load_config () -> JobConfig :
89+ """Parse Glue arguments once and return a typed configuration object."""
90+
8191 required = getResolvedOptions (sys .argv , ["JOB_NAME" ])
8292 refined_prefix = parse_optional_argument ("REFINED_PREFIX" , "refined/dim_media" )
8393 assert refined_prefix is not None
@@ -93,6 +103,8 @@ def load_config() -> JobConfig:
93103
94104
95105def workflow_context (config : JobConfig ) -> tuple [str , str ] | None :
106+ """Return complete workflow identifiers or fail on a partial configuration."""
107+
96108 if not config .workflow_name and not config .workflow_run_id :
97109 return None
98110 if not config .workflow_name or not config .workflow_run_id :
@@ -101,6 +113,8 @@ def workflow_context(config: JobConfig) -> tuple[str, str] | None:
101113
102114
103115def resolve_run_input (glue_client : Any , config : JobConfig ) -> RunInput :
116+ """Choose manual inputs first, otherwise read this workflow run's inputs."""
117+
104118 if config .ingestion_run_id or config .validation_report_uri :
105119 if not config .ingestion_run_id or not config .validation_report_uri :
106120 raise DimMediaError (
@@ -144,13 +158,17 @@ def resolve_run_input(glue_client: Any, config: JobConfig) -> RunInput:
144158
145159
146160def parse_s3_uri (uri : str ) -> tuple [str , str ]:
161+ """Split an S3 URI into bucket and key, rejecting malformed locations."""
162+
147163 parsed = urlparse (uri )
148164 if parsed .scheme != "s3" or not parsed .netloc or not parsed .path .lstrip ("/" ):
149165 raise DimMediaError (f"Invalid S3 URI: { uri !r} ." )
150166 return parsed .netloc , parsed .path .lstrip ("/" )
151167
152168
153169def read_validation_report (s3_client : Any , uri : str ) -> dict [str , Any ]:
170+ """Download and parse the validation report that points to valid raw data."""
171+
154172 bucket , key = parse_s3_uri (uri )
155173 try :
156174 body = s3_client .get_object (Bucket = bucket , Key = key )["Body" ].read ()
@@ -166,6 +184,8 @@ def resolve_raw_input(
166184 report : dict [str , Any ],
167185 expected_ingestion_run_id : str ,
168186) -> str :
187+ """Verify report lineage and return the validated raw object URI."""
188+
169189 report_run_id = report .get ("ingestion_run_id" )
170190 if report_run_id != expected_ingestion_run_id :
171191 raise DimMediaError (
@@ -180,6 +200,8 @@ def resolve_raw_input(
180200
181201
182202def valid_record_count (report : dict [str , Any ]) -> int :
203+ """Read the report's valid count as a non-negative integer."""
204+
183205 count = report .get ("valid_record_count" )
184206 if type (count ) is not int or count < 0 :
185207 raise DimMediaError ("Validation report has an invalid valid_record_count." )
@@ -190,6 +212,8 @@ def resolve_table_uri(
190212 raw_input_uri : str ,
191213 config : JobConfig ,
192214) -> str :
215+ """Use an explicit Delta location or infer one in the raw bucket."""
216+
193217 if config .dim_media_table_uri :
194218 parse_s3_uri (config .dim_media_table_uri )
195219 return config .dim_media_table_uri .rstrip ("/" )
@@ -198,6 +222,8 @@ def resolve_table_uri(
198222
199223
200224def channel_from_title (title : str | None ) -> str | None :
225+ """Classify a media title as Youtube or Facebook when unambiguous."""
226+
201227 if not isinstance (title , str ):
202228 return None
203229 lowered = title .lower ()
@@ -209,6 +235,8 @@ def channel_from_title(title: str | None) -> str | None:
209235
210236
211237def build_dimension_dataframe (spark : Any , raw_input_uri : str ) -> Any :
238+ """Create one latest, valid media record per media ID from raw events."""
239+
212240 from pyspark .sql import Window
213241 from pyspark .sql import functions as functions
214242 from pyspark .sql .types import StringType
@@ -244,6 +272,8 @@ def build_dimension_dataframe(spark: Any, raw_input_uri: str) -> Any:
244272 f"Unable to derive a unique Youtube or Facebook channel for: { examples } ."
245273 )
246274
275+ # If a media appears multiple times, keep the attributes from its newest
276+ # event so the dimension reflects the latest known title and URL.
247277 latest_per_media = Window .partitionBy ("media_id" ).orderBy (
248278 functions .col ("_received_at" ).desc (),
249279 functions .col ("title" ).desc (),
@@ -257,6 +287,8 @@ def build_dimension_dataframe(spark: Any, raw_input_uri: str) -> Any:
257287
258288
259289def upsert_delta_table (spark : Any , dimension : Any , table_uri : str ) -> int :
290+ """Insert new media and update existing media in the Delta table."""
291+
260292 from delta .tables import DeltaTable
261293
262294 row_count = dimension .count ()
@@ -288,6 +320,8 @@ def publish_workflow_properties(
288320 table_uri : str ,
289321 row_count : int ,
290322) -> None :
323+ """Publish the table URI and row count for later workflow jobs."""
324+
291325 context = workflow_context (config )
292326 if context is None :
293327 LOGGER .info ("No Glue workflow context found; skipping dim_media property publication." )
@@ -312,6 +346,8 @@ def publish_workflow_properties(
312346
313347
314348def main () -> None :
349+ """Resolve validated input, build dim_media, and upsert it into Delta."""
350+
315351 from awsglue .context import GlueContext
316352 from awsglue .job import Job
317353 from pyspark .context import SparkContext
0 commit comments