Repository: apify/crawlee-python
File: src/crawlee/statistics/models.py (Python)

Code before:
from rich.table import Table
from typing_extensions import override

from crawlee._utils.models import timedelta_ms


@dataclass(frozen=True)
class FinalStatistics:
    """Statistics about a crawler run."""

    requests_finished: int
    requests_failed: int
    retry_histogram: list[int]
    request_avg_failed_duration: timedelta | None
    request_avg_finished_duration: timedelta | None
    requests_finished_per_minute: float
    requests_failed_per_minute: float
    request_total_duration: timedelta
    requests_total: int
    crawler_runtime: timedelta

    def to_table(self) -> None:
        """Print out the Final Statistics data as a table."""
        table = Table()
        table.add_column('INFO')
        table.add_column('Statistics')

        str_dict = {k: v.total_seconds() if isinstance(v, timedelta) else v for k, v in asdict(self).items()}

        for k, v in str_dict.items():
            table.add_row(str(k), str(v))

        console = Console()
        console.print(table)

    @override
    def __str__(self) -> str:
        return json.dumps(
            {k: v.total_seconds() if isinstance(v, timedelta) else v for k, v in asdict(self).items()},
        )


class StatisticsState(BaseModel):
    """Statistic data about a crawler run."""

    model_config = ConfigDict(populate_by_name=True, ser_json_inf_nan='constants')

    requests_finished: Annotated[int, Field(alias='requestsFinished')] = 0
    requests_failed: Annotated[int, Field(alias='requestsFailed')] = 0
    requests_retries: Annotated[int, Field(alias='requestsRetries')] = 0
    requests_failed_per_minute: Annotated[float, Field(alias='requestsFailedPerMinute')] = 0

Review comment (suggestion):
```suggestion
        table = Table(show_header=False)
        table.add_column()
        table.add_column()
```

The headers are not particularly useful in this case.

Code after:
from rich.table import Table
from typing_extensions import override

from crawlee._utils.models import timedelta_ms


@dataclass(frozen=True)
class FinalStatistics:
    """Statistics about a crawler run."""

    requests_finished: int
    requests_failed: int
    retry_histogram: list[int]
    request_avg_failed_duration: timedelta | None
    request_avg_finished_duration: timedelta | None
    requests_finished_per_minute: float
    requests_failed_per_minute: float
    request_total_duration: timedelta
    requests_total: int
    crawler_runtime: timedelta

    def to_table(self) -> str:
        """Print out the Final Statistics data as a table."""
        table = Table(show_header=False)
        table.add_column()
        table.add_column()

        str_dict = {k: v.total_seconds() if isinstance(v, timedelta) else v for k, v in asdict(self).items()}

        for k, v in str_dict.items():
            table.add_row(str(k), str(v))

        console = Console(width=60)
        with console.capture() as capture:
            console.print(table, end='\n')

        return capture.get().strip('\n')

    @override
    def __str__(self) -> str:
        return json.dumps(
            {k: v.total_seconds() if isinstance(v, timedelta) else v for k, v in asdict(self).items()},
        )


class StatisticsState(BaseModel):
    """Statistic data about a crawler run."""

    model_config = ConfigDict(populate_by_name=True, ser_json_inf_nan='constants')

    requests_finished: Annotated[int, Field(alias='requestsFinished')] = 0