osc_sdk_python 0.39.2__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.
@@ -0,0 +1,322 @@
1
+ import os
2
+ import sys
3
+ from .call import Call
4
+ from .limiter import RateLimiter
5
+ import ruamel.yaml
6
+ from .version import get_version
7
+ import warnings
8
+ from datetime import timedelta
9
+
10
+ type_mapping = {"boolean": "bool", "string": "str", "integer": "int", "array": "list"}
11
+
12
+ # Logs Output Options
13
+ LOG_NONE = 0
14
+ LOG_STDERR = 1
15
+ LOG_STDIO = 2
16
+ LOG_MEMORY = 4
17
+
18
+ # what to Log
19
+ LOG_ALL = 0
20
+ LOG_KEEP_ONLY_LAST_REQ = 1
21
+
22
+ # Default
23
+ DEFAULT_LIMITER_WINDOW = timedelta(seconds=1) # 1 second
24
+ DEFAULT_LIMITER_MAX_REQUESTS = 5 # 5 requests / sec
25
+
26
+
27
+ class ActionNotExists(NotImplementedError):
28
+ pass
29
+
30
+
31
+ class ParameterNotValid(NotImplementedError):
32
+ pass
33
+
34
+
35
+ class ParameterIsRequired(NotImplementedError):
36
+ pass
37
+
38
+
39
+ class ParameterHasWrongType(NotImplementedError):
40
+ pass
41
+
42
+
43
+ class Logger:
44
+ string = ""
45
+ type = LOG_NONE
46
+ what = LOG_ALL
47
+
48
+ def config(self, type=None, what=None):
49
+ if type is not None:
50
+ self.type = type
51
+ if what is not None:
52
+ self.what = what
53
+
54
+ def str(self):
55
+ if self.type == LOG_MEMORY:
56
+ return self.string
57
+ return None
58
+
59
+ def do_log(self, s):
60
+ if self.type & LOG_MEMORY:
61
+ if self.what == LOG_KEEP_ONLY_LAST_REQ:
62
+ self.string = s
63
+ else:
64
+ self.string = self.string + "\n" + s
65
+
66
+ if self.type & LOG_STDIO:
67
+ print(s)
68
+ if self.type & LOG_STDERR:
69
+ print(s, file=sys.stderr)
70
+
71
+
72
+ class BaseAPI:
73
+ def __init__(self, spec, **kwargs):
74
+ self._load_gateway_structure(spec)
75
+ self._load_errors()
76
+ self.log = Logger()
77
+ self.limiter = RateLimiter(DEFAULT_LIMITER_WINDOW, DEFAULT_LIMITER_MAX_REQUESTS)
78
+ self.call = Call(
79
+ logger=self.log,
80
+ version=self.endpoint_api_version,
81
+ limiter=self.limiter,
82
+ **kwargs,
83
+ )
84
+
85
+ def update_credentials(self, **kwargs):
86
+ warnings.warn(
87
+ "update_credentials in deprecated. Use update_profile instead.",
88
+ DeprecationWarning,
89
+ stacklevel=2,
90
+ )
91
+ self.update_profile(**kwargs)
92
+
93
+ def update_profile(self, **kwargs):
94
+ """
95
+ destroy and create a new credential map use for each call.
96
+ so you can change your ak/sk, region without having to recreate the whole Gateway
97
+ as the object is recreate, you can't expect to keep parameter from the old configuration
98
+ example: just updating the password, without renter the login will fail
99
+ """
100
+ self.call.update_profile(**kwargs)
101
+
102
+ def access_key(self):
103
+ return self.call.profile.access_key
104
+
105
+ def secret_key(self):
106
+ return self.call.profile.secret_key
107
+
108
+ def region(self):
109
+ return self.call.profile.region
110
+
111
+ def email(self):
112
+ warnings.warn(
113
+ "email in deprecated. Use login instead.",
114
+ DeprecationWarning,
115
+ stacklevel=2,
116
+ )
117
+ return self.login()
118
+
119
+ def login(self):
120
+ return self.call.profile.login
121
+
122
+ def password(self):
123
+ return self.call.profile.password
124
+
125
+ def _convert(self, input_file):
126
+ structure = {}
127
+ try:
128
+ with open(input_file, "r") as fi:
129
+ yaml = ruamel.yaml.YAML(typ="safe")
130
+ content = yaml.load(fi.read())
131
+ except Exception as err:
132
+ print("Problem reading {}:{}".format(input_file, str(err)))
133
+ self.api_version = content["info"]["version"]
134
+ self.endpoint_api_version = content["servers"][0]["url"].split("/")[-1]
135
+ for action, params in content["components"]["schemas"].items():
136
+ if action.endswith("Request"):
137
+ action_name = action.split("Request")[0]
138
+ structure[action_name] = {}
139
+ for propertie_name, properties in params["properties"].items():
140
+ if propertie_name == "DryRun":
141
+ continue
142
+ if "type" not in properties.keys():
143
+ action_type = None
144
+ else:
145
+ action_type = type_mapping[properties["type"]]
146
+ structure[action_name][propertie_name] = {
147
+ "type": action_type,
148
+ "required": False,
149
+ }
150
+
151
+ if "required" in params.keys():
152
+ for required in params["required"]:
153
+ structure[action_name][required]["required"] = True
154
+ return structure
155
+
156
+ def _load_gateway_structure(self, spec):
157
+ self.gateway_structure = self._convert(spec)
158
+
159
+ def _load_errors(self):
160
+ dir_path = os.path.join(os.path.dirname(__file__))
161
+ yaml_file = os.path.abspath("{}/resources/gateway_errors.yaml".format(dir_path))
162
+ with open(yaml_file, "r") as yam:
163
+ yaml = ruamel.yaml.YAML(typ="safe")
164
+ self.gateway_errors = yaml.load(yam.read())
165
+
166
+ def _check_parameters_type(self, action_structure, input_structure):
167
+ for i_param, i_value in input_structure.items():
168
+ if (
169
+ i_param != "Filters"
170
+ and action_structure[i_param]["type"] is not None
171
+ and action_structure[i_param]["type"] != i_value.__class__.__name__
172
+ ):
173
+ raise ParameterHasWrongType(
174
+ "{} is <{}> instead of <{}>".format(
175
+ i_param,
176
+ i_value.__class__.__name__,
177
+ action_structure[i_param]["type"],
178
+ )
179
+ )
180
+
181
+ def _check_parameters_required(self, action_structure, input_structure):
182
+ action_mandatory_params = [
183
+ param for param in action_structure if action_structure[param]["required"]
184
+ ]
185
+ difference = set(action_mandatory_params).difference(
186
+ set(input_structure.keys())
187
+ )
188
+ if difference:
189
+ raise ParameterIsRequired(
190
+ "Missing {}. Required parameters are {}".format(
191
+ ", ".join(list(difference)), ", ".join(action_mandatory_params)
192
+ )
193
+ )
194
+
195
+ def _check_parameters_valid(self, action_name, params):
196
+ structure_parameters = self.gateway_structure[action_name].keys()
197
+ input_parameters = set(params)
198
+ different_parameters = list(
199
+ input_parameters.difference(set(structure_parameters))
200
+ )
201
+ if different_parameters:
202
+ raise ParameterNotValid(
203
+ """{}. Available parameters on sdk: {} api: {} are: {}.""".format(
204
+ ", ".join(different_parameters),
205
+ get_version(),
206
+ self.api_version,
207
+ ", ".join(structure_parameters),
208
+ )
209
+ )
210
+
211
+ def _check(self, action_name, **params):
212
+ if action_name not in self.gateway_structure:
213
+ raise ActionNotExists(
214
+ "Action {} does not exists for python sdk: {} with api: {}".format(
215
+ action_name, get_version(), self.api_version
216
+ )
217
+ )
218
+ self._check_parameters_valid(action_name, params)
219
+ self._check_parameters_required(self.gateway_structure[action_name], params)
220
+ self._check_parameters_type(self.gateway_structure[action_name], params)
221
+
222
+ @staticmethod
223
+ def _remove_none_parameters(**params):
224
+ """
225
+ Remove parameters having None as value
226
+ to perform CreateVolumes(Iops=None, Size=10)
227
+ """
228
+ return {key: value for key, value in params.items() if value is not None}
229
+
230
+ def _get_action(self, action_name):
231
+ def action(**kwargs):
232
+ kwargs = self._remove_none_parameters(**kwargs)
233
+ self._check(action_name, **kwargs)
234
+ result = self.call.api(action_name, **kwargs)
235
+ return result
236
+
237
+ return action
238
+
239
+ def __getattr__(self, attr):
240
+ return self._get_action(attr)
241
+
242
+ def __dir__(self):
243
+ return self.gateway_structure.keys()
244
+
245
+ def raw(self, action_name, **kwargs):
246
+ return self.call.api(action_name, **kwargs)
247
+
248
+ def __enter__(self):
249
+ return self
250
+
251
+ def __exit__(self, type, value, traceback):
252
+ self.call.close()
253
+
254
+
255
+ class OutscaleGateway(BaseAPI):
256
+ def __init__(self, **kwargs):
257
+ super().__init__(
258
+ os.path.join(os.path.dirname(__file__), "resources/outscale.yaml"), **kwargs
259
+ )
260
+
261
+
262
+ def test():
263
+ a = OutscaleGateway()
264
+ a.CreateVms(
265
+ ImageId="ami-xx",
266
+ BlockDeviceMappings=[{"/dev/sda1": {"Size": 10}}],
267
+ SecurityGroupIds=["sg-aaa", "sg-bbb"],
268
+ )
269
+ try:
270
+ a.CreateVms(
271
+ ImageId="ami-xx",
272
+ BlockDeviceMappings=[{"/dev/sda1": {"Size": 10}}],
273
+ SecurityGroupIds=["sg-aaa", "sg-bbb"],
274
+ Wrong="wrong",
275
+ )
276
+ except ParameterNotValid:
277
+ pass
278
+ else:
279
+ raise AssertionError()
280
+ try:
281
+ a.CreateVms(
282
+ BlockDeviceMappings=[{"/dev/sda1": {"Size": 10}}],
283
+ SecurityGroupIds=["sg-aaa", "sg-bbb"],
284
+ )
285
+ except ParameterIsRequired:
286
+ pass
287
+ else:
288
+ raise AssertionError()
289
+ try:
290
+ a.CreateVms(
291
+ ImageId=["ami-xxx"],
292
+ BlockDeviceMappings=[{"/dev/sda1": {"Size": 10}}],
293
+ SecurityGroupIds=["sg-aaa", "sg-bbb"],
294
+ )
295
+ except ParameterHasWrongType:
296
+ pass
297
+ else:
298
+ raise AssertionError()
299
+ try:
300
+ a.CreateVms(
301
+ ImageId="ami-xxx",
302
+ BlockDeviceMappings=[{"/dev/sda1": {"Size": 10}}],
303
+ SecurityGroupIds="wrong",
304
+ )
305
+ except ParameterHasWrongType:
306
+ pass
307
+ else:
308
+ raise AssertionError()
309
+ try:
310
+ a.CreateVms(
311
+ ImageId=["ami-wrong"],
312
+ BlockDeviceMappings=[{"/dev/sda1": {"Size": 10}}],
313
+ SecurityGroupIds="wrong",
314
+ )
315
+ except ParameterHasWrongType:
316
+ pass
317
+ else:
318
+ raise AssertionError()
319
+
320
+
321
+ if __name__ == "__main__":
322
+ test()
@@ -0,0 +1,100 @@
1
+ import json
2
+
3
+
4
+ class ProblemDecoder(json.JSONDecoder):
5
+ def decode(self, s):
6
+ data = super().decode(s)
7
+ if isinstance(data, dict):
8
+ return self._make_problem(data)
9
+ return data
10
+
11
+ def _make_problem(self, data):
12
+ type_ = data.pop("type", None)
13
+ status = data.pop("status", None)
14
+ title = data.pop("title", None)
15
+ detail = data.pop("detail", None)
16
+ instance = data.pop("instance", None)
17
+ return Problem(type_, status, title, detail, instance, **data)
18
+
19
+
20
+ class Problem(Exception):
21
+ def __init__(self, type_, status, title, detail, instance, **kwargs):
22
+ self._type = type_ or "about:blank"
23
+ self.status = status
24
+ self.title = title
25
+ self.detail = detail
26
+ self.instance = instance
27
+ self.extras = kwargs
28
+
29
+ for k in self.extras:
30
+ if k in ["type", "status", "title", "detail", "instance"]:
31
+ raise ValueError(f"Reserved key '{k}' used in Problem extra arguments.")
32
+
33
+ def __str__(self):
34
+ return self.title
35
+
36
+ def __repr__(self):
37
+ return f"{self.__class__.__name__}<type={self._type}; status={self.status}; title={self.title}>"
38
+
39
+ def msg(self):
40
+ msg = (
41
+ f"type = {self._type}, "
42
+ f"status = {self.status}, "
43
+ f"title = {self.title}, "
44
+ f"detail = {self.detail}, "
45
+ f"instance = {self.instance}, "
46
+ f"extras = {self.extras}"
47
+ )
48
+ return msg
49
+
50
+ @property
51
+ def type(self):
52
+ return self._type
53
+
54
+
55
+ class LegacyProblemDecoder(json.JSONDecoder):
56
+ def decode(self, s):
57
+ data = super().decode(s)
58
+ if isinstance(data, dict):
59
+ return self._make_legacy_problem(data)
60
+ return data
61
+
62
+ def _make_legacy_problem(self, data):
63
+ request_id = None
64
+ error_code = None
65
+ code_type = None
66
+
67
+ if "__type" in data:
68
+ error_code = data.get("__type")
69
+ else:
70
+ request_id = (data.get("ResponseContext") or {}).get("RequestId")
71
+ errors = data.get("Errors")
72
+ if errors:
73
+ error = errors[0]
74
+ error_code = error.get("Code")
75
+ reason = error.get("Type")
76
+ if error.get("Details"):
77
+ code_type = reason
78
+ else:
79
+ code_type = None
80
+ return LegacyProblem(None, error_code, code_type, request_id, None)
81
+
82
+
83
+ class LegacyProblem(Exception):
84
+ def __init__(self, status, error_code, code_type, request_id, url):
85
+ self.status = status
86
+ self.error_code = error_code
87
+ self.code_type = code_type
88
+ self.request_id = request_id
89
+ self.url = url
90
+
91
+ def msg(self):
92
+ msg = (
93
+ f"status = {self.status}, "
94
+ f"code = {self.error_code}, "
95
+ f"{'code_type = ' if self.code_type is not None else ''}"
96
+ f"{self.code_type + ', ' if self.code_type is not None else ''}"
97
+ f"request_id = {self.request_id}, "
98
+ f"url = {self.url}"
99
+ )
100
+ return msg
@@ -0,0 +1,29 @@
1
+ from .retry import Retry
2
+
3
+
4
+ class Requester:
5
+ def __init__(self, session, auth, endpoint, **kwargs):
6
+ self.session = session
7
+ self.auth = auth
8
+ self.endpoint = endpoint
9
+ self.request_kwargs = kwargs
10
+
11
+ def send(self, uri, payload):
12
+ headers = None
13
+ if self.auth.is_basic_auth_configured():
14
+ headers = self.auth.get_basic_auth_header()
15
+ else:
16
+ headers = self.auth.forge_headers_signed(uri, payload)
17
+
18
+ if self.auth.x509_client_cert is not None:
19
+ cert_file = self.auth.x509_client_cert
20
+ else:
21
+ cert_file = None
22
+
23
+ retry_kwargs = self.request_kwargs.copy()
24
+ retry_kwargs.update(
25
+ {"data": payload, "headers": headers, "verify": True, "cert": cert_file}
26
+ )
27
+
28
+ response = Retry(self.session, "post", self.endpoint, **retry_kwargs)
29
+ return response.execute().json()