feat: CSV utilities for order entity serialization
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
70
src/order_bak/csv_utils.py
Normal file
70
src/order_bak/csv_utils.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import csv
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
CSV_COLUMNS = [
|
||||
"PartitionKey",
|
||||
"RowKey",
|
||||
"OrderTime",
|
||||
"ReceiveTime",
|
||||
"revenue",
|
||||
"platformOrder",
|
||||
"prodCount",
|
||||
"pfId",
|
||||
"prodId",
|
||||
"prodPrice",
|
||||
"state",
|
||||
"ver",
|
||||
"customData",
|
||||
]
|
||||
|
||||
|
||||
def _format_value(val: Any) -> str:
|
||||
if val is None:
|
||||
return ""
|
||||
if isinstance(val, datetime):
|
||||
return val.isoformat()
|
||||
return str(val)
|
||||
|
||||
|
||||
def entity_to_row(entity: dict[str, Any]) -> dict[str, str]:
|
||||
return {col: _format_value(entity.get(col)) for col in CSV_COLUMNS}
|
||||
|
||||
|
||||
def _parse_row(raw: dict[str, str]) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {}
|
||||
for col in CSV_COLUMNS:
|
||||
val = raw.get(col, "")
|
||||
if val == "":
|
||||
result[col] = None
|
||||
continue
|
||||
if col in ("OrderTime", "ReceiveTime"):
|
||||
result[col] = datetime.fromisoformat(val)
|
||||
elif col in ("revenue", "prodPrice"):
|
||||
result[col] = float(val)
|
||||
elif col in ("prodCount", "state", "ver"):
|
||||
result[col] = int(val)
|
||||
else:
|
||||
result[col] = val
|
||||
return result
|
||||
|
||||
|
||||
def row_to_entity(raw: dict[str, str]) -> dict[str, Any]:
|
||||
return _parse_row(raw)
|
||||
|
||||
|
||||
def write_csv(path: Path, entities: list[dict[str, Any]]) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(path, "w", newline="") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=CSV_COLUMNS)
|
||||
writer.writeheader()
|
||||
for entity in entities:
|
||||
writer.writerow(entity_to_row(entity))
|
||||
|
||||
|
||||
def read_csv(path: Path):
|
||||
with open(path, newline="") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
yield _parse_row(row)
|
||||
132
tests/test_csv_utils.py
Normal file
132
tests/test_csv_utils.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import csv
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from order_bak.csv_utils import (
|
||||
CSV_COLUMNS,
|
||||
entity_to_row,
|
||||
row_to_entity,
|
||||
write_csv,
|
||||
read_csv,
|
||||
)
|
||||
|
||||
|
||||
def test_csv_columns_defined():
|
||||
assert "PartitionKey" in CSV_COLUMNS
|
||||
assert "RowKey" in CSV_COLUMNS
|
||||
assert "OrderTime" in CSV_COLUMNS
|
||||
assert "state" in CSV_COLUMNS
|
||||
assert len(CSV_COLUMNS) == 13
|
||||
|
||||
|
||||
def test_entity_to_row_converts_datetime():
|
||||
entity = {
|
||||
"PartitionKey": "user1",
|
||||
"RowKey": "order1",
|
||||
"OrderTime": datetime(2025, 6, 15, 10, 30, 0, tzinfo=timezone.utc),
|
||||
"ReceiveTime": datetime(2025, 6, 15, 11, 0, 0, tzinfo=timezone.utc),
|
||||
"revenue": 99.5,
|
||||
"platformOrder": "plat123",
|
||||
"prodCount": 2,
|
||||
"pfId": "user1",
|
||||
"prodId": "prod1",
|
||||
"prodPrice": 49.75,
|
||||
"state": 1,
|
||||
"ver": 3,
|
||||
"customData": '{"key":"val"}',
|
||||
}
|
||||
row = entity_to_row(entity)
|
||||
assert row["OrderTime"] == "2025-06-15T10:30:00+00:00"
|
||||
assert row["ReceiveTime"] == "2025-06-15T11:00:00+00:00"
|
||||
assert row["revenue"] == "99.5"
|
||||
assert row["state"] == "1"
|
||||
|
||||
|
||||
def test_entity_to_row_handles_none():
|
||||
entity = {
|
||||
"PartitionKey": "user1",
|
||||
"RowKey": "order1",
|
||||
"OrderTime": datetime(2025, 1, 1, tzinfo=timezone.utc),
|
||||
"ReceiveTime": None,
|
||||
"revenue": None,
|
||||
"platformOrder": None,
|
||||
"prodCount": None,
|
||||
"pfId": None,
|
||||
"prodId": None,
|
||||
"prodPrice": None,
|
||||
"state": 0,
|
||||
"ver": None,
|
||||
"customData": None,
|
||||
}
|
||||
row = entity_to_row(entity)
|
||||
assert row["ReceiveTime"] == ""
|
||||
assert row["revenue"] == ""
|
||||
|
||||
|
||||
def test_row_to_entity_roundtrip(tmp_path):
|
||||
original = {
|
||||
"PartitionKey": "user1",
|
||||
"RowKey": "order1",
|
||||
"OrderTime": "2025-06-15T10:30:00+00:00",
|
||||
"ReceiveTime": "",
|
||||
"revenue": "99.5",
|
||||
"platformOrder": "plat123",
|
||||
"prodCount": "2",
|
||||
"pfId": "user1",
|
||||
"prodId": "prod1",
|
||||
"prodPrice": "49.75",
|
||||
"state": "1",
|
||||
"ver": "3",
|
||||
"customData": '{"key":"val"}',
|
||||
}
|
||||
entity = row_to_entity(original)
|
||||
assert entity["PartitionKey"] == "user1"
|
||||
assert entity["state"] == 1
|
||||
assert entity["revenue"] == 99.5
|
||||
assert entity["prodCount"] == 2
|
||||
assert entity["ReceiveTime"] is None
|
||||
|
||||
|
||||
def test_write_and_read_csv(tmp_path):
|
||||
entities = [
|
||||
{
|
||||
"PartitionKey": "user1",
|
||||
"RowKey": "order1",
|
||||
"OrderTime": datetime(2025, 6, 15, 10, 30, tzinfo=timezone.utc),
|
||||
"ReceiveTime": None,
|
||||
"revenue": 99.5,
|
||||
"platformOrder": "p1",
|
||||
"prodCount": 2,
|
||||
"pfId": "user1",
|
||||
"prodId": "prod1",
|
||||
"prodPrice": 49.75,
|
||||
"state": 1,
|
||||
"ver": 3,
|
||||
"customData": None,
|
||||
},
|
||||
{
|
||||
"PartitionKey": "user2",
|
||||
"RowKey": "order2",
|
||||
"OrderTime": datetime(2025, 6, 16, 12, 0, tzinfo=timezone.utc),
|
||||
"ReceiveTime": datetime(2025, 6, 16, 13, 0, tzinfo=timezone.utc),
|
||||
"revenue": 10.0,
|
||||
"platformOrder": "p2",
|
||||
"prodCount": 1,
|
||||
"pfId": "user2",
|
||||
"prodId": "prod2",
|
||||
"prodPrice": 10.0,
|
||||
"state": 1,
|
||||
"ver": 3,
|
||||
"customData": '{"note":"test"}',
|
||||
},
|
||||
]
|
||||
csv_path = tmp_path / "test.csv"
|
||||
write_csv(csv_path, entities)
|
||||
|
||||
result = list(read_csv(csv_path))
|
||||
assert len(result) == 2
|
||||
assert result[0]["PartitionKey"] == "user1"
|
||||
assert result[1]["PartitionKey"] == "user2"
|
||||
assert result[1]["revenue"] == 10.0
|
||||
assert result[0]["ReceiveTime"] is None
|
||||
assert result[1]["ReceiveTime"] is not None
|
||||
Reference in New Issue
Block a user