Coverage for src/usaspending/models/download.py: 91%
47 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-03 17:15 -0700
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-03 17:15 -0700
1# src/usaspending/models/download.py
3from __future__ import annotations
4from typing import Optional, Literal
5from enum import Enum
7from ..utils.formatter import to_float, to_int
8from .base_model import BaseModel
10AwardType = Literal["contract", "assistance", "idv"]
11FileFormat = Literal["csv", "tsv", "pstxt"]
13class DownloadState(Enum):
14 """Enumeration for download job states."""
15 PENDING = "pending" # Custom state before first API check
16 READY = "ready"
17 RUNNING = "running"
18 FINISHED = "finished"
19 FAILED = "failed"
20 UNKNOWN = "unknown" # Custom state if API returns unexpected value
22class DownloadStatus(BaseModel):
23 """Represents the status details of a download job returned by the API."""
25 @property
26 def file_name(self) -> Optional[str]:
27 return self.get_value("file_name")
29 @property
30 def message(self) -> Optional[str]:
31 """A human readable error message if the status is failed, otherwise null."""
32 return self.get_value("message")
34 @property
35 def seconds_elapsed(self) -> Optional[float]:
36 """Time taken to generate the file or time taken so far."""
37 return to_float(self.get_value("seconds_elapsed"))
39 @property
40 def api_status(self) -> DownloadState:
41 """Current state of the request from the API."""
42 status_str = self.get_value("status")
43 if status_str:
44 try:
45 return DownloadState(status_str)
46 except ValueError:
47 return DownloadState.UNKNOWN
48 return DownloadState.UNKNOWN
50 @property
51 def total_columns(self) -> Optional[int]:
52 return to_int(self.get_value("total_columns"))
54 @property
55 def total_rows(self) -> Optional[int]:
56 return to_int(self.get_value("total_rows"))
58 @property
59 def total_size_kb(self) -> Optional[float]:
60 """Estimated file size in kilobytes."""
61 return to_float(self.get_value("total_size"))
63 @property
64 def file_url(self) -> Optional[str]:
65 """The URL for the file (relative path)."""
66 return self.get_value("file_url")
68 def __repr__(self) -> str:
69 return f"<DownloadStatus status='{self.api_status.value}' file='{self.file_name}'>"