Skip to content

Commit 67f3d99

Browse files
author
Datata1
committed
feat(usage): Add usage history manager
1 parent b2e644c commit 67f3d99

File tree

12 files changed

+1251
-9
lines changed

12 files changed

+1251
-9
lines changed

examples/create_workspace_with_landscape.py renamed to examples/scripts/create_workspace_with_landscape.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import time
3+
from datetime import datetime, timedelta, timezone
34

45
from codesphere import CodesphereSDK
56
from codesphere.resources.workspace import WorkspaceCreate
@@ -10,7 +11,7 @@
1011
)
1112
from codesphere.resources.workspace.logs import LogStage
1213

13-
TEAM_ID = 123
14+
TEAM_ID = 35698
1415

1516

1617
async def main():
@@ -28,6 +29,9 @@ async def main():
2829
)
2930
print(f"✓ Workspace created (ID: {workspace.id})")
3031

32+
# Get the team for usage history access
33+
team = await sdk.teams.get(team_id=TEAM_ID)
34+
3135
print("Waiting for workspace to start...")
3236
await workspace.wait_until_running(timeout=300.0, poll_interval=5.0)
3337
print("✓ Workspace is running\n")
@@ -90,6 +94,73 @@ async def main():
9094

9195
print(f"\n✓ Stream ended ({count} log entries)")
9296

97+
# ========================================
98+
# Usage History Collection
99+
# ========================================
100+
print("\n--- Usage History ---")
101+
102+
end_date = datetime.now(timezone.utc)
103+
begin_date = end_date - timedelta(days=1)
104+
105+
print(
106+
f"Fetching usage summary from {begin_date.isoformat()} to {end_date.isoformat()}..."
107+
)
108+
usage_summary = await team.usage.get_landscape_summary(
109+
begin_date=begin_date,
110+
end_date=end_date,
111+
limit=50,
112+
)
113+
114+
print(f"Total resources with usage: {usage_summary.total_items}")
115+
print(f"Page {usage_summary.current_page} of {usage_summary.total_pages}")
116+
117+
if usage_summary.items:
118+
print("\nResource Usage Summary:")
119+
for item in usage_summary.items:
120+
hours = item.usage_seconds / 3600
121+
print(f" • {item.resource_name}")
122+
print(f" - Plan: {item.plan_name}")
123+
print(
124+
f" - Usage: {hours:.2f} hours ({item.usage_seconds:.0f} seconds)"
125+
)
126+
print(f" - Replicas: {item.replicas}")
127+
print(f" - Always On: {item.always_on}")
128+
print()
129+
130+
first_resource = usage_summary.items[0]
131+
print(f"Fetching events for '{first_resource.resource_name}'...")
132+
133+
events = await team.usage.get_landscape_events(
134+
resource_id=first_resource.resource_id,
135+
begin_date=begin_date,
136+
end_date=end_date,
137+
)
138+
139+
print(f"Total events: {events.total_items}")
140+
for event in events.items:
141+
print(
142+
f" [{event.date.isoformat()}] {event.action.value.upper()} by {event.initiator_email}"
143+
)
144+
145+
print("\nRefreshing usage summary...")
146+
await usage_summary.refresh()
147+
print(f"✓ Refreshed - still {usage_summary.total_items} items")
148+
else:
149+
print("No usage data found for the specified time range.")
150+
151+
print("\n--- Auto-Pagination Example ---")
152+
print("Iterating through ALL usage summaries (auto-pagination):")
153+
item_count = 0
154+
async for item in team.usage.iter_all_landscape_summary(
155+
begin_date=begin_date,
156+
end_date=end_date,
157+
page_size=25, # Fetch 25 at a time
158+
):
159+
item_count += 1
160+
print(f" {item_count}. {item.resource_id}: {item.usage_seconds:.0f}s")
161+
162+
print(f"\n✓ Total items processed: {item_count}")
163+
93164

