deepctl-cmd-usage 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepctl_cmd_usage-0.1.0/PKG-INFO +26 -0
- deepctl_cmd_usage-0.1.0/README.md +3 -0
- deepctl_cmd_usage-0.1.0/pyproject.toml +43 -0
- deepctl_cmd_usage-0.1.0/setup.cfg +4 -0
- deepctl_cmd_usage-0.1.0/src/deepctl_cmd_usage/__init__.py +6 -0
- deepctl_cmd_usage-0.1.0/src/deepctl_cmd_usage/command.py +401 -0
- deepctl_cmd_usage-0.1.0/src/deepctl_cmd_usage/models.py +16 -0
- deepctl_cmd_usage-0.1.0/src/deepctl_cmd_usage.egg-info/PKG-INFO +26 -0
- deepctl_cmd_usage-0.1.0/src/deepctl_cmd_usage.egg-info/SOURCES.txt +11 -0
- deepctl_cmd_usage-0.1.0/src/deepctl_cmd_usage.egg-info/dependency_links.txt +1 -0
- deepctl_cmd_usage-0.1.0/src/deepctl_cmd_usage.egg-info/entry_points.txt +2 -0
- deepctl_cmd_usage-0.1.0/src/deepctl_cmd_usage.egg-info/requires.txt +6 -0
- deepctl_cmd_usage-0.1.0/src/deepctl_cmd_usage.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-cmd-usage
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Usage command for deepctl
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: deepgram,cli,usage,statistics
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: deepctl-core>=0.1.0
|
|
18
|
+
Requires-Dist: deepctl-shared-utils>=0.1.0
|
|
19
|
+
Requires-Dist: click>=8.0.0
|
|
20
|
+
Requires-Dist: rich>=13.0.0
|
|
21
|
+
Requires-Dist: deepgram-sdk>=3.0.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0.0
|
|
23
|
+
|
|
24
|
+
# deepctl-cmd-usage
|
|
25
|
+
|
|
26
|
+
Usage command for deepctl. This package is an internal component of deepctl and is not intended for standalone use.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "deepctl-cmd-usage"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Usage command for deepctl"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{ name = "Deepgram", email = "devrel@deepgram.com" }]
|
|
12
|
+
maintainers = [{ name = "Deepgram", email = "devrel@deepgram.com" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
]
|
|
21
|
+
keywords = ["deepgram", "cli", "usage", "statistics"]
|
|
22
|
+
requires-python = ">=3.10"
|
|
23
|
+
dependencies = [
|
|
24
|
+
"deepctl-core>=0.1.0",
|
|
25
|
+
"deepctl-shared-utils>=0.1.0",
|
|
26
|
+
"click>=8.0.0",
|
|
27
|
+
"rich>=13.0.0",
|
|
28
|
+
"deepgram-sdk>=3.0.0",
|
|
29
|
+
"pydantic>=2.0.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
# Internal commands not meant to be called directly
|
|
34
|
+
|
|
35
|
+
[project.entry-points."deepctl.commands"]
|
|
36
|
+
usage = "deepctl_cmd_usage.command:UsageCommand"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
package-dir = { "" = "src" }
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
include = ["deepctl_cmd_usage*"]
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"""Usage command for deepctl."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from deepctl_core import (
|
|
7
|
+
AuthManager,
|
|
8
|
+
BaseCommand,
|
|
9
|
+
BaseResult,
|
|
10
|
+
Config,
|
|
11
|
+
DeepgramClient,
|
|
12
|
+
)
|
|
13
|
+
from deepctl_shared_utils import validate_date_format
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
|
|
16
|
+
from .models import UsageBucket, UsageResult
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UsageCommand(BaseCommand):
|
|
22
|
+
"""Command for viewing Deepgram usage statistics."""
|
|
23
|
+
|
|
24
|
+
name = "usage"
|
|
25
|
+
help = "View Deepgram usage statistics"
|
|
26
|
+
short_help = "View usage statistics"
|
|
27
|
+
|
|
28
|
+
# Usage requires authentication and project
|
|
29
|
+
requires_auth = True
|
|
30
|
+
requires_project = True
|
|
31
|
+
ci_friendly = True
|
|
32
|
+
|
|
33
|
+
def get_arguments(self) -> list[dict[str, Any]]:
|
|
34
|
+
"""Get command arguments and options."""
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
"names": ["--project-id", "-p"],
|
|
38
|
+
"help": "Project ID (uses configured project if not provided)",
|
|
39
|
+
"type": str,
|
|
40
|
+
"is_option": True,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"names": ["--start-date", "-s"],
|
|
44
|
+
"help": "Start date (YYYY-MM-DD or ISO format)",
|
|
45
|
+
"type": str,
|
|
46
|
+
"is_option": True,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"names": ["--end-date", "-e"],
|
|
50
|
+
"help": "End date (YYYY-MM-DD or ISO format)",
|
|
51
|
+
"type": str,
|
|
52
|
+
"is_option": True,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"names": ["--last-week"],
|
|
56
|
+
"help": "Show usage for last week",
|
|
57
|
+
"is_flag": True,
|
|
58
|
+
"is_option": True,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"names": ["--last-month"],
|
|
62
|
+
"help": "Show usage for last month",
|
|
63
|
+
"is_flag": True,
|
|
64
|
+
"is_option": True,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"names": ["--current-month"],
|
|
68
|
+
"help": "Show usage for current month",
|
|
69
|
+
"is_flag": True,
|
|
70
|
+
"is_option": True,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"names": ["--summary"],
|
|
74
|
+
"help": "Show summary only",
|
|
75
|
+
"is_flag": True,
|
|
76
|
+
"is_option": True,
|
|
77
|
+
},
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
def handle(
|
|
81
|
+
self,
|
|
82
|
+
config: Config,
|
|
83
|
+
auth_manager: AuthManager,
|
|
84
|
+
client: DeepgramClient,
|
|
85
|
+
**kwargs: Any,
|
|
86
|
+
) -> BaseResult:
|
|
87
|
+
"""Handle usage command."""
|
|
88
|
+
project_id = kwargs.get("project_id")
|
|
89
|
+
start_date = kwargs.get("start_date")
|
|
90
|
+
end_date = kwargs.get("end_date")
|
|
91
|
+
last_week = kwargs.get("last_week", False)
|
|
92
|
+
last_month = kwargs.get("last_month", False)
|
|
93
|
+
current_month = kwargs.get("current_month", False)
|
|
94
|
+
summary_only = kwargs.get("summary", False)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# Determine date range
|
|
98
|
+
if last_week:
|
|
99
|
+
start_date, end_date = self._get_last_week_range()
|
|
100
|
+
console.print("[blue]Fetching usage for last week...[/blue]")
|
|
101
|
+
elif last_month:
|
|
102
|
+
start_date, end_date = self._get_last_month_range()
|
|
103
|
+
console.print("[blue]Fetching usage for last month...[/blue]")
|
|
104
|
+
elif current_month:
|
|
105
|
+
start_date, end_date = self._get_current_month_range()
|
|
106
|
+
console.print(
|
|
107
|
+
"[blue]Fetching usage for current month...[/blue]"
|
|
108
|
+
)
|
|
109
|
+
elif start_date or end_date:
|
|
110
|
+
# Validate custom date range
|
|
111
|
+
if start_date and not validate_date_format(start_date):
|
|
112
|
+
return BaseResult(
|
|
113
|
+
status="error",
|
|
114
|
+
message=f"Invalid start date format: {start_date}",
|
|
115
|
+
)
|
|
116
|
+
if end_date and not validate_date_format(end_date):
|
|
117
|
+
return BaseResult(
|
|
118
|
+
status="error",
|
|
119
|
+
message=f"Invalid end date format: {end_date}",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
console.print(
|
|
123
|
+
f"[blue]Fetching usage from "
|
|
124
|
+
f"{start_date or 'beginning'} to "
|
|
125
|
+
f"{end_date or 'now'}...[/blue]"
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
# Default to current month
|
|
129
|
+
start_date, end_date = self._get_current_month_range()
|
|
130
|
+
console.print(
|
|
131
|
+
"[blue]Fetching usage for current month...[/blue]"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Get usage data
|
|
135
|
+
result = client.get_usage(project_id, start_date, end_date)
|
|
136
|
+
|
|
137
|
+
# Process and display results
|
|
138
|
+
return self._process_usage_result(
|
|
139
|
+
result, summary_only, start_date, end_date
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
console.print(f"[red]Error fetching usage:[/red] {e}")
|
|
144
|
+
return BaseResult(status="error", message=str(e))
|
|
145
|
+
|
|
146
|
+
def _get_last_week_range(self) -> tuple[str, str]:
|
|
147
|
+
"""Get date range for last week."""
|
|
148
|
+
today = datetime.now()
|
|
149
|
+
last_week = today - timedelta(days=7)
|
|
150
|
+
return last_week.strftime("%Y-%m-%d"), today.strftime("%Y-%m-%d")
|
|
151
|
+
|
|
152
|
+
def _get_last_month_range(self) -> tuple[str, str]:
|
|
153
|
+
"""Get date range for last month."""
|
|
154
|
+
today = datetime.now()
|
|
155
|
+
last_month = today - timedelta(days=30)
|
|
156
|
+
return last_month.strftime("%Y-%m-%d"), today.strftime("%Y-%m-%d")
|
|
157
|
+
|
|
158
|
+
def _get_current_month_range(self) -> tuple[str, str]:
|
|
159
|
+
"""Get date range for current month."""
|
|
160
|
+
today = datetime.now()
|
|
161
|
+
first_day = today.replace(day=1)
|
|
162
|
+
return first_day.strftime("%Y-%m-%d"), today.strftime("%Y-%m-%d")
|
|
163
|
+
|
|
164
|
+
def _process_usage_result(
|
|
165
|
+
self,
|
|
166
|
+
result: Any,
|
|
167
|
+
summary_only: bool,
|
|
168
|
+
start_date: str | None,
|
|
169
|
+
end_date: str | None,
|
|
170
|
+
) -> BaseResult:
|
|
171
|
+
"""Process usage result and display formatted output."""
|
|
172
|
+
try:
|
|
173
|
+
# Handle UsageSummaryResponse object - try different
|
|
174
|
+
# conversion methods
|
|
175
|
+
result_dict = None
|
|
176
|
+
|
|
177
|
+
if hasattr(result, "to_dict"):
|
|
178
|
+
result_dict = result.to_dict()
|
|
179
|
+
elif hasattr(result, "dict"):
|
|
180
|
+
result_dict = result.dict()
|
|
181
|
+
elif hasattr(result, "__dict__"):
|
|
182
|
+
# For SDK response objects, we need to access attributes
|
|
183
|
+
# directly
|
|
184
|
+
result_dict = {
|
|
185
|
+
"results": (
|
|
186
|
+
result.results if hasattr(result, "results") else []
|
|
187
|
+
),
|
|
188
|
+
"start": (
|
|
189
|
+
result.start
|
|
190
|
+
if hasattr(result, "start")
|
|
191
|
+
else start_date
|
|
192
|
+
),
|
|
193
|
+
"end": result.end if hasattr(result, "end") else end_date,
|
|
194
|
+
"project_id": (
|
|
195
|
+
result.project_id
|
|
196
|
+
if hasattr(result, "project_id")
|
|
197
|
+
else ""
|
|
198
|
+
),
|
|
199
|
+
}
|
|
200
|
+
else:
|
|
201
|
+
result_dict = result
|
|
202
|
+
|
|
203
|
+
# Extract usage data
|
|
204
|
+
if (
|
|
205
|
+
result_dict
|
|
206
|
+
and "results" in result_dict
|
|
207
|
+
and isinstance(result_dict["results"], list)
|
|
208
|
+
):
|
|
209
|
+
# Calculate totals from results array
|
|
210
|
+
total_hours = 0
|
|
211
|
+
total_requests = 0
|
|
212
|
+
total_tts_characters = 0
|
|
213
|
+
total_tokens_out = 0
|
|
214
|
+
|
|
215
|
+
buckets: list[UsageBucket] = []
|
|
216
|
+
|
|
217
|
+
for item in result_dict["results"]:
|
|
218
|
+
# Sum up totals
|
|
219
|
+
if "total_hours" in item:
|
|
220
|
+
total_hours += item["total_hours"]
|
|
221
|
+
if "requests" in item:
|
|
222
|
+
total_requests += item["requests"]
|
|
223
|
+
if "tts" in item and "characters" in item["tts"]:
|
|
224
|
+
total_tts_characters += item["tts"]["characters"]
|
|
225
|
+
if "tokens" in item and "out" in item["tokens"]:
|
|
226
|
+
total_tokens_out += item["tokens"]["out"]
|
|
227
|
+
|
|
228
|
+
# Create bucket for each day
|
|
229
|
+
buckets.append(
|
|
230
|
+
UsageBucket(
|
|
231
|
+
start=item.get("start", ""),
|
|
232
|
+
end=item.get("end", ""),
|
|
233
|
+
hours=float(item.get("total_hours", 0)),
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Display summary
|
|
238
|
+
console.print(
|
|
239
|
+
f"\n[green]Usage Summary ({start_date} to "
|
|
240
|
+
f"{end_date}):[/green]"
|
|
241
|
+
)
|
|
242
|
+
console.print(f" Total Hours: {total_hours:,.1f}")
|
|
243
|
+
console.print(f" Total Requests: {total_requests:,}")
|
|
244
|
+
|
|
245
|
+
if total_tts_characters > 0:
|
|
246
|
+
console.print(
|
|
247
|
+
f" TTS Characters: {total_tts_characters:,}"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if total_tokens_out > 0:
|
|
251
|
+
console.print(f" Tokens Out: {total_tokens_out:,}")
|
|
252
|
+
|
|
253
|
+
# Display detailed breakdown if not summary only
|
|
254
|
+
if not summary_only and result_dict["results"]:
|
|
255
|
+
console.print("\n[blue]Daily Breakdown:[/blue]")
|
|
256
|
+
for item in result_dict["results"]:
|
|
257
|
+
item_date = item.get("start", "Unknown")
|
|
258
|
+
hours = item.get("total_hours", 0)
|
|
259
|
+
requests = item.get("requests", 0)
|
|
260
|
+
|
|
261
|
+
console.print(f"\n {item_date}:")
|
|
262
|
+
console.print(f" Hours: {hours}")
|
|
263
|
+
console.print(f" Requests: {requests}")
|
|
264
|
+
|
|
265
|
+
if "tts" in item:
|
|
266
|
+
console.print(
|
|
267
|
+
f" TTS Characters: "
|
|
268
|
+
f"{item['tts'].get('characters', 0):,}"
|
|
269
|
+
)
|
|
270
|
+
console.print(
|
|
271
|
+
f" TTS Requests: "
|
|
272
|
+
f"{item['tts'].get('requests', 0):,}"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if (
|
|
276
|
+
"tokens" in item
|
|
277
|
+
and item["tokens"].get("out", 0) > 0
|
|
278
|
+
):
|
|
279
|
+
console.print(
|
|
280
|
+
f" Tokens Out: "
|
|
281
|
+
f"{item['tokens'].get('out', 0):,}"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
project_id = result_dict.get("project_id", "")
|
|
285
|
+
return UsageResult(
|
|
286
|
+
status="success",
|
|
287
|
+
project_id=project_id,
|
|
288
|
+
buckets=buckets,
|
|
289
|
+
total_hours=float(total_hours),
|
|
290
|
+
)
|
|
291
|
+
else:
|
|
292
|
+
console.print(
|
|
293
|
+
"[yellow]No usage data found for the specified "
|
|
294
|
+
"period[/yellow]"
|
|
295
|
+
)
|
|
296
|
+
return BaseResult(status="info", message="No usage data found")
|
|
297
|
+
|
|
298
|
+
except Exception as e:
|
|
299
|
+
console.print(f"[red]Error processing usage data:[/red] {e}")
|
|
300
|
+
import traceback
|
|
301
|
+
|
|
302
|
+
traceback.print_exc()
|
|
303
|
+
return BaseResult(status="error", message=str(e))
|
|
304
|
+
|
|
305
|
+
def _extract_usage_data(self, result: dict[str, Any]) -> dict[str, Any]:
|
|
306
|
+
"""Extract usage data from API response."""
|
|
307
|
+
if "usage" in result:
|
|
308
|
+
return dict(result["usage"])
|
|
309
|
+
elif "results" in result:
|
|
310
|
+
return dict(result["results"])
|
|
311
|
+
else:
|
|
312
|
+
return result
|
|
313
|
+
|
|
314
|
+
def _display_usage_summary(
|
|
315
|
+
self, usage_data: dict[str, Any], start_date: str, end_date: str
|
|
316
|
+
) -> None:
|
|
317
|
+
"""Display usage summary."""
|
|
318
|
+
console.print(
|
|
319
|
+
f"\n[green]Usage Summary ({start_date} to " f"{end_date}):[/green]"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Try to extract common usage metrics
|
|
323
|
+
total_requests = usage_data.get("requests", 0)
|
|
324
|
+
total_duration = usage_data.get("duration", 0)
|
|
325
|
+
total_cost = usage_data.get("cost", 0)
|
|
326
|
+
|
|
327
|
+
if total_requests:
|
|
328
|
+
console.print(f" Total Requests: {total_requests:,}")
|
|
329
|
+
|
|
330
|
+
if total_duration:
|
|
331
|
+
if isinstance(total_duration, int | float):
|
|
332
|
+
hours = total_duration / 3600
|
|
333
|
+
console.print(
|
|
334
|
+
f" Total Duration: {hours:.2f} hours "
|
|
335
|
+
f"({total_duration:,} seconds)"
|
|
336
|
+
)
|
|
337
|
+
else:
|
|
338
|
+
console.print(f" Total Duration: {total_duration}")
|
|
339
|
+
|
|
340
|
+
if total_cost:
|
|
341
|
+
console.print(f" Total Cost: ${total_cost:.2f}")
|
|
342
|
+
|
|
343
|
+
# Display any other summary metrics
|
|
344
|
+
for key, value in usage_data.items():
|
|
345
|
+
if key not in [
|
|
346
|
+
"requests",
|
|
347
|
+
"duration",
|
|
348
|
+
"cost",
|
|
349
|
+
"details",
|
|
350
|
+
"breakdown",
|
|
351
|
+
]:
|
|
352
|
+
if isinstance(value, int | float):
|
|
353
|
+
console.print(
|
|
354
|
+
f" {key.replace('_', ' ').title()}: " f"{value:,}"
|
|
355
|
+
)
|
|
356
|
+
else:
|
|
357
|
+
console.print(
|
|
358
|
+
f" {key.replace('_', ' ').title()}: {value}"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
def _display_usage_details(self, usage_data: dict[str, Any]) -> None:
|
|
362
|
+
"""Display detailed usage breakdown."""
|
|
363
|
+
console.print("\n[blue]Detailed Breakdown:[/blue]")
|
|
364
|
+
|
|
365
|
+
# Look for detailed breakdown data
|
|
366
|
+
details = usage_data.get("details", usage_data.get("breakdown", {}))
|
|
367
|
+
|
|
368
|
+
if details and isinstance(details, dict):
|
|
369
|
+
for category, data in details.items():
|
|
370
|
+
console.print(f"\n {category.replace('_', ' ').title()}:")
|
|
371
|
+
|
|
372
|
+
if isinstance(data, dict):
|
|
373
|
+
for key, value in data.items():
|
|
374
|
+
if isinstance(value, int | float):
|
|
375
|
+
console.print(
|
|
376
|
+
f" {key.replace('_', ' ').title()}: "
|
|
377
|
+
f"{value:,}"
|
|
378
|
+
)
|
|
379
|
+
else:
|
|
380
|
+
console.print(
|
|
381
|
+
f" {key.replace('_', ' ').title()}: {value}"
|
|
382
|
+
)
|
|
383
|
+
else:
|
|
384
|
+
console.print(f" {data}")
|
|
385
|
+
else:
|
|
386
|
+
console.print(" No detailed breakdown available")
|
|
387
|
+
|
|
388
|
+
def _format_duration(self, seconds: float) -> str:
|
|
389
|
+
"""Format duration in seconds to human readable format."""
|
|
390
|
+
if seconds < 60:
|
|
391
|
+
return f"{seconds:.1f} seconds"
|
|
392
|
+
elif seconds < 3600:
|
|
393
|
+
minutes = seconds / 60
|
|
394
|
+
return f"{minutes:.1f} minutes"
|
|
395
|
+
else:
|
|
396
|
+
hours = seconds / 3600
|
|
397
|
+
return f"{hours:.2f} hours"
|
|
398
|
+
|
|
399
|
+
def _format_cost(self, cost: float) -> str:
|
|
400
|
+
"""Format cost with currency symbol."""
|
|
401
|
+
return f"${cost:.2f}"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Models for usage command."""
|
|
2
|
+
|
|
3
|
+
from deepctl_core import BaseResult
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UsageBucket(BaseModel):
|
|
8
|
+
start: str # ISO date
|
|
9
|
+
end: str
|
|
10
|
+
hours: float
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UsageResult(BaseResult):
|
|
14
|
+
project_id: str
|
|
15
|
+
buckets: list[UsageBucket] = Field(default_factory=list)
|
|
16
|
+
total_hours: float = 0.0
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deepctl-cmd-usage
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Usage command for deepctl
|
|
5
|
+
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
|
+
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: deepgram,cli,usage,statistics
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: deepctl-core>=0.1.0
|
|
18
|
+
Requires-Dist: deepctl-shared-utils>=0.1.0
|
|
19
|
+
Requires-Dist: click>=8.0.0
|
|
20
|
+
Requires-Dist: rich>=13.0.0
|
|
21
|
+
Requires-Dist: deepgram-sdk>=3.0.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0.0
|
|
23
|
+
|
|
24
|
+
# deepctl-cmd-usage
|
|
25
|
+
|
|
26
|
+
Usage command for deepctl. This package is an internal component of deepctl and is not intended for standalone use.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/deepctl_cmd_usage/__init__.py
|
|
4
|
+
src/deepctl_cmd_usage/command.py
|
|
5
|
+
src/deepctl_cmd_usage/models.py
|
|
6
|
+
src/deepctl_cmd_usage.egg-info/PKG-INFO
|
|
7
|
+
src/deepctl_cmd_usage.egg-info/SOURCES.txt
|
|
8
|
+
src/deepctl_cmd_usage.egg-info/dependency_links.txt
|
|
9
|
+
src/deepctl_cmd_usage.egg-info/entry_points.txt
|
|
10
|
+
src/deepctl_cmd_usage.egg-info/requires.txt
|
|
11
|
+
src/deepctl_cmd_usage.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
deepctl_cmd_usage
|