Coverage for seedboxsync/cli/commands/cmd_stats.py: 98%
43 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-26 17:46 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-26 17:46 +0000
1#
2# Copyright (C) 2015-2026 Guillaume Kulakowski <guillaume@kulakowski.fr>
3#
4# For the full copyright and license information, please view the LICENSE
5# file that was distributed with this source code.
6#
7"""All commands related to statistics operations in SeedboxSync."""
9import click
10from humanize import filesize
11from peewee import fn
12from seedboxsync.cli import Context, group, pass_context
13from seedboxsync.core.dao import Download, typed_peewee_dicts
16@group(
17 "stats",
18 help="Stats operations.",
19 invoke_without_command=True,
20 no_args_is_help=False,
21) # type: ignore[untyped-decorator]
22@pass_context
23def cli(ctx: Context) -> None:
24 """
25 Display statistics about completed downloads.
27 Args:
28 ctx (Context): The Click context object.
29 """
30 if ctx.invoked_subcommand is None:
31 click.echo(ctx.get_help())
32 click.echo()
33 ctx.invoke(total)
36@cli.command("by-month", help="Show statistics aggregated by month.") # type: ignore[untyped-decorator]
37@pass_context
38def by_month(ctx: Context) -> None:
39 """
40 Show statistics aggregated by month.
42 Args:
43 ctx (Context): The Click context object.
44 """
45 _stats_by_period(ctx, "month", "Month")
48@cli.command("by-year", help="Show statistics aggregated by year.") # type: ignore[untyped-decorator]
49@pass_context
50def by_year(ctx: Context) -> None:
51 """
52 Show statistics aggregated by year.
54 Args:
55 ctx (Context): The Click context object.
56 """
57 _stats_by_period(ctx, "year", "Year")
60@cli.command("total", help="Show total statistics") # type: ignore[untyped-decorator]
61@pass_context
62def total(ctx: Context) -> None:
63 """
64 Show total statistics for all completed downloads.
66 Displays the total number of files and the total size.
68 Args:
69 ctx (Context): The Click context object.
70 """
71 query = Download.select().where(Download.finished != 0)
72 total_files = query.count()
73 total_size = sum([d.seedbox_size for d in query if d.seedbox_size])
75 stats = [
76 {
77 "files": total_files,
78 "total_size": filesize.naturalsize(total_size, True),
79 }
80 ]
82 click.echo(ctx.render(stats, headers={"files": "Nb files", "total_size": "Total size"}))
85def _stats_by_period(ctx: Context, period: str, header_label: str) -> None:
86 """
87 Generic helper to calculate statistics by a given period.
89 Aggregates the number of files and total size for each month or year.
91 Args:
92 ctx (Context): The Click context object.
93 period (str): Either 'month' or 'year' to group statistics.
94 header_label (str): Label used for rendering the period column.
95 """
96 strftime_format = "%Y-%m" if period == "month" else "%Y"
98 data = typed_peewee_dicts(
99 Download.select(
100 Download.id,
101 Download.finished,
102 fn.strftime(strftime_format, Download.finished).alias(period),
103 Download.seedbox_size,
104 )
105 .where(Download.finished != 0)
106 .order_by(Download.finished.desc())
107 .dicts()
108 )
110 tmp = {}
111 for download in data:
112 key = download[period]
113 size = download["seedbox_size"]
114 if not key or not size:
115 continue
116 if key not in tmp:
117 tmp[key] = {"files": 0, "total_size": 0.0}
118 tmp[key]["files"] += 1
119 tmp[key]["total_size"] += size
121 stats = [
122 {
123 period: key,
124 "files": tmp[key]["files"],
125 "total_size": filesize.naturalsize(tmp[key]["total_size"], True),
126 }
127 for key in sorted(tmp)
128 ]
130 click.echo(
131 ctx.render(
132 stats,
133 headers={
134 period: header_label,
135 "files": "Nb files",
136 "total_size": "Total size",
137 },
138 )
139 )