perses-api-sdk 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.
- perses_api/__init__.py +36 -0
- perses_api/_base.py +136 -0
- perses_api/api.py +190 -0
- perses_api/dashboard.py +148 -0
- perses_api/datasource.py +119 -0
- perses_api/ephemeral_dashboard.py +160 -0
- perses_api/migrate.py +50 -0
- perses_api/model.py +533 -0
- perses_api/plugin.py +35 -0
- perses_api/project.py +127 -0
- perses_api/role.py +119 -0
- perses_api/role_binding.py +119 -0
- perses_api/secret.py +119 -0
- perses_api/user.py +127 -0
- perses_api/validate.py +57 -0
- perses_api/variable.py +119 -0
- perses_api_sdk-0.1.0.dist-info/METADATA +207 -0
- perses_api_sdk-0.1.0.dist-info/RECORD +21 -0
- perses_api_sdk-0.1.0.dist-info/WHEEL +5 -0
- perses_api_sdk-0.1.0.dist-info/licenses/LICENSE +201 -0
- perses_api_sdk-0.1.0.dist-info/top_level.txt +1 -0
perses_api/__init__.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from .model import APIModel
|
|
2
|
+
from .api import Api
|
|
3
|
+
from .project import Project
|
|
4
|
+
from .dashboard import Dashboard
|
|
5
|
+
from .ephemeral_dashboard import EphemeralDashboard
|
|
6
|
+
from .datasource import ProjectDatasource, GlobalDatasource
|
|
7
|
+
from .variable import ProjectVariable, GlobalVariable
|
|
8
|
+
from .role import ProjectRole, GlobalRole
|
|
9
|
+
from .role_binding import ProjectRoleBinding, GlobalRoleBinding
|
|
10
|
+
from .secret import ProjectSecret, GlobalSecret
|
|
11
|
+
from .user import User
|
|
12
|
+
from .plugin import Plugin
|
|
13
|
+
from .migrate import Migrate
|
|
14
|
+
from .validate import Validate
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"APIModel",
|
|
18
|
+
"Api",
|
|
19
|
+
"Project",
|
|
20
|
+
"Dashboard",
|
|
21
|
+
"EphemeralDashboard",
|
|
22
|
+
"ProjectDatasource",
|
|
23
|
+
"GlobalDatasource",
|
|
24
|
+
"ProjectVariable",
|
|
25
|
+
"GlobalVariable",
|
|
26
|
+
"ProjectRole",
|
|
27
|
+
"GlobalRole",
|
|
28
|
+
"ProjectRoleBinding",
|
|
29
|
+
"GlobalRoleBinding",
|
|
30
|
+
"ProjectSecret",
|
|
31
|
+
"GlobalSecret",
|
|
32
|
+
"User",
|
|
33
|
+
"Plugin",
|
|
34
|
+
"Migrate",
|
|
35
|
+
"Validate",
|
|
36
|
+
]
|
perses_api/_base.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from .api import Api
|
|
6
|
+
from .model import APIModel, RequestsMethods
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ResourceBase:
|
|
10
|
+
"""The class includes all necessary base methods to access dual-scoped Perses resources
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
perses_api_model (APIModel): Inject a Perses API model object that includes all necessary values and information
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
api (Api): This is where we store the api
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, perses_api_model: APIModel):
|
|
20
|
+
self.api = Api(perses_api_model)
|
|
21
|
+
|
|
22
|
+
def _base_path(self) -> str:
|
|
23
|
+
"""The method returns the base API path for the resource type
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
NotImplementedError: Subclasses must implement this method
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
str: The base API path
|
|
30
|
+
"""
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
|
|
33
|
+
def _get_all(self, name: str = None) -> list:
|
|
34
|
+
"""The method includes a functionality to retrieve all resources, optionally filtered by name
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
name (str): Specify a name to filter the results (default None)
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
Exception: Unspecified error by executing the API call
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
list: A list of resource dicts
|
|
44
|
+
"""
|
|
45
|
+
path = self._base_path()
|
|
46
|
+
if name:
|
|
47
|
+
path = f"{path}?name={name}"
|
|
48
|
+
result = self.api.call_the_api(path)
|
|
49
|
+
if not isinstance(result, list):
|
|
50
|
+
logging.error(f"Failed to retrieve resources from {self._base_path()}.")
|
|
51
|
+
raise Exception(result)
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
def _get_one(self, name: str) -> dict:
|
|
55
|
+
"""The method includes a functionality to retrieve a single resource by name
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name (str): Specify the name of the resource to retrieve
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
ValueError: Missed specifying a necessary value
|
|
62
|
+
Exception: Unspecified error by executing the API call
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
dict: The resource dict
|
|
66
|
+
"""
|
|
67
|
+
if not name:
|
|
68
|
+
raise ValueError("name must not be empty")
|
|
69
|
+
result = self.api.call_the_api(f"{self._base_path()}/{name}")
|
|
70
|
+
if not isinstance(result, dict):
|
|
71
|
+
logging.error(f"Failed to retrieve resource: {name}")
|
|
72
|
+
raise Exception(result)
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
def _create(self, body) -> dict:
|
|
76
|
+
"""The method includes a functionality to create a new resource
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
body (BaseModel): Specify the resource body as a Pydantic model instance
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
Exception: Unspecified error by executing the API call
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
dict: The created resource dict
|
|
86
|
+
"""
|
|
87
|
+
result = self.api.call_the_api(
|
|
88
|
+
self._base_path(),
|
|
89
|
+
method=RequestsMethods.POST,
|
|
90
|
+
json_complete=body.model_dump_json(by_alias=True, exclude_none=True),
|
|
91
|
+
)
|
|
92
|
+
if not isinstance(result, dict):
|
|
93
|
+
logging.error(f"Failed to create resource at {self._base_path()}.")
|
|
94
|
+
raise Exception(result)
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
def _update(self, name: str, body) -> dict:
|
|
98
|
+
"""The method includes a functionality to update an existing resource by name
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
name (str): Specify the name of the resource to update
|
|
102
|
+
body (BaseModel): Specify the updated resource body as a Pydantic model instance
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
ValueError: Missed specifying a necessary value
|
|
106
|
+
Exception: Unspecified error by executing the API call
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
dict: The updated resource dict
|
|
110
|
+
"""
|
|
111
|
+
if not name:
|
|
112
|
+
raise ValueError("name must not be empty")
|
|
113
|
+
result = self.api.call_the_api(
|
|
114
|
+
f"{self._base_path()}/{name}",
|
|
115
|
+
method=RequestsMethods.PUT,
|
|
116
|
+
json_complete=body.model_dump_json(by_alias=True, exclude_none=True),
|
|
117
|
+
)
|
|
118
|
+
if not isinstance(result, dict):
|
|
119
|
+
logging.error(f"Failed to update resource: {name}")
|
|
120
|
+
raise Exception(result)
|
|
121
|
+
return result
|
|
122
|
+
|
|
123
|
+
def _delete(self, name: str) -> None:
|
|
124
|
+
"""The method includes a functionality to delete a resource by name
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
name (str): Specify the name of the resource to delete
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
ValueError: Missed specifying a necessary value
|
|
131
|
+
"""
|
|
132
|
+
if not name:
|
|
133
|
+
raise ValueError("name must not be empty")
|
|
134
|
+
self.api.call_the_api(
|
|
135
|
+
f"{self._base_path()}/{name}", method=RequestsMethods.DELETE
|
|
136
|
+
)
|
perses_api/api.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import base64
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Union
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from .model import APIModel, RequestsMethods
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Api:
|
|
15
|
+
"""The class includes all necessary methods to access the Perses API
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
perses_api_model (APIModel): Inject a Perses API model object that includes all necessary values and information
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
perses_api_model (APIModel): This is where we store the perses_api_model
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, perses_api_model: APIModel):
|
|
25
|
+
self.perses_api_model = perses_api_model
|
|
26
|
+
|
|
27
|
+
def call_the_api(
|
|
28
|
+
self,
|
|
29
|
+
api_call: str,
|
|
30
|
+
method: RequestsMethods = RequestsMethods.GET,
|
|
31
|
+
json_complete: str = None,
|
|
32
|
+
response_status_code: bool = False,
|
|
33
|
+
) -> Any:
|
|
34
|
+
"""The method includes a functionality to execute a defined API call against the Perses endpoints
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
api_call (str): Specify the API call path relative to the host
|
|
38
|
+
method (RequestsMethods): Specify the HTTP method to use (default GET)
|
|
39
|
+
json_complete (str): Specify the JSON-serialised request body for POST and PUT requests (default None)
|
|
40
|
+
response_status_code (bool): Specify if the HTTP status code should be injected into the response dict (default False)
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: Missed specifying a necessary value
|
|
44
|
+
Exception: Unspecified error by executing the API call
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
any: The API response as a parsed dict or list, or the raw httpx.Response for non-JSON responses
|
|
48
|
+
"""
|
|
49
|
+
api_url = f"{self.perses_api_model.host}{api_call}"
|
|
50
|
+
headers = dict(self.perses_api_model.headers or {})
|
|
51
|
+
|
|
52
|
+
if self.perses_api_model.token:
|
|
53
|
+
headers["Authorization"] = f"Bearer {self.perses_api_model.token}"
|
|
54
|
+
elif self.perses_api_model.username and self.perses_api_model.password:
|
|
55
|
+
credentials = base64.b64encode(
|
|
56
|
+
f"{self.perses_api_model.username}:{self.perses_api_model.password}".encode()
|
|
57
|
+
).decode("utf-8")
|
|
58
|
+
headers["Authorization"] = f"Basic {credentials}"
|
|
59
|
+
|
|
60
|
+
headers["Content-Type"] = "application/json"
|
|
61
|
+
headers["Accept"] = "application/json"
|
|
62
|
+
|
|
63
|
+
http = self.create_the_http_api_client(headers)
|
|
64
|
+
|
|
65
|
+
if self.perses_api_model.http2_support:
|
|
66
|
+
|
|
67
|
+
async def _run():
|
|
68
|
+
async with http:
|
|
69
|
+
return self._check_the_api_call_response(
|
|
70
|
+
await self._send_request(http, method, api_url, json_complete),
|
|
71
|
+
response_status_code,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return asyncio.run(_run())
|
|
75
|
+
|
|
76
|
+
response = self._send_request(http, method, api_url, json_complete)
|
|
77
|
+
return self._check_the_api_call_response(response, response_status_code)
|
|
78
|
+
|
|
79
|
+
def create_the_http_api_client(
|
|
80
|
+
self, headers: dict = None
|
|
81
|
+
) -> Union[httpx.Client, httpx.AsyncClient]:
|
|
82
|
+
"""The method includes a functionality to create the HTTP client based on the API model configuration
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
headers (dict): Specify the HTTP headers to attach to every request (default None)
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Union[httpx.Client, httpx.AsyncClient]: A configured sync or async httpx client
|
|
89
|
+
"""
|
|
90
|
+
limits = httpx.Limits(max_connections=self.perses_api_model.num_pools)
|
|
91
|
+
|
|
92
|
+
if self.perses_api_model.http2_support:
|
|
93
|
+
transport = httpx.AsyncHTTPTransport(
|
|
94
|
+
retries=self.perses_api_model.retries,
|
|
95
|
+
http2=True,
|
|
96
|
+
)
|
|
97
|
+
return httpx.AsyncClient(
|
|
98
|
+
http2=True,
|
|
99
|
+
limits=limits,
|
|
100
|
+
timeout=self.perses_api_model.timeout,
|
|
101
|
+
headers=headers,
|
|
102
|
+
transport=transport,
|
|
103
|
+
verify=self.perses_api_model.ssl_context or True,
|
|
104
|
+
follow_redirects=self.perses_api_model.follow_redirects,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
transport = httpx.HTTPTransport(
|
|
108
|
+
verify=self.perses_api_model.ssl_context or True,
|
|
109
|
+
retries=self.perses_api_model.retries,
|
|
110
|
+
)
|
|
111
|
+
return httpx.Client(
|
|
112
|
+
limits=limits,
|
|
113
|
+
timeout=self.perses_api_model.timeout,
|
|
114
|
+
headers=headers,
|
|
115
|
+
transport=transport,
|
|
116
|
+
verify=self.perses_api_model.ssl_context or True,
|
|
117
|
+
follow_redirects=self.perses_api_model.follow_redirects,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _send_request(
|
|
121
|
+
self,
|
|
122
|
+
http: Union[httpx.Client, httpx.AsyncClient],
|
|
123
|
+
method: RequestsMethods,
|
|
124
|
+
api_url: str,
|
|
125
|
+
json_complete: str,
|
|
126
|
+
) -> Any:
|
|
127
|
+
"""The method includes a functionality to dispatch a single HTTP request using the provided client
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
http (Union[httpx.Client, httpx.AsyncClient]): Specify the httpx client to use for the request
|
|
131
|
+
method (RequestsMethods): Specify the HTTP method
|
|
132
|
+
api_url (str): Specify the fully-qualified request URL
|
|
133
|
+
json_complete (str): Specify the JSON-serialised request body for POST and PUT (default None)
|
|
134
|
+
|
|
135
|
+
Raises:
|
|
136
|
+
ValueError: Missed specifying a necessary value for POST or PUT requests
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
any: The raw httpx response object
|
|
140
|
+
"""
|
|
141
|
+
if method in (RequestsMethods.GET, RequestsMethods.DELETE):
|
|
142
|
+
return http.request(method.value, api_url)
|
|
143
|
+
if json_complete is None:
|
|
144
|
+
logging.error("Please define the json_complete.")
|
|
145
|
+
raise ValueError(f"json_complete is required for {method.value}")
|
|
146
|
+
return http.request(method.value, api_url, content=json_complete)
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def _check_the_api_call_response(
|
|
150
|
+
response: Any, response_status_code: bool = False
|
|
151
|
+
) -> Any:
|
|
152
|
+
"""The method includes a functionality to parse the API response and optionally inject the HTTP status code
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
response (any): Specify the raw httpx response object
|
|
156
|
+
response_status_code (bool): Specify if the HTTP status code should be injected into the response (default False)
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
any: The parsed JSON response as dict or list, or the raw response for non-JSON bodies
|
|
160
|
+
"""
|
|
161
|
+
if Api._check_if_valid_json(response.text):
|
|
162
|
+
json_response = json.loads(response.text)
|
|
163
|
+
if response_status_code:
|
|
164
|
+
if isinstance(json_response, dict):
|
|
165
|
+
json_response["status"] = response.status_code
|
|
166
|
+
elif isinstance(json_response, list) and json_response:
|
|
167
|
+
json_response[0]["status"] = response.status_code
|
|
168
|
+
return json_response
|
|
169
|
+
else:
|
|
170
|
+
if response_status_code:
|
|
171
|
+
return {"status": response.status_code, "data": response.text}
|
|
172
|
+
return response
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def _check_if_valid_json(response: str) -> bool:
|
|
176
|
+
"""The method includes a functionality to check if the given string is valid JSON
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
response (str): Specify the string to validate
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
bool: True if the string is valid JSON, False otherwise
|
|
183
|
+
"""
|
|
184
|
+
if not response or response.strip() in ("", "null"):
|
|
185
|
+
return False
|
|
186
|
+
try:
|
|
187
|
+
json.loads(response)
|
|
188
|
+
return True
|
|
189
|
+
except (TypeError, ValueError):
|
|
190
|
+
return False
|
perses_api/dashboard.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from .api import Api
|
|
6
|
+
from .model import APIModel, APIEndpoints, RequestsMethods
|
|
7
|
+
from .model import Dashboard as DashboardModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Dashboard:
|
|
11
|
+
"""The class includes all necessary methods to access the Perses dashboards API
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
perses_api_model (APIModel): Inject a Perses API model object that includes all necessary values and information
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
api (Api): This is where we store the api
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, perses_api_model: APIModel):
|
|
21
|
+
self.api = Api(perses_api_model)
|
|
22
|
+
|
|
23
|
+
def get_dashboards(self, project_name: str, name: str = None) -> list:
|
|
24
|
+
"""The method includes a functionality to retrieve all dashboards within a project
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
project_name (str): Specify the name of the project to list dashboards for
|
|
28
|
+
name (str): Specify a name to filter the results (default None)
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
ValueError: Missed specifying a necessary value
|
|
32
|
+
Exception: Unspecified error by executing the API call
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
list: A list of dashboard dicts
|
|
36
|
+
"""
|
|
37
|
+
if not project_name:
|
|
38
|
+
raise ValueError("project_name must not be empty")
|
|
39
|
+
path = APIEndpoints.DASHBOARDS.value.format(project=project_name)
|
|
40
|
+
if name:
|
|
41
|
+
path = f"{path}?name={name}"
|
|
42
|
+
result = self.api.call_the_api(path)
|
|
43
|
+
if not isinstance(result, list):
|
|
44
|
+
logging.error("Failed to retrieve dashboards.")
|
|
45
|
+
raise Exception(result)
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
def get_dashboard(self, project_name: str, name: str) -> dict:
|
|
49
|
+
"""The method includes a functionality to retrieve a single dashboard by name
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
project_name (str): Specify the name of the project the dashboard belongs to
|
|
53
|
+
name (str): Specify the name of the dashboard to retrieve
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: Missed specifying a necessary value
|
|
57
|
+
Exception: Unspecified error by executing the API call
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
dict: The dashboard dict
|
|
61
|
+
"""
|
|
62
|
+
if not project_name:
|
|
63
|
+
raise ValueError("project_name must not be empty")
|
|
64
|
+
if not name:
|
|
65
|
+
raise ValueError("name must not be empty")
|
|
66
|
+
result = self.api.call_the_api(
|
|
67
|
+
APIEndpoints.DASHBOARD.value.format(project=project_name, name=name)
|
|
68
|
+
)
|
|
69
|
+
if not isinstance(result, dict):
|
|
70
|
+
logging.error(f"Failed to retrieve dashboard: {name}")
|
|
71
|
+
raise Exception(result)
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
def create_dashboard(self, project_name: str, dashboard: DashboardModel) -> dict:
|
|
75
|
+
"""The method includes a functionality to create a new dashboard within a project
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
project_name (str): Specify the name of the project to create the dashboard in
|
|
79
|
+
dashboard (Dashboard): Specify the dashboard resource to create
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: Missed specifying a necessary value
|
|
83
|
+
Exception: Unspecified error by executing the API call
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
dict: The created dashboard dict
|
|
87
|
+
"""
|
|
88
|
+
if not project_name:
|
|
89
|
+
raise ValueError("project_name must not be empty")
|
|
90
|
+
result = self.api.call_the_api(
|
|
91
|
+
APIEndpoints.DASHBOARDS.value.format(project=project_name),
|
|
92
|
+
method=RequestsMethods.POST,
|
|
93
|
+
json_complete=dashboard.model_dump_json(by_alias=True, exclude_none=True),
|
|
94
|
+
)
|
|
95
|
+
if not isinstance(result, dict):
|
|
96
|
+
logging.error("Failed to create dashboard.")
|
|
97
|
+
raise Exception(result)
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
def update_dashboard(
|
|
101
|
+
self, project_name: str, name: str, dashboard: DashboardModel
|
|
102
|
+
) -> dict:
|
|
103
|
+
"""The method includes a functionality to update an existing dashboard by name
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
project_name (str): Specify the name of the project the dashboard belongs to
|
|
107
|
+
name (str): Specify the name of the dashboard to update
|
|
108
|
+
dashboard (Dashboard): Specify the updated dashboard resource
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ValueError: Missed specifying a necessary value
|
|
112
|
+
Exception: Unspecified error by executing the API call
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
dict: The updated dashboard dict
|
|
116
|
+
"""
|
|
117
|
+
if not project_name:
|
|
118
|
+
raise ValueError("project_name must not be empty")
|
|
119
|
+
if not name:
|
|
120
|
+
raise ValueError("name must not be empty")
|
|
121
|
+
result = self.api.call_the_api(
|
|
122
|
+
APIEndpoints.DASHBOARD.value.format(project=project_name, name=name),
|
|
123
|
+
method=RequestsMethods.PUT,
|
|
124
|
+
json_complete=dashboard.model_dump_json(by_alias=True, exclude_none=True),
|
|
125
|
+
)
|
|
126
|
+
if not isinstance(result, dict):
|
|
127
|
+
logging.error(f"Failed to update dashboard: {name}")
|
|
128
|
+
raise Exception(result)
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
def delete_dashboard(self, project_name: str, name: str) -> None:
|
|
132
|
+
"""The method includes a functionality to delete a dashboard by name
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
project_name (str): Specify the name of the project the dashboard belongs to
|
|
136
|
+
name (str): Specify the name of the dashboard to delete
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
ValueError: Missed specifying a necessary value
|
|
140
|
+
"""
|
|
141
|
+
if not project_name:
|
|
142
|
+
raise ValueError("project_name must not be empty")
|
|
143
|
+
if not name:
|
|
144
|
+
raise ValueError("name must not be empty")
|
|
145
|
+
self.api.call_the_api(
|
|
146
|
+
APIEndpoints.DASHBOARD.value.format(project=project_name, name=name),
|
|
147
|
+
method=RequestsMethods.DELETE,
|
|
148
|
+
)
|
perses_api/datasource.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._base import ResourceBase
|
|
4
|
+
from .model import APIModel, APIEndpoints
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DatasourceBase(ResourceBase):
|
|
8
|
+
"""The class includes all necessary base methods to access the Perses datasources API
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
perses_api_model (APIModel): Inject a Perses API model object that includes all necessary values and information
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
api (Api): This is where we store the api
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def get_datasources(self, name: str = None) -> list:
|
|
18
|
+
"""The method includes a functionality to retrieve all datasources
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
name (str): Specify a name to filter the results (default None)
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
Exception: Unspecified error by executing the API call
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
list: A list of datasource dicts
|
|
28
|
+
"""
|
|
29
|
+
return self._get_all(name)
|
|
30
|
+
|
|
31
|
+
def get_datasource(self, name: str) -> dict:
|
|
32
|
+
"""The method includes a functionality to retrieve a single datasource by name
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
name (str): Specify the name of the datasource to retrieve
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
ValueError: Missed specifying a necessary value
|
|
39
|
+
Exception: Unspecified error by executing the API call
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
dict: The datasource dict
|
|
43
|
+
"""
|
|
44
|
+
return self._get_one(name)
|
|
45
|
+
|
|
46
|
+
def create_datasource(self, datasource) -> dict:
|
|
47
|
+
"""The method includes a functionality to create a new datasource
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
datasource (Datasource | GlobalDatasource): Specify the datasource resource to create
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
Exception: Unspecified error by executing the API call
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
dict: The created datasource dict
|
|
57
|
+
"""
|
|
58
|
+
return self._create(datasource)
|
|
59
|
+
|
|
60
|
+
def update_datasource(self, name: str, datasource) -> dict:
|
|
61
|
+
"""The method includes a functionality to update an existing datasource by name
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
name (str): Specify the name of the datasource to update
|
|
65
|
+
datasource (Datasource | GlobalDatasource): Specify the updated datasource resource
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
ValueError: Missed specifying a necessary value
|
|
69
|
+
Exception: Unspecified error by executing the API call
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
dict: The updated datasource dict
|
|
73
|
+
"""
|
|
74
|
+
return self._update(name, datasource)
|
|
75
|
+
|
|
76
|
+
def delete_datasource(self, name: str) -> None:
|
|
77
|
+
"""The method includes a functionality to delete a datasource by name
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
name (str): Specify the name of the datasource to delete
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ValueError: Missed specifying a necessary value
|
|
84
|
+
"""
|
|
85
|
+
self._delete(name)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ProjectDatasource(DatasourceBase):
|
|
89
|
+
"""The class includes all necessary methods to access the Perses project-scoped datasources API
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
perses_api_model (APIModel): Inject a Perses API model object that includes all necessary values and information
|
|
93
|
+
project_name (str): Specify the name of the project to scope datasource operations to
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
api (Api): This is where we store the api
|
|
97
|
+
project_name (str): This is where we store the project_name
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self, perses_api_model: APIModel, project_name: str):
|
|
101
|
+
super().__init__(perses_api_model)
|
|
102
|
+
self.project_name = project_name
|
|
103
|
+
|
|
104
|
+
def _base_path(self) -> str:
|
|
105
|
+
return APIEndpoints.DATASOURCES.value.format(project=self.project_name)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class GlobalDatasource(DatasourceBase):
|
|
109
|
+
"""The class includes all necessary methods to access the Perses global datasources API
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
perses_api_model (APIModel): Inject a Perses API model object that includes all necessary values and information
|
|
113
|
+
|
|
114
|
+
Attributes:
|
|
115
|
+
api (Api): This is where we store the api
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def _base_path(self) -> str:
|
|
119
|
+
return APIEndpoints.GLOBAL_DATASOURCES.value
|