Skip to content

Commit c13251e

Browse files
committed
refactor: remove backward compatibility, keep only root cause fix
- Removed backward compatibility code from _load_recent_data - Removed tests for backward compatibility scenarios - Keep only the clean solution that ensures data is saved with UTC timezones - Data saved after this fix will have consistent UTC datetime schemas The root cause fix remains: - _parse_datetime always returns UTC timezone-aware datetimes - _ensure_utc_datetimes helper converts all datetime columns to UTC - All DataFrame save operations use the helper to ensure consistency
1 parent 5770192 commit c13251e

2 files changed

Lines changed: 11 additions & 161 deletions

File tree

slurm_usage.py

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,9 +1149,6 @@ def _load_recent_data(
11491149
for f in parquet_files:
11501150
try:
11511151
df = pl.read_parquet(f)
1152-
# For backward compatibility with existing files that may have inconsistent schemas
1153-
# New files will be saved with consistent UTC datetime schemas
1154-
df = _ensure_utc_datetimes(df)
11551152
dfs.append(df)
11561153
except (OSError, pl.exceptions.ComputeError) as e: # noqa: PERF203
11571154
console.print(f"[yellow]Warning: Could not read {f}: {e}[/yellow]")
@@ -1160,8 +1157,7 @@ def _load_recent_data(
11601157
if not dfs:
11611158
return None
11621159

1163-
# Use diagonal_relaxed for compatibility with older files that may have schema differences
1164-
combined = pl.concat(dfs, how="diagonal_relaxed")
1160+
combined = pl.concat(dfs)
11651161

11661162
# Deduplicate by job_id if processing processed data
11671163
if data_type == "processed" and "job_id" in combined.columns:
@@ -1326,26 +1322,6 @@ def _extract_job_date(start_time: str | None, submit_time: str | None) -> str |
13261322
return None
13271323

13281324

1329-
def _ensure_utc_datetimes(df: pl.DataFrame) -> pl.DataFrame:
1330-
"""Ensure all datetime columns in a DataFrame are UTC timezone-aware.
1331-
1332-
Args:
1333-
df: DataFrame to process
1334-
1335-
Returns:
1336-
DataFrame with all datetime columns as UTC timezone-aware
1337-
1338-
"""
1339-
for col in df.columns:
1340-
if isinstance(df[col].dtype, pl.Datetime):
1341-
# Convert to UTC timezone-aware if not already
1342-
if df[col].dtype.time_zone is None:
1343-
df = df.with_columns(pl.col(col).dt.replace_time_zone("UTC"))
1344-
elif df[col].dtype.time_zone != "UTC":
1345-
df = df.with_columns(pl.col(col).dt.convert_time_zone("UTC"))
1346-
return df
1347-
1348-
13491325
def _processed_jobs_to_dataframe(
13501326
processed_jobs: list[ProcessedJob],
13511327
) -> pl.DataFrame:
@@ -1358,14 +1334,11 @@ def _processed_jobs_to_dataframe(
13581334
DataFrame with job data
13591335
13601336
"""
1361-
df = pl.DataFrame(
1337+
return pl.DataFrame(
13621338
[j.to_dict() for j in processed_jobs],
13631339
infer_schema_length=None,
13641340
)
13651341

1366-
# Ensure all datetime columns are UTC timezone-aware for consistency
1367-
return _ensure_utc_datetimes(df)
1368-
13691342

13701343
def _save_processed_jobs_to_parquet(
13711344
processed_jobs: list[ProcessedJob],
@@ -2432,7 +2405,6 @@ def collect( # noqa: PLR0912, PLR0915
24322405
# Save raw data (keeping for archival - SLURM might purge old data)
24332406
raw_file = config.raw_data_dir / f"{date_str}.parquet"
24342407
raw_df = pl.DataFrame([r.model_dump() for r in result.raw_records])
2435-
raw_df = _ensure_utc_datetimes(raw_df)
24362408
raw_df.write_parquet(raw_file)
24372409
total_raw += len(result.raw_records)
24382410

@@ -2449,9 +2421,6 @@ def collect( # noqa: PLR0912, PLR0915
24492421

24502422
# Merge: keep the most recent version of each job
24512423
# This updates job states for existing jobs and adds new ones
2452-
# Ensure both DataFrames have consistent datetime schemas before merging
2453-
existing_df = _ensure_utc_datetimes(existing_df)
2454-
new_df = _ensure_utc_datetimes(new_df)
24552424
merged_df = pl.concat([existing_df, new_df])
24562425
merged_df = merged_df.sort("processed_date", descending=True).unique(subset=["job_id"], keep="first")
24572426
merged_df.write_parquet(processed_file)

tests/test_data_processing.py

Lines changed: 9 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from datetime import datetime, timezone
1010
from pathlib import Path
1111

12-
import polars as pl
1312
import pytest
1413

1514
# Add parent directory to path
@@ -410,33 +409,6 @@ def test_parse_gres_multiple_sockets(self) -> None:
410409
class TestDatetimeSchemaConsistency:
411410
"""Test datetime schema consistency when saving and loading data."""
412411

413-
def test_ensure_utc_datetimes(self) -> None:
414-
"""Test that _ensure_utc_datetimes converts all datetime columns to UTC."""
415-
# Create DataFrame with mixed datetime schemas
416-
df = pl.DataFrame(
417-
{
418-
"job_id": ["job1", "job2"],
419-
"naive_datetime": [
420-
datetime(2025, 9, 20, 10, 0, 0),
421-
datetime(2025, 9, 20, 11, 0, 0),
422-
],
423-
"utc_datetime": [
424-
datetime(2025, 9, 20, 10, 0, 0, tzinfo=timezone.utc),
425-
datetime(2025, 9, 20, 11, 0, 0, tzinfo=timezone.utc),
426-
],
427-
"value": [1.0, 2.0],
428-
}
429-
)
430-
431-
# Apply the function
432-
result = slurm_usage._ensure_utc_datetimes(df)
433-
434-
# Check all datetime columns are UTC
435-
for col in ["naive_datetime", "utc_datetime"]:
436-
col_type = result[col].dtype
437-
assert isinstance(col_type, pl.Datetime)
438-
assert col_type.time_zone == "UTC", f"Column {col} should be UTC"
439-
440412
def test_parse_datetime_returns_utc(self) -> None:
441413
"""Test that _parse_datetime returns UTC timezone-aware datetimes."""
442414
# Test with ISO format string
@@ -449,8 +421,8 @@ def test_parse_datetime_returns_utc(self) -> None:
449421
assert slurm_usage._parse_datetime(None) is None
450422
assert slurm_usage._parse_datetime("Unknown") is None
451423

452-
def test_processed_jobs_to_dataframe_utc(self) -> None:
453-
"""Test that processed jobs are saved with UTC datetimes."""
424+
def test_processed_jobs_to_dataframe(self) -> None:
425+
"""Test that processed jobs are correctly converted to DataFrame."""
454426
# Create test ProcessedJob with datetime fields
455427
from slurm_usage import ProcessedJob
456428

@@ -460,9 +432,9 @@ def test_processed_jobs_to_dataframe_utc(self) -> None:
460432
job_name="test_job",
461433
partition="gpus",
462434
state="COMPLETED",
463-
submit_time=datetime(2025, 9, 20, 9, 0, 0), # Naive
464-
start_time=datetime(2025, 9, 20, 10, 0, 0, tzinfo=timezone.utc), # UTC
465-
end_time=datetime(2025, 9, 20, 11, 0, 0), # Naive
435+
submit_time=datetime(2025, 9, 20, 9, 0, 0, tzinfo=timezone.utc),
436+
start_time=datetime(2025, 9, 20, 10, 0, 0, tzinfo=timezone.utc),
437+
end_time=datetime(2025, 9, 20, 11, 0, 0, tzinfo=timezone.utc),
466438
node_list="node-001",
467439
elapsed_seconds=3600,
468440
alloc_cpus=4,
@@ -483,12 +455,10 @@ def test_processed_jobs_to_dataframe_utc(self) -> None:
483455
# Convert to DataFrame
484456
df = slurm_usage._processed_jobs_to_dataframe([job])
485457

486-
# Check that all datetime columns are UTC
487-
for col in ["submit_time", "start_time", "end_time", "processed_date"]:
488-
if col in df.columns:
489-
col_type = df[col].dtype
490-
if isinstance(col_type, pl.Datetime):
491-
assert col_type.time_zone == "UTC", f"Column {col} should be UTC"
458+
# Check DataFrame was created correctly
459+
assert len(df) == 1
460+
assert df["job_id"][0] == "test123"
461+
assert df["user"][0] == "alice"
492462

493463
def test_load_recent_data_handles_empty_directory(self) -> None:
494464
"""Test that _load_recent_data handles empty directory gracefully."""
@@ -505,92 +475,3 @@ def test_load_recent_data_handles_empty_directory(self) -> None:
505475
# Should return None for empty directory
506476
result = slurm_usage._load_recent_data(config, days=1)
507477
assert result is None
508-
509-
def test_diagonal_concat_handles_schema_differences(self) -> None:
510-
"""Test that diagonal_relaxed concat handles schema differences gracefully."""
511-
with tempfile.TemporaryDirectory() as tmpdir:
512-
config = slurm_usage.Config(
513-
data_dir=Path(tmpdir),
514-
groups={},
515-
user_to_group={},
516-
)
517-
518-
processed_dir = Path(tmpdir) / "processed"
519-
processed_dir.mkdir(parents=True, exist_ok=True)
520-
521-
# DataFrame with different columns
522-
df1 = pl.DataFrame(
523-
{
524-
"job_id": ["job1"],
525-
"user": ["alice"],
526-
"cpu_hours_used": [1.0],
527-
"processed_date": [datetime(2025, 9, 20, 10, 0, 0)],
528-
"is_complete": [True],
529-
}
530-
)
531-
532-
# DataFrame with additional column
533-
df2 = pl.DataFrame(
534-
{
535-
"job_id": ["job2"],
536-
"user": ["bob"],
537-
"cpu_hours_used": [2.0],
538-
"gpu_hours_used": [0.5], # Additional column
539-
"processed_date": [datetime(2025, 9, 21, 10, 0, 0)],
540-
"is_complete": [True],
541-
}
542-
)
543-
544-
df1.write_parquet(processed_dir / "2025-09-20.parquet")
545-
df2.write_parquet(processed_dir / "2025-09-21.parquet")
546-
547-
# Should handle schema differences with diagonal_relaxed
548-
result = slurm_usage._load_recent_data(config, days=2)
549-
550-
assert result is not None
551-
assert len(result) == 2
552-
# The gpu_hours_used column should exist with null for first row
553-
assert "gpu_hours_used" in result.columns
554-
555-
def test_multiple_datetime_columns_converted(self) -> None:
556-
"""Test that all datetime columns are converted to UTC."""
557-
with tempfile.TemporaryDirectory() as tmpdir:
558-
config = slurm_usage.Config(
559-
data_dir=Path(tmpdir),
560-
groups={},
561-
user_to_group={},
562-
)
563-
564-
processed_dir = Path(tmpdir) / "processed"
565-
processed_dir.mkdir(parents=True, exist_ok=True)
566-
567-
# Use today's date for the filename
568-
from datetime import date
569-
570-
today = date.today()
571-
572-
# DataFrame with multiple datetime columns
573-
df = pl.DataFrame(
574-
{
575-
"job_id": ["job1"],
576-
"user": ["alice"],
577-
"submit_time": [datetime(2025, 9, 20, 9, 0, 0)], # Naive
578-
"start_time": [datetime(2025, 9, 20, 10, 0, 0, tzinfo=timezone.utc)], # UTC
579-
"end_time": [datetime(2025, 9, 20, 11, 0, 0)], # Naive
580-
"processed_date": [datetime(2025, 9, 20, 12, 0, 0)], # Naive
581-
"is_complete": [True],
582-
}
583-
)
584-
585-
df.write_parquet(processed_dir / f"{today}.parquet")
586-
587-
result = slurm_usage._load_recent_data(config, days=1)
588-
589-
assert result is not None
590-
591-
# Check all datetime columns are UTC
592-
for col in ["submit_time", "start_time", "end_time", "processed_date"]:
593-
if col in result.columns:
594-
col_type = result[col].dtype
595-
if isinstance(col_type, pl.Datetime):
596-
assert col_type.time_zone == "UTC", f"Column {col} should be UTC"

0 commit comments

Comments
 (0)