94165
if __name__ == "__main__":
95166
asyncio.run(main())
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import argparse
2+
import asyncio
3+
4+
from codesphere import CodesphereSDK
5+
6+
7+
async def delete_all_workspaces(team_id: int, dry_run: bool = False) -> None:
8+
async with CodesphereSDK() as sdk:
9+
print(f"Fetching workspaces for team {team_id}...")
10+
workspaces = await sdk.workspaces.list(team_id=team_id)
11+
12+
if not workspaces:
13+
print("No workspaces found in this team.")
14+
return
15+
16+
print(f"Found {len(workspaces)} workspace(s):\n")
17+
for ws in workspaces:
18+
print(f" • {ws.name} (ID: {ws.id})")
19+
20+
if dry_run:
21+
print("\n[DRY RUN] No workspaces were deleted.")
22+
return
23+
24+
print("\n" + "=" * 50)
25+
confirm = input(f"Delete all {len(workspaces)} workspaces? (yes/no): ")
26+
if confirm.lower() != "yes":
27+
print("Aborted.")
28+
return
29+
30+
print("\nDeleting workspaces...")
31+
for ws in workspaces:
32+
try:
33+
await ws.delete()
34+
print(f" ✓ Deleted: {ws.name} (ID: {ws.id})")
35+
except Exception as e:
36+
print(f" ✗ Failed to delete {ws.name}: {e}")
37+
38+
print(f"\n✓ Done. Deleted {len(workspaces)} workspace(s).")
39+
40+
41+
def main():
42+
parser = argparse.ArgumentParser(description="Delete all workspaces in a team")
43+
parser.add_argument("--team-id", type=int, required=True, help="Team ID")
44+
parser.add_argument(
45+
"--dry-run",
46+
action="store_true",
47+
help="Show what would be deleted without actually deleting",
48+
)
49+
args = parser.parse_args()
50+
51+
asyncio.run(delete_all_workspaces(team_id=args.team_id, dry_run=args.dry_run))
52+
53+
54+
if __name__ == "__main__":
55+
main()
Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1-
from .schemas import Team, TeamCreate, TeamBase
2-
from .resources import TeamsResource
31
from .domain.resources import (
4-
Domain,
52
CustomDomainConfig,
6-
DomainVerificationStatus,
3+
Domain,
74
DomainBase,
85
DomainRouting,
6+
DomainVerificationStatus,
7+
)
8+
from .resources import TeamsResource
9+
from .schemas import Team, TeamBase, TeamCreate
10+
from .usage import (
11+
LandscapeServiceEvent,
12+
LandscapeServiceSummary,
13+
PaginatedResponse,
14+
ServiceAction,
15+
TeamUsageManager,
16+
UsageEventsResponse,
17+
UsageSummaryResponse,
918
)
10-
1119

1220
__all__ = [
1321
"Team",
@@ -19,4 +27,11 @@
1927
"DomainVerificationStatus",
2028
"DomainBase",
2129
"DomainRouting",
30+
"TeamUsageManager",
31+
"LandscapeServiceEvent",
32+
"LandscapeServiceSummary",
33+
"PaginatedResponse",
34+
"ServiceAction",
35+
"UsageEventsResponse",
36+
"UsageSummaryResponse",
2237
]

src/codesphere/resources/team/schemas.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from __future__ import annotations
2+
23
from functools import cached_property
3-
from pydantic import Field
44
from typing import Optional
55

6-
from .domain.manager import TeamDomainManager
6+
from pydantic import Field
7+
8+
from ...core import APIOperation, AsyncCallable, _APIOperationExecutor
79
from ...core.base import CamelModel
8-
from ...core import _APIOperationExecutor, APIOperation, AsyncCallable
10+
from .domain.manager import TeamDomainManager
11+
from .usage.manager import TeamUsageManager
912

1013

1114
class TeamCreate(CamelModel):
@@ -39,3 +42,8 @@ class Team(TeamBase, _APIOperationExecutor):
3942
def domains(self) -> TeamDomainManager:
4043
http_client = self.validate_http_client()
4144
return TeamDomainManager(http_client, team_id=self.id)
45+
46+
@cached_property
47+
def usage(self) -> TeamUsageManager:
48+
http_client = self.validate_http_client()
49+
return TeamUsageManager(http_client, team_id=self.id)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Team usage history resources."""
2+
3+
from .manager import TeamUsageManager
4+
from .schemas import (
5+
LandscapeServiceEvent,
6+
LandscapeServiceSummary,
7+
PaginatedResponse,
8+
ServiceAction,
9+
UsageEventsResponse,
10+
UsageSummaryResponse,
11+
)
12+
13+
__all__ = [
14+
"TeamUsageManager",
15+
"LandscapeServiceEvent",
16+
"LandscapeServiceSummary",
17+
"PaginatedResponse",
18+
"ServiceAction",
19+
"UsageEventsResponse",
20+
"UsageSummaryResponse",
21+
]

0 commit comments

Comments
 (0)