kuberoute 0.1.0__py3-none-any.whl

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.
kuberoute/__init__.py ADDED
@@ -0,0 +1,49 @@
1
+ """Kuberoute Python SDK."""
2
+
3
+ from .client import KubeRoute, ClustersResource, DecisionsResource, CostsResource
4
+ from .async_client import AsyncKubeRoute, AsyncClustersResource, AsyncDecisionsResource, AsyncCostsResource
5
+ from .models import (
6
+ User,
7
+ TenantInfo,
8
+ CurrentUserResponse,
9
+ Cluster,
10
+ ClusterListResponse,
11
+ Decision,
12
+ DecisionListResponse,
13
+ CostSummary,
14
+ HealthResponse,
15
+ AuthResponse,
16
+ )
17
+ from .exceptions import (
18
+ KubeRouteError,
19
+ AuthenticationError,
20
+ NotFoundError,
21
+ ValidationError,
22
+ ServerError,
23
+ )
24
+
25
+ __all__ = [
26
+ "KubeRoute",
27
+ "AsyncKubeRoute",
28
+ "ClustersResource",
29
+ "DecisionsResource",
30
+ "CostsResource",
31
+ "AsyncClustersResource",
32
+ "AsyncDecisionsResource",
33
+ "AsyncCostsResource",
34
+ "User",
35
+ "TenantInfo",
36
+ "CurrentUserResponse",
37
+ "Cluster",
38
+ "ClusterListResponse",
39
+ "Decision",
40
+ "DecisionListResponse",
41
+ "CostSummary",
42
+ "HealthResponse",
43
+ "AuthResponse",
44
+ "KubeRouteError",
45
+ "AuthenticationError",
46
+ "NotFoundError",
47
+ "ValidationError",
48
+ "ServerError",
49
+ ]
@@ -0,0 +1,182 @@
1
+ """Asynchronous Kuberoute Client."""
2
+
3
+ import os
4
+ from typing import Optional, Dict, Any
5
+ import httpx
6
+
7
+ from .models import (
8
+ CurrentUserResponse,
9
+ Cluster,
10
+ ClusterListResponse,
11
+ Decision,
12
+ DecisionListResponse,
13
+ CostSummary,
14
+ HealthResponse,
15
+ AuthResponse,
16
+ )
17
+ from .client import _handle_response
18
+
19
+
20
+ class AsyncClustersResource:
21
+ """Async operations on Kubernetes clusters."""
22
+
23
+ def __init__(self, client: "AsyncKubeRoute") -> None:
24
+ self._client = client
25
+
26
+ async def list(self, page: int = 1, limit: int = 20) -> ClusterListResponse:
27
+ """List all clusters registered with Kuberoute."""
28
+ data = await self._client._get("/api/v1/clusters", params={"page": page, "limit": limit})
29
+ return ClusterListResponse.model_validate(data)
30
+
31
+ async def get(self, cluster_id: str) -> Cluster:
32
+ """Get details of a specific cluster by ID."""
33
+ data = await self._client._get(f"/api/v1/clusters/{cluster_id}")
34
+ return Cluster.model_validate(data)
35
+
36
+ async def create(self, name: str, provider: Optional[str] = None, region: Optional[str] = None) -> Cluster:
37
+ """Register a new cluster."""
38
+ payload: Dict[str, Any] = {"name": name}
39
+ if provider:
40
+ payload["provider"] = provider
41
+ if region:
42
+ payload["region"] = region
43
+ data = await self._client._post("/api/v1/clusters", json_data=payload)
44
+ return Cluster.model_validate(data)
45
+
46
+ async def delete(self, cluster_id: str) -> None:
47
+ """Deregister and delete a cluster."""
48
+ await self._client._delete(f"/api/v1/clusters/{cluster_id}")
49
+
50
+
51
+ class AsyncDecisionsResource:
52
+ """Async operations on AI routing decisions."""
53
+
54
+ def __init__(self, client: "AsyncKubeRoute") -> None:
55
+ self._client = client
56
+
57
+ async def list(
58
+ self,
59
+ cluster_id: str,
60
+ status: Optional[str] = None,
61
+ page: int = 1,
62
+ limit: int = 20,
63
+ ) -> DecisionListResponse:
64
+ """List AI placement and routing decisions for a cluster."""
65
+ params: Dict[str, Any] = {"page": page, "limit": limit}
66
+ if status:
67
+ params["status"] = status
68
+ data = await self._client._get(f"/api/v1/clusters/{cluster_id}/decisions", params=params)
69
+ return DecisionListResponse.model_validate(data)
70
+
71
+ async def get(self, cluster_id: str, decision_id: str) -> Decision:
72
+ """Get a single decision by ID."""
73
+ data = await self._client._get(f"/api/v1/clusters/{cluster_id}/decisions/{decision_id}")
74
+ return Decision.model_validate(data)
75
+
76
+ async def apply(self, cluster_id: str, decision_id: str) -> bool:
77
+ """Apply an AI routing decision to the cluster."""
78
+ data = await self._client._post(f"/api/v1/clusters/{cluster_id}/decisions/{decision_id}/apply")
79
+ return data.get("status") == "applied"
80
+
81
+ async def reject(self, cluster_id: str, decision_id: str) -> bool:
82
+ """Reject an AI routing decision."""
83
+ data = await self._client._post(f"/api/v1/clusters/{cluster_id}/decisions/{decision_id}/reject")
84
+ return data.get("status") == "rejected"
85
+
86
+ async def revert(self, cluster_id: str, decision_id: str) -> Decision:
87
+ """Revert a previously applied decision."""
88
+ data = await self._client._post(f"/api/v1/clusters/{cluster_id}/decisions/{decision_id}/revert")
89
+ return Decision.model_validate(data)
90
+
91
+
92
+ class AsyncCostsResource:
93
+ """Async operations on infrastructure cost summaries and analytics."""
94
+
95
+ def __init__(self, client: "AsyncKubeRoute") -> None:
96
+ self._client = client
97
+
98
+ async def get_total(self) -> CostSummary:
99
+ """Get aggregate cost summary across all registered clusters."""
100
+ data = await self._client._get("/api/v1/costs")
101
+ return CostSummary.model_validate(data)
102
+
103
+ async def get_cluster(self, cluster_id: str) -> CostSummary:
104
+ """Get cost summary and estimated savings for a specific cluster."""
105
+ data = await self._client._get(f"/api/v1/clusters/{cluster_id}/costs")
106
+ return CostSummary.model_validate(data)
107
+
108
+
109
+ class AsyncKubeRoute:
110
+ """Asynchronous client for Kuberoute AI Orchestrator API."""
111
+
112
+ def __init__(
113
+ self,
114
+ base_url: Optional[str] = None,
115
+ token: Optional[str] = None,
116
+ timeout: float = 30.0,
117
+ ) -> None:
118
+ self.base_url = (
119
+ base_url
120
+ or os.environ.get("KUBEROUTE_API_URL")
121
+ or "http://localhost:8080"
122
+ ).rstrip("/")
123
+ self.token = token or os.environ.get("KUBEROUTE_TOKEN")
124
+ self.timeout = timeout
125
+ self._client = httpx.AsyncClient(timeout=self.timeout)
126
+
127
+ self.clusters = AsyncClustersResource(self)
128
+ self.decisions = AsyncDecisionsResource(self)
129
+ self.costs = AsyncCostsResource(self)
130
+
131
+ def _headers(self) -> Dict[str, str]:
132
+ headers = {
133
+ "Accept": "application/json",
134
+ "Content-Type": "application/json",
135
+ }
136
+ if self.token:
137
+ headers["Authorization"] = f"Bearer {self.token}"
138
+ return headers
139
+
140
+ async def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
141
+ url = f"{self.base_url}{path}"
142
+ resp = await self._client.get(url, headers=self._headers(), params=params)
143
+ return _handle_response(resp)
144
+
145
+ async def _post(self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Any:
146
+ url = f"{self.base_url}{path}"
147
+ resp = await self._client.post(url, headers=self._headers(), json=json_data, params=params)
148
+ return _handle_response(resp)
149
+
150
+ async def _delete(self, path: str) -> Any:
151
+ url = f"{self.base_url}{path}"
152
+ resp = await self._client.delete(url, headers=self._headers())
153
+ return _handle_response(resp)
154
+
155
+ async def health(self) -> HealthResponse:
156
+ """Check Orchestrator health status."""
157
+ data = await self._get("/health")
158
+ return HealthResponse.model_validate(data)
159
+
160
+ async def login_dev(self) -> AuthResponse:
161
+ """Perform dev authentication (local dev only) and save JWT token."""
162
+ url = f"{self.base_url}/auth/dev?format=json"
163
+ resp = await self._client.post(url, headers={"Accept": "application/json"})
164
+ data = _handle_response(resp)
165
+ auth_resp = AuthResponse.model_validate(data)
166
+ self.token = auth_resp.token
167
+ return auth_resp
168
+
169
+ async def me(self) -> CurrentUserResponse:
170
+ """Get current authenticated user and tenant info."""
171
+ data = await self._get("/api/v1/me")
172
+ return CurrentUserResponse.model_validate(data)
173
+
174
+ async def close(self) -> None:
175
+ """Close the underlying HTTP client."""
176
+ await self._client.aclose()
177
+
178
+ async def __aenter__(self) -> "AsyncKubeRoute":
179
+ return self
180
+
181
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
182
+ await self.close()
kuberoute/client.py ADDED
@@ -0,0 +1,216 @@
1
+ """Synchronous Kuberoute Client."""
2
+
3
+ import os
4
+ from typing import Optional, Dict, Any
5
+ import httpx
6
+
7
+ from .models import (
8
+ User,
9
+ CurrentUserResponse,
10
+ Cluster,
11
+ ClusterListResponse,
12
+ Decision,
13
+ DecisionListResponse,
14
+ CostSummary,
15
+ HealthResponse,
16
+ AuthResponse,
17
+ )
18
+ from .exceptions import (
19
+ KubeRouteError,
20
+ AuthenticationError,
21
+ NotFoundError,
22
+ ValidationError,
23
+ ServerError,
24
+ )
25
+
26
+
27
+ def _handle_response(resp: httpx.Response) -> Any:
28
+ if resp.status_code == 204:
29
+ return None
30
+ try:
31
+ data = resp.json()
32
+ except Exception:
33
+ data = {"raw": resp.text}
34
+
35
+ if resp.status_code >= 400:
36
+ err_msg = data.get("error") if isinstance(data, dict) else str(data)
37
+ if not err_msg:
38
+ err_msg = f"HTTP {resp.status_code}"
39
+
40
+ if resp.status_code in (401, 403):
41
+ raise AuthenticationError(err_msg, status_code=resp.status_code, details=data)
42
+ elif resp.status_code == 404:
43
+ raise NotFoundError(err_msg, status_code=resp.status_code, details=data)
44
+ elif resp.status_code == 400:
45
+ raise ValidationError(err_msg, status_code=resp.status_code, details=data)
46
+ elif resp.status_code >= 500:
47
+ raise ServerError(err_msg, status_code=resp.status_code, details=data)
48
+ else:
49
+ raise KubeRouteError(err_msg, status_code=resp.status_code, details=data)
50
+
51
+ return data
52
+
53
+
54
+ class ClustersResource:
55
+ """Operations on Kubernetes clusters."""
56
+
57
+ def __init__(self, client: "KubeRoute") -> None:
58
+ self._client = client
59
+
60
+ def list(self, page: int = 1, limit: int = 20) -> ClusterListResponse:
61
+ """List all clusters registered with Kuberoute."""
62
+ data = self._client._get("/api/v1/clusters", params={"page": page, "limit": limit})
63
+ return ClusterListResponse.model_validate(data)
64
+
65
+ def get(self, cluster_id: str) -> Cluster:
66
+ """Get details of a specific cluster by ID."""
67
+ data = self._client._get(f"/api/v1/clusters/{cluster_id}")
68
+ return Cluster.model_validate(data)
69
+
70
+ def create(self, name: str, provider: Optional[str] = None, region: Optional[str] = None) -> Cluster:
71
+ """Register a new cluster."""
72
+ payload: Dict[str, Any] = {"name": name}
73
+ if provider:
74
+ payload["provider"] = provider
75
+ if region:
76
+ payload["region"] = region
77
+ data = self._client._post("/api/v1/clusters", json_data=payload)
78
+ return Cluster.model_validate(data)
79
+
80
+ def delete(self, cluster_id: str) -> None:
81
+ """Deregister and delete a cluster."""
82
+ self._client._delete(f"/api/v1/clusters/{cluster_id}")
83
+
84
+
85
+ class DecisionsResource:
86
+ """Operations on AI routing decisions."""
87
+
88
+ def __init__(self, client: "KubeRoute") -> None:
89
+ self._client = client
90
+
91
+ def list(
92
+ self,
93
+ cluster_id: str,
94
+ status: Optional[str] = None,
95
+ page: int = 1,
96
+ limit: int = 20,
97
+ ) -> DecisionListResponse:
98
+ """List AI placement and routing decisions for a cluster."""
99
+ params: Dict[str, Any] = {"page": page, "limit": limit}
100
+ if status:
101
+ params["status"] = status
102
+ data = self._client._get(f"/api/v1/clusters/{cluster_id}/decisions", params=params)
103
+ return DecisionListResponse.model_validate(data)
104
+
105
+ def get(self, cluster_id: str, decision_id: str) -> Decision:
106
+ """Get a single decision by ID."""
107
+ data = self._client._get(f"/api/v1/clusters/{cluster_id}/decisions/{decision_id}")
108
+ return Decision.model_validate(data)
109
+
110
+ def apply(self, cluster_id: str, decision_id: str) -> bool:
111
+ """Apply an AI routing decision to the cluster."""
112
+ data = self._client._post(f"/api/v1/clusters/{cluster_id}/decisions/{decision_id}/apply")
113
+ return data.get("status") == "applied"
114
+
115
+ def reject(self, cluster_id: str, decision_id: str) -> bool:
116
+ """Reject an AI routing decision."""
117
+ data = self._client._post(f"/api/v1/clusters/{cluster_id}/decisions/{decision_id}/reject")
118
+ return data.get("status") == "rejected"
119
+
120
+ def revert(self, cluster_id: str, decision_id: str) -> Decision:
121
+ """Revert a previously applied decision."""
122
+ data = self._client._post(f"/api/v1/clusters/{cluster_id}/decisions/{decision_id}/revert")
123
+ return Decision.model_validate(data)
124
+
125
+
126
+ class CostsResource:
127
+ """Operations on infrastructure cost summaries and analytics."""
128
+
129
+ def __init__(self, client: "KubeRoute") -> None:
130
+ self._client = client
131
+
132
+ def get_total(self) -> CostSummary:
133
+ """Get aggregate cost summary across all registered clusters."""
134
+ data = self._client._get("/api/v1/costs")
135
+ return CostSummary.model_validate(data)
136
+
137
+ def get_cluster(self, cluster_id: str) -> CostSummary:
138
+ """Get cost summary and estimated savings for a specific cluster."""
139
+ data = self._client._get(f"/api/v1/clusters/{cluster_id}/costs")
140
+ return CostSummary.model_validate(data)
141
+
142
+
143
+ class KubeRoute:
144
+ """Synchronous client for Kuberoute AI Orchestrator API."""
145
+
146
+ def __init__(
147
+ self,
148
+ base_url: Optional[str] = None,
149
+ token: Optional[str] = None,
150
+ timeout: float = 30.0,
151
+ ) -> None:
152
+ self.base_url = (
153
+ base_url
154
+ or os.environ.get("KUBEROUTE_API_URL")
155
+ or "http://localhost:8080"
156
+ ).rstrip("/")
157
+ self.token = token or os.environ.get("KUBEROUTE_TOKEN")
158
+ self.timeout = timeout
159
+ self._client = httpx.Client(timeout=self.timeout)
160
+
161
+ self.clusters = ClustersResource(self)
162
+ self.decisions = DecisionsResource(self)
163
+ self.costs = CostsResource(self)
164
+
165
+ def _headers(self) -> Dict[str, str]:
166
+ headers = {
167
+ "Accept": "application/json",
168
+ "Content-Type": "application/json",
169
+ }
170
+ if self.token:
171
+ headers["Authorization"] = f"Bearer {self.token}"
172
+ return headers
173
+
174
+ def _get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
175
+ url = f"{self.base_url}{path}"
176
+ resp = self._client.get(url, headers=self._headers(), params=params)
177
+ return _handle_response(resp)
178
+
179
+ def _post(self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Any:
180
+ url = f"{self.base_url}{path}"
181
+ resp = self._client.post(url, headers=self._headers(), json=json_data, params=params)
182
+ return _handle_response(resp)
183
+
184
+ def _delete(self, path: str) -> Any:
185
+ url = f"{self.base_url}{path}"
186
+ resp = self._client.delete(url, headers=self._headers())
187
+ return _handle_response(resp)
188
+
189
+ def health(self) -> HealthResponse:
190
+ """Check Orchestrator health status."""
191
+ data = self._get("/health")
192
+ return HealthResponse.model_validate(data)
193
+
194
+ def login_dev(self) -> AuthResponse:
195
+ """Perform dev authentication (local dev only) and save JWT token."""
196
+ url = f"{self.base_url}/auth/dev?format=json"
197
+ resp = self._client.post(url, headers={"Accept": "application/json"})
198
+ data = _handle_response(resp)
199
+ auth_resp = AuthResponse.model_validate(data)
200
+ self.token = auth_resp.token
201
+ return auth_resp
202
+
203
+ def me(self) -> CurrentUserResponse:
204
+ """Get current authenticated user and tenant info."""
205
+ data = self._get("/api/v1/me")
206
+ return CurrentUserResponse.model_validate(data)
207
+
208
+ def close(self) -> None:
209
+ """Close the underlying HTTP client."""
210
+ self._client.close()
211
+
212
+ def __enter__(self) -> "KubeRoute":
213
+ return self
214
+
215
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
216
+ self.close()
@@ -0,0 +1,38 @@
1
+ """Kuberoute SDK Exceptions."""
2
+
3
+ from typing import Optional, Any
4
+
5
+
6
+ class KubeRouteError(Exception):
7
+ """Base exception for all KubeRoute SDK errors."""
8
+
9
+ def __init__(self, message: str, status_code: Optional[int] = None, details: Optional[Any] = None) -> None:
10
+ super().__init__(message)
11
+ self.message = message
12
+ self.status_code = status_code
13
+ self.details = details
14
+
15
+ def __str__(self) -> str:
16
+ if self.status_code:
17
+ return f"[{self.status_code}] {self.message}"
18
+ return self.message
19
+
20
+
21
+ class AuthenticationError(KubeRouteError):
22
+ """Raised when authentication fails (401 Unauthorized or 403 Forbidden)."""
23
+ pass
24
+
25
+
26
+ class NotFoundError(KubeRouteError):
27
+ """Raised when a requested resource is not found (404)."""
28
+ pass
29
+
30
+
31
+ class ValidationError(KubeRouteError):
32
+ """Raised when request payload or parameters fail validation (400 Bad Request)."""
33
+ pass
34
+
35
+
36
+ class ServerError(KubeRouteError):
37
+ """Raised when the Kuberoute server returns an internal server error (500+)."""
38
+ pass
kuberoute/models.py ADDED
@@ -0,0 +1,91 @@
1
+ """Pydantic Models for Kuberoute SDK."""
2
+
3
+ from datetime import datetime
4
+ from typing import Dict, List, Optional, Any
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class User(BaseModel):
9
+ id: str
10
+ email: Optional[str] = None
11
+ name: Optional[str] = None
12
+ avatar_url: Optional[str] = None
13
+ role: Optional[str] = None
14
+ tenant_id: Optional[str] = None
15
+ created_at: Optional[datetime] = None
16
+ updated_at: Optional[datetime] = None
17
+
18
+
19
+ class TenantInfo(BaseModel):
20
+ id: str
21
+ plan: str = "free"
22
+ task_credits: int = 0
23
+ task_credits_used: int = 0
24
+
25
+
26
+ class CurrentUserResponse(BaseModel):
27
+ user: User
28
+ tenant: TenantInfo
29
+
30
+
31
+ class Cluster(BaseModel):
32
+ id: str
33
+ tenant_id: Optional[str] = None
34
+ name: str
35
+ provider: Optional[str] = None
36
+ region: Optional[str] = None
37
+ k8s_version: Optional[str] = None
38
+ agent_version: Optional[str] = None
39
+ status: Optional[str] = "active"
40
+ node_count: Optional[int] = 0
41
+ created_at: Optional[datetime] = None
42
+ updated_at: Optional[datetime] = None
43
+
44
+
45
+ class ClusterListResponse(BaseModel):
46
+ clusters: List[Cluster] = Field(default_factory=list)
47
+ total: int = 0
48
+ page: int = 1
49
+ limit: int = 20
50
+
51
+
52
+ class Decision(BaseModel):
53
+ id: str
54
+ cluster_id: str
55
+ workload_id: Optional[str] = None
56
+ decision_type: Optional[str] = None
57
+ target_node_id: Optional[str] = None
58
+ confidence_score: Optional[float] = None
59
+ predicted_cost_savings: Optional[float] = None
60
+ predicted_latency_ms: Optional[int] = None
61
+ reason: Optional[str] = None
62
+ status: Optional[str] = "pending"
63
+ applied: Optional[bool] = False
64
+ applied_at: Optional[datetime] = None
65
+ applied_by: Optional[str] = None
66
+ revert_of: Optional[str] = None
67
+ created_at: Optional[datetime] = None
68
+
69
+
70
+ class DecisionListResponse(BaseModel):
71
+ decisions: List[Decision] = Field(default_factory=list)
72
+ total: int = 0
73
+ page: int = 1
74
+ limit: int = 20
75
+
76
+
77
+ class CostSummary(BaseModel):
78
+ cluster_id: Optional[str] = None
79
+ total: float = 0.0
80
+ optimized: float = 0.0
81
+ savings: float = 0.0
82
+ breakdown: Dict[str, float] = Field(default_factory=dict)
83
+
84
+
85
+ class HealthResponse(BaseModel):
86
+ status: str = "ok"
87
+
88
+
89
+ class AuthResponse(BaseModel):
90
+ user: User
91
+ token: str
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.5
2
+ Name: kuberoute
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for Kuberoute AI Intelligent Routing & Orchestration
5
+ Author-email: Kuberoute AI Team <vikas.budde@hotmail.com>
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Intended Audience :: System Administrators
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: System :: Distributed Computing
19
+ Requires-Python: >=3.9
20
+ Requires-Dist: httpx>=0.24.0
21
+ Requires-Dist: pydantic>=2.0.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
24
+ Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
25
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
26
+ Requires-Dist: respx>=0.20.0; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Kuberoute Python SDK
30
+
31
+ Official Python SDK for [Kuberoute AI](https://kuberoute.ai) — Intelligent Kubernetes Workload Placement & Cost Optimization.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install kuberoute
37
+ ```
38
+
39
+ Or install in editable mode from this monorepo:
40
+
41
+ ```bash
42
+ pip install -e packages/sdk-python
43
+ ```
44
+
45
+ ## Quickstart
46
+
47
+ ### Synchronous Usage
48
+
49
+ ```python
50
+ from kuberoute import KubeRoute
51
+
52
+ # Initialize the client
53
+ client = KubeRoute(
54
+ base_url="http://localhost:8080", # or https://api.kuberoute.ai
55
+ token="your-jwt-token" # or KUBEROUTE_TOKEN env var
56
+ )
57
+
58
+ # In local dev mode, you can log in directly:
59
+ client.login_dev()
60
+
61
+ # Check health
62
+ health = client.health()
63
+ print(f"Server status: {health.status}")
64
+
65
+ # List registered Kubernetes clusters
66
+ clusters = client.clusters.list()
67
+ for cluster in clusters.clusters:
68
+ print(f"Cluster: {cluster.name} ({cluster.provider}/{cluster.region})")
69
+
70
+ # Register a new cluster
71
+ new_cluster = client.clusters.create(
72
+ name="production-eks",
73
+ provider="aws",
74
+ region="us-west-2"
75
+ )
76
+ print(f"Registered cluster ID: {new_cluster.id}")
77
+
78
+ # Fetch routing & placement decisions
79
+ decisions = client.decisions.list(cluster_id=new_cluster.id)
80
+ for d in decisions.decisions:
81
+ print(f"Decision: {d.id} | Savings: ${d.predicted_cost_savings}/mo | Reason: {d.reason}")
82
+ if d.confidence_score > 0.9:
83
+ client.decisions.apply(cluster_id=new_cluster.id, decision_id=d.id)
84
+ print("Applied decision!")
85
+
86
+ # Get aggregate infrastructure costs and savings
87
+ costs = client.costs.get_total()
88
+ print(f"Total: ${costs.total} | Optimized: ${costs.optimized} | Savings: ${costs.savings}")
89
+ ```
90
+
91
+ ### Asynchronous Usage (`AsyncKubeRoute`)
92
+
93
+ ```python
94
+ import asyncio
95
+ from kuberoute import AsyncKubeRoute
96
+
97
+ async def main():
98
+ async with AsyncKubeRoute(base_url="http://localhost:8080", token="your-token") as client:
99
+ clusters = await client.clusters.list()
100
+ print(f"Found {clusters.total} clusters")
101
+
102
+ asyncio.run(main())
103
+ ```
104
+
105
+ ## Resources
106
+
107
+ - **`client.clusters`**: `list()`, `get(id)`, `create(name, provider, region)`, `delete(id)`
108
+ - **`client.decisions`**: `list(cluster_id, status, page, limit)`, `get(cluster_id, decision_id)`, `apply(...)`, `reject(...)`, `revert(...)`
109
+ - **`client.costs`**: `get_total()`, `get_cluster(cluster_id)`
110
+ - **`client.me()`**: Get current authenticated user and tenant credit stats.
@@ -0,0 +1,8 @@
1
+ kuberoute/__init__.py,sha256=hGc1ppd6zRd4p0ubJaTNQbiHryM1g6AjhNsy0BOHoYA,1082
2
+ kuberoute/async_client.py,sha256=Aj_ayYR49vTheNKEo7JGvBlwvc2R_cU4OxBw5tOtC6U,6935
3
+ kuberoute/client.py,sha256=_OiqvioTZJ3iQeP2hsAXLimhaiH1munv2TEP_ooPRmg,7722
4
+ kuberoute/exceptions.py,sha256=zs7-m94NLSNa3ywuHpMkQW7IhB-Aamj61KGl_1RXJWw,1062
5
+ kuberoute/models.py,sha256=eLvUXMvCL14bpzqEdXmjzh_xNhHeCRTtx-v1LU3r4vQ,2262
6
+ kuberoute-0.1.0.dist-info/METADATA,sha256=Gz1VCat7JGUdy4KAR-UV-UQqLymDW1E_iAuGglBK32k,3527
7
+ kuberoute-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
8
+ kuberoute-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any