opsmanager 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.
- opsmanager/__init__.py +67 -0
- opsmanager/auth.py +92 -0
- opsmanager/client.py +206 -0
- opsmanager/errors.py +332 -0
- opsmanager/network.py +392 -0
- opsmanager/pagination.py +197 -0
- opsmanager/py.typed +0 -0
- opsmanager/services/__init__.py +39 -0
- opsmanager/services/alerts.py +169 -0
- opsmanager/services/base.py +205 -0
- opsmanager/services/clusters.py +141 -0
- opsmanager/services/deployments.py +276 -0
- opsmanager/services/measurements.py +397 -0
- opsmanager/services/organizations.py +135 -0
- opsmanager/services/performance_advisor.py +252 -0
- opsmanager/services/projects.py +110 -0
- opsmanager/types.py +521 -0
- opsmanager-0.1.0.dist-info/METADATA +259 -0
- opsmanager-0.1.0.dist-info/RECORD +22 -0
- opsmanager-0.1.0.dist-info/WHEEL +5 -0
- opsmanager-0.1.0.dist-info/licenses/LICENSE +190 -0
- opsmanager-0.1.0.dist-info/top_level.txt +1 -0
opsmanager/__init__.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Copyright 2024 Frank Snow
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
MongoDB Ops Manager Python Client
|
|
17
|
+
|
|
18
|
+
A production-quality Python client library for the MongoDB Ops Manager API.
|
|
19
|
+
|
|
20
|
+
Example usage:
|
|
21
|
+
from opsmanager import OpsManagerClient
|
|
22
|
+
|
|
23
|
+
client = OpsManagerClient(
|
|
24
|
+
base_url="https://ops-manager.example.com",
|
|
25
|
+
public_key="your-public-key",
|
|
26
|
+
private_key="your-private-key",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# List all hosts in a project
|
|
30
|
+
hosts = client.deployments.list_hosts(project_id="your-project-id")
|
|
31
|
+
|
|
32
|
+
# Get metrics for a host
|
|
33
|
+
metrics = client.measurements.host(
|
|
34
|
+
project_id="your-project-id",
|
|
35
|
+
host_id="host-id",
|
|
36
|
+
granularity="PT1M",
|
|
37
|
+
period="P1D",
|
|
38
|
+
)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
__version__ = "0.1.0"
|
|
42
|
+
__author__ = "Frank Snow"
|
|
43
|
+
|
|
44
|
+
from opsmanager.client import OpsManagerClient
|
|
45
|
+
from opsmanager.errors import (
|
|
46
|
+
OpsManagerError,
|
|
47
|
+
OpsManagerAuthenticationError,
|
|
48
|
+
OpsManagerNotFoundError,
|
|
49
|
+
OpsManagerBadRequestError,
|
|
50
|
+
OpsManagerForbiddenError,
|
|
51
|
+
OpsManagerConflictError,
|
|
52
|
+
OpsManagerServerError,
|
|
53
|
+
OpsManagerRateLimitError,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"OpsManagerClient",
|
|
58
|
+
"OpsManagerError",
|
|
59
|
+
"OpsManagerAuthenticationError",
|
|
60
|
+
"OpsManagerNotFoundError",
|
|
61
|
+
"OpsManagerBadRequestError",
|
|
62
|
+
"OpsManagerForbiddenError",
|
|
63
|
+
"OpsManagerConflictError",
|
|
64
|
+
"OpsManagerServerError",
|
|
65
|
+
"OpsManagerRateLimitError",
|
|
66
|
+
"__version__",
|
|
67
|
+
]
|
opsmanager/auth.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Copyright 2024 Frank Snow
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Authentication module for MongoDB Ops Manager API.
|
|
17
|
+
|
|
18
|
+
The Ops Manager API uses HTTP Digest Authentication with API key pairs.
|
|
19
|
+
This module provides the authentication handler for requests.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from requests.auth import HTTPDigestAuth
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OpsManagerAuth(HTTPDigestAuth):
|
|
27
|
+
"""HTTP Digest Authentication for Ops Manager API.
|
|
28
|
+
|
|
29
|
+
Ops Manager uses HTTP Digest Authentication with a public/private API key pair.
|
|
30
|
+
The public key is used as the username and the private key as the password.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
auth = OpsManagerAuth(
|
|
34
|
+
public_key="your-public-key",
|
|
35
|
+
private_key="your-private-key"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Used with requests
|
|
39
|
+
response = requests.get(url, auth=auth)
|
|
40
|
+
|
|
41
|
+
Note:
|
|
42
|
+
API keys can be created at either the Organization or Project level
|
|
43
|
+
in Ops Manager. The key's permissions determine which API endpoints
|
|
44
|
+
are accessible.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, public_key: str, private_key: str):
|
|
48
|
+
"""Initialize the authentication handler.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
public_key: The Ops Manager API public key (used as username).
|
|
52
|
+
private_key: The Ops Manager API private key (used as password).
|
|
53
|
+
"""
|
|
54
|
+
super().__init__(username=public_key, password=private_key)
|
|
55
|
+
self._public_key = public_key
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def public_key(self) -> str:
|
|
59
|
+
"""Return the public key (for logging/debugging, not the private key)."""
|
|
60
|
+
return self._public_key
|
|
61
|
+
|
|
62
|
+
def __repr__(self) -> str:
|
|
63
|
+
# Mask the private key in repr for security
|
|
64
|
+
return f"OpsManagerAuth(public_key={self._public_key!r}, private_key='***')"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_auth(
|
|
68
|
+
public_key: Optional[str] = None,
|
|
69
|
+
private_key: Optional[str] = None,
|
|
70
|
+
) -> OpsManagerAuth:
|
|
71
|
+
"""Create an authentication handler from API keys.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
public_key: The Ops Manager API public key.
|
|
75
|
+
private_key: The Ops Manager API private key.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
OpsManagerAuth: Configured authentication handler.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
ValueError: If public_key or private_key is not provided.
|
|
82
|
+
|
|
83
|
+
Note:
|
|
84
|
+
In a future version, this could support loading keys from
|
|
85
|
+
environment variables or configuration files.
|
|
86
|
+
"""
|
|
87
|
+
if not public_key:
|
|
88
|
+
raise ValueError("public_key is required")
|
|
89
|
+
if not private_key:
|
|
90
|
+
raise ValueError("private_key is required")
|
|
91
|
+
|
|
92
|
+
return OpsManagerAuth(public_key=public_key, private_key=private_key)
|
opsmanager/client.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Copyright 2024 Frank Snow
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Main client for MongoDB Ops Manager API.
|
|
17
|
+
|
|
18
|
+
This is the primary entry point for interacting with the Ops Manager API.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Callable, Optional
|
|
22
|
+
|
|
23
|
+
from opsmanager.auth import OpsManagerAuth
|
|
24
|
+
from opsmanager.network import NetworkSession
|
|
25
|
+
from opsmanager.services.organizations import OrganizationsService
|
|
26
|
+
from opsmanager.services.projects import ProjectsService
|
|
27
|
+
from opsmanager.services.clusters import ClustersService
|
|
28
|
+
from opsmanager.services.deployments import DeploymentsService
|
|
29
|
+
from opsmanager.services.measurements import MeasurementsService
|
|
30
|
+
from opsmanager.services.performance_advisor import PerformanceAdvisorService
|
|
31
|
+
from opsmanager.services.alerts import AlertsService
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class OpsManagerClient:
|
|
35
|
+
"""Client for MongoDB Ops Manager API.
|
|
36
|
+
|
|
37
|
+
This is the main entry point for interacting with Ops Manager.
|
|
38
|
+
It provides access to all API services through properties.
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
from opsmanager import OpsManagerClient
|
|
42
|
+
|
|
43
|
+
# Create client
|
|
44
|
+
client = OpsManagerClient(
|
|
45
|
+
base_url="https://ops-manager.example.com",
|
|
46
|
+
public_key="your-public-key",
|
|
47
|
+
private_key="your-private-key",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Use services
|
|
51
|
+
projects = client.projects.list()
|
|
52
|
+
hosts = client.deployments.list_hosts(project_id="abc123")
|
|
53
|
+
metrics = client.measurements.host(
|
|
54
|
+
project_id="abc123",
|
|
55
|
+
host_id="host123",
|
|
56
|
+
period="P1D",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Close when done
|
|
60
|
+
client.close()
|
|
61
|
+
|
|
62
|
+
# Or use as context manager
|
|
63
|
+
with OpsManagerClient(...) as client:
|
|
64
|
+
projects = client.projects.list()
|
|
65
|
+
|
|
66
|
+
Attributes:
|
|
67
|
+
organizations: Service for managing organizations.
|
|
68
|
+
projects: Service for managing projects (groups).
|
|
69
|
+
clusters: Service for managing clusters.
|
|
70
|
+
deployments: Service for hosts, databases, and disks.
|
|
71
|
+
measurements: Service for time-series metrics.
|
|
72
|
+
performance_advisor: Service for slow query analysis and index suggestions.
|
|
73
|
+
alerts: Service for alert management.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# Default base URL for Cloud Manager (Ops Manager URL must be provided)
|
|
77
|
+
DEFAULT_BASE_URL = "https://cloud.mongodb.com"
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
base_url: str,
|
|
82
|
+
public_key: str,
|
|
83
|
+
private_key: str,
|
|
84
|
+
timeout: float = 30.0,
|
|
85
|
+
rate_limit: float = 2.0,
|
|
86
|
+
retry_count: int = 3,
|
|
87
|
+
retry_backoff: float = 1.0,
|
|
88
|
+
verify_ssl: bool = True,
|
|
89
|
+
user_agent: Optional[str] = None,
|
|
90
|
+
):
|
|
91
|
+
"""Initialize the Ops Manager client.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
base_url: Base URL for the Ops Manager instance
|
|
95
|
+
(e.g., "https://ops-manager.example.com").
|
|
96
|
+
public_key: API public key.
|
|
97
|
+
private_key: API private key.
|
|
98
|
+
timeout: Request timeout in seconds (default 30).
|
|
99
|
+
rate_limit: Maximum requests per second (default 2).
|
|
100
|
+
Set conservatively to protect production Ops Manager.
|
|
101
|
+
retry_count: Number of retries for failed requests (default 3).
|
|
102
|
+
retry_backoff: Base backoff time between retries in seconds.
|
|
103
|
+
verify_ssl: Whether to verify SSL certificates (default True).
|
|
104
|
+
user_agent: Custom User-Agent string.
|
|
105
|
+
"""
|
|
106
|
+
# Create authentication handler
|
|
107
|
+
auth = OpsManagerAuth(public_key=public_key, private_key=private_key)
|
|
108
|
+
|
|
109
|
+
# Create network session with rate limiting
|
|
110
|
+
self._session = NetworkSession(
|
|
111
|
+
base_url=base_url.rstrip("/"),
|
|
112
|
+
auth=auth,
|
|
113
|
+
timeout=timeout,
|
|
114
|
+
rate_limit=rate_limit,
|
|
115
|
+
retry_count=retry_count,
|
|
116
|
+
retry_backoff=retry_backoff,
|
|
117
|
+
verify_ssl=verify_ssl,
|
|
118
|
+
user_agent=user_agent,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Initialize services
|
|
122
|
+
self._organizations = OrganizationsService(self._session)
|
|
123
|
+
self._projects = ProjectsService(self._session)
|
|
124
|
+
self._clusters = ClustersService(self._session)
|
|
125
|
+
self._deployments = DeploymentsService(self._session)
|
|
126
|
+
self._measurements = MeasurementsService(self._session)
|
|
127
|
+
self._performance_advisor = PerformanceAdvisorService(self._session)
|
|
128
|
+
self._alerts = AlertsService(self._session)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def organizations(self) -> OrganizationsService:
|
|
132
|
+
"""Service for managing organizations."""
|
|
133
|
+
return self._organizations
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def projects(self) -> ProjectsService:
|
|
137
|
+
"""Service for managing projects (groups)."""
|
|
138
|
+
return self._projects
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def clusters(self) -> ClustersService:
|
|
142
|
+
"""Service for managing clusters."""
|
|
143
|
+
return self._clusters
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def deployments(self) -> DeploymentsService:
|
|
147
|
+
"""Service for hosts, databases, and disks."""
|
|
148
|
+
return self._deployments
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def measurements(self) -> MeasurementsService:
|
|
152
|
+
"""Service for time-series metrics."""
|
|
153
|
+
return self._measurements
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def performance_advisor(self) -> PerformanceAdvisorService:
|
|
157
|
+
"""Service for slow query analysis and index suggestions."""
|
|
158
|
+
return self._performance_advisor
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def alerts(self) -> AlertsService:
|
|
162
|
+
"""Service for alert management."""
|
|
163
|
+
return self._alerts
|
|
164
|
+
|
|
165
|
+
def set_rate_limit(self, rate: float) -> None:
|
|
166
|
+
"""Update the rate limit for API requests.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
rate: Maximum requests per second.
|
|
170
|
+
"""
|
|
171
|
+
self._session.set_rate_limit(rate)
|
|
172
|
+
|
|
173
|
+
def on_request(self, callback: Callable) -> None:
|
|
174
|
+
"""Set a callback to be invoked before each request.
|
|
175
|
+
|
|
176
|
+
Useful for logging or debugging.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
callback: Function(method, url, kwargs) called before each request.
|
|
180
|
+
"""
|
|
181
|
+
self._session.on_request(callback)
|
|
182
|
+
|
|
183
|
+
def on_response(self, callback: Callable) -> None:
|
|
184
|
+
"""Set a callback to be invoked after each response.
|
|
185
|
+
|
|
186
|
+
Useful for logging or debugging.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
callback: Function(response) called after each response.
|
|
190
|
+
"""
|
|
191
|
+
self._session.on_response(callback)
|
|
192
|
+
|
|
193
|
+
def close(self) -> None:
|
|
194
|
+
"""Close the client and release resources."""
|
|
195
|
+
self._session.close()
|
|
196
|
+
|
|
197
|
+
def __enter__(self) -> "OpsManagerClient":
|
|
198
|
+
"""Enter context manager."""
|
|
199
|
+
return self
|
|
200
|
+
|
|
201
|
+
def __exit__(self, *args) -> None:
|
|
202
|
+
"""Exit context manager."""
|
|
203
|
+
self.close()
|
|
204
|
+
|
|
205
|
+
def __repr__(self) -> str:
|
|
206
|
+
return f"OpsManagerClient(base_url={self._session.base_url!r})"
|
opsmanager/errors.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# Copyright 2024 Frank Snow
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Exception hierarchy for MongoDB Ops Manager API client.
|
|
17
|
+
|
|
18
|
+
All exceptions inherit from OpsManagerError, allowing callers to catch
|
|
19
|
+
all API-related errors with a single except clause if desired.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import Any, Dict, Optional
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OpsManagerError(Exception):
|
|
26
|
+
"""Base exception for all Ops Manager API errors.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
message: Human-readable error message.
|
|
30
|
+
status_code: HTTP status code from the API response (if applicable).
|
|
31
|
+
error_code: Ops Manager error code from the response (if applicable).
|
|
32
|
+
detail: Detailed error message from the API response.
|
|
33
|
+
response: Raw response data from the API.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
message: str,
|
|
39
|
+
status_code: Optional[int] = None,
|
|
40
|
+
error_code: Optional[str] = None,
|
|
41
|
+
detail: Optional[str] = None,
|
|
42
|
+
response: Optional[Dict[str, Any]] = None,
|
|
43
|
+
):
|
|
44
|
+
super().__init__(message)
|
|
45
|
+
self.message = message
|
|
46
|
+
self.status_code = status_code
|
|
47
|
+
self.error_code = error_code
|
|
48
|
+
self.detail = detail
|
|
49
|
+
self.response = response or {}
|
|
50
|
+
|
|
51
|
+
def __str__(self) -> str:
|
|
52
|
+
parts = [self.message]
|
|
53
|
+
if self.error_code:
|
|
54
|
+
parts.append(f"[{self.error_code}]")
|
|
55
|
+
if self.detail:
|
|
56
|
+
parts.append(f"- {self.detail}")
|
|
57
|
+
return " ".join(parts)
|
|
58
|
+
|
|
59
|
+
def __repr__(self) -> str:
|
|
60
|
+
return (
|
|
61
|
+
f"{self.__class__.__name__}("
|
|
62
|
+
f"message={self.message!r}, "
|
|
63
|
+
f"status_code={self.status_code}, "
|
|
64
|
+
f"error_code={self.error_code!r})"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class OpsManagerAuthenticationError(OpsManagerError):
|
|
69
|
+
"""Authentication failed (HTTP 401).
|
|
70
|
+
|
|
71
|
+
Raised when API credentials are invalid or missing.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
message: str = "Authentication failed",
|
|
77
|
+
error_code: Optional[str] = None,
|
|
78
|
+
detail: Optional[str] = None,
|
|
79
|
+
response: Optional[Dict[str, Any]] = None,
|
|
80
|
+
):
|
|
81
|
+
super().__init__(
|
|
82
|
+
message=message,
|
|
83
|
+
status_code=401,
|
|
84
|
+
error_code=error_code,
|
|
85
|
+
detail=detail,
|
|
86
|
+
response=response,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class OpsManagerForbiddenError(OpsManagerError):
|
|
91
|
+
"""Access forbidden (HTTP 403).
|
|
92
|
+
|
|
93
|
+
Raised when the authenticated user lacks permission for the requested resource.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
message: str = "Access forbidden",
|
|
99
|
+
error_code: Optional[str] = None,
|
|
100
|
+
detail: Optional[str] = None,
|
|
101
|
+
response: Optional[Dict[str, Any]] = None,
|
|
102
|
+
):
|
|
103
|
+
super().__init__(
|
|
104
|
+
message=message,
|
|
105
|
+
status_code=403,
|
|
106
|
+
error_code=error_code,
|
|
107
|
+
detail=detail,
|
|
108
|
+
response=response,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class OpsManagerNotFoundError(OpsManagerError):
|
|
113
|
+
"""Resource not found (HTTP 404).
|
|
114
|
+
|
|
115
|
+
Raised when the requested resource does not exist.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
message: str = "Resource not found",
|
|
121
|
+
error_code: Optional[str] = None,
|
|
122
|
+
detail: Optional[str] = None,
|
|
123
|
+
response: Optional[Dict[str, Any]] = None,
|
|
124
|
+
):
|
|
125
|
+
super().__init__(
|
|
126
|
+
message=message,
|
|
127
|
+
status_code=404,
|
|
128
|
+
error_code=error_code,
|
|
129
|
+
detail=detail,
|
|
130
|
+
response=response,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class OpsManagerBadRequestError(OpsManagerError):
|
|
135
|
+
"""Bad request (HTTP 400).
|
|
136
|
+
|
|
137
|
+
Raised when the request is malformed or contains invalid parameters.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
message: str = "Bad request",
|
|
143
|
+
error_code: Optional[str] = None,
|
|
144
|
+
detail: Optional[str] = None,
|
|
145
|
+
response: Optional[Dict[str, Any]] = None,
|
|
146
|
+
):
|
|
147
|
+
super().__init__(
|
|
148
|
+
message=message,
|
|
149
|
+
status_code=400,
|
|
150
|
+
error_code=error_code,
|
|
151
|
+
detail=detail,
|
|
152
|
+
response=response,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class OpsManagerConflictError(OpsManagerError):
|
|
157
|
+
"""Conflict (HTTP 409).
|
|
158
|
+
|
|
159
|
+
Raised when the request conflicts with the current state of the resource,
|
|
160
|
+
such as attempting to create a resource that already exists.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def __init__(
|
|
164
|
+
self,
|
|
165
|
+
message: str = "Conflict",
|
|
166
|
+
error_code: Optional[str] = None,
|
|
167
|
+
detail: Optional[str] = None,
|
|
168
|
+
response: Optional[Dict[str, Any]] = None,
|
|
169
|
+
):
|
|
170
|
+
super().__init__(
|
|
171
|
+
message=message,
|
|
172
|
+
status_code=409,
|
|
173
|
+
error_code=error_code,
|
|
174
|
+
detail=detail,
|
|
175
|
+
response=response,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class OpsManagerRateLimitError(OpsManagerError):
|
|
180
|
+
"""Rate limit exceeded (HTTP 429).
|
|
181
|
+
|
|
182
|
+
Raised when too many requests have been made in a given time period.
|
|
183
|
+
|
|
184
|
+
Attributes:
|
|
185
|
+
retry_after: Seconds to wait before retrying (if provided by API).
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
def __init__(
|
|
189
|
+
self,
|
|
190
|
+
message: str = "Rate limit exceeded",
|
|
191
|
+
error_code: Optional[str] = None,
|
|
192
|
+
detail: Optional[str] = None,
|
|
193
|
+
response: Optional[Dict[str, Any]] = None,
|
|
194
|
+
retry_after: Optional[int] = None,
|
|
195
|
+
):
|
|
196
|
+
super().__init__(
|
|
197
|
+
message=message,
|
|
198
|
+
status_code=429,
|
|
199
|
+
error_code=error_code,
|
|
200
|
+
detail=detail,
|
|
201
|
+
response=response,
|
|
202
|
+
)
|
|
203
|
+
self.retry_after = retry_after
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class OpsManagerServerError(OpsManagerError):
|
|
207
|
+
"""Server error (HTTP 5xx).
|
|
208
|
+
|
|
209
|
+
Raised when the Ops Manager server encounters an internal error.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
def __init__(
|
|
213
|
+
self,
|
|
214
|
+
message: str = "Server error",
|
|
215
|
+
status_code: int = 500,
|
|
216
|
+
error_code: Optional[str] = None,
|
|
217
|
+
detail: Optional[str] = None,
|
|
218
|
+
response: Optional[Dict[str, Any]] = None,
|
|
219
|
+
):
|
|
220
|
+
super().__init__(
|
|
221
|
+
message=message,
|
|
222
|
+
status_code=status_code,
|
|
223
|
+
error_code=error_code,
|
|
224
|
+
detail=detail,
|
|
225
|
+
response=response,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class OpsManagerTimeoutError(OpsManagerError):
|
|
230
|
+
"""Request timeout.
|
|
231
|
+
|
|
232
|
+
Raised when a request to the Ops Manager API times out.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
def __init__(
|
|
236
|
+
self,
|
|
237
|
+
message: str = "Request timed out",
|
|
238
|
+
detail: Optional[str] = None,
|
|
239
|
+
):
|
|
240
|
+
super().__init__(
|
|
241
|
+
message=message,
|
|
242
|
+
detail=detail,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class OpsManagerConnectionError(OpsManagerError):
|
|
247
|
+
"""Connection error.
|
|
248
|
+
|
|
249
|
+
Raised when unable to connect to the Ops Manager API.
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
def __init__(
|
|
253
|
+
self,
|
|
254
|
+
message: str = "Connection failed",
|
|
255
|
+
detail: Optional[str] = None,
|
|
256
|
+
):
|
|
257
|
+
super().__init__(
|
|
258
|
+
message=message,
|
|
259
|
+
detail=detail,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class OpsManagerValidationError(OpsManagerError):
|
|
264
|
+
"""Client-side validation error.
|
|
265
|
+
|
|
266
|
+
Raised when input validation fails before making an API request.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
def __init__(
|
|
270
|
+
self,
|
|
271
|
+
message: str,
|
|
272
|
+
field: Optional[str] = None,
|
|
273
|
+
):
|
|
274
|
+
detail = f"Invalid value for field: {field}" if field else None
|
|
275
|
+
super().__init__(
|
|
276
|
+
message=message,
|
|
277
|
+
detail=detail,
|
|
278
|
+
)
|
|
279
|
+
self.field = field
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def raise_for_status(status_code: int, response_data: Dict[str, Any]) -> None:
|
|
283
|
+
"""Raise an appropriate exception based on HTTP status code.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
status_code: HTTP status code from the response.
|
|
287
|
+
response_data: Parsed JSON response body.
|
|
288
|
+
|
|
289
|
+
Raises:
|
|
290
|
+
OpsManagerError: Appropriate subclass based on status code.
|
|
291
|
+
"""
|
|
292
|
+
if 200 <= status_code < 300:
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
error_code = response_data.get("errorCode")
|
|
296
|
+
detail = response_data.get("detail")
|
|
297
|
+
reason = response_data.get("reason", "")
|
|
298
|
+
|
|
299
|
+
error_classes = {
|
|
300
|
+
400: OpsManagerBadRequestError,
|
|
301
|
+
401: OpsManagerAuthenticationError,
|
|
302
|
+
403: OpsManagerForbiddenError,
|
|
303
|
+
404: OpsManagerNotFoundError,
|
|
304
|
+
409: OpsManagerConflictError,
|
|
305
|
+
429: OpsManagerRateLimitError,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if status_code in error_classes:
|
|
309
|
+
raise error_classes[status_code](
|
|
310
|
+
message=reason or error_classes[status_code].__doc__.split("\n")[0],
|
|
311
|
+
error_code=error_code,
|
|
312
|
+
detail=detail,
|
|
313
|
+
response=response_data,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if status_code >= 500:
|
|
317
|
+
raise OpsManagerServerError(
|
|
318
|
+
message=reason or "Server error",
|
|
319
|
+
status_code=status_code,
|
|
320
|
+
error_code=error_code,
|
|
321
|
+
detail=detail,
|
|
322
|
+
response=response_data,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# Generic error for unexpected status codes
|
|
326
|
+
raise OpsManagerError(
|
|
327
|
+
message=reason or f"HTTP {status_code}",
|
|
328
|
+
status_code=status_code,
|
|
329
|
+
error_code=error_code,
|
|
330
|
+
detail=detail,
|
|
331
|
+
response=response_data,
|
|
332
|
+
)
|