github.com/phrase/openapi@v0.0.0-20240514140800-49e8a106740e/openapi-generator/templates/python/api_client.mustache (about) 1 # coding: utf-8 2 {{>partial_header}} 3 from __future__ import absolute_import 4 5 import atexit 6 import datetime 7 from dateutil.parser import parse 8 import json 9 import mimetypes 10 from multiprocessing.pool import ThreadPool 11 import os 12 import re 13 import tempfile 14 15 # python 2 and python 3 compatibility library 16 import six 17 from six.moves.urllib.parse import quote 18 {{#tornado}} 19 import tornado.gen 20 {{/tornado}} 21 22 from {{packageName}}.configuration import Configuration 23 import {{modelPackage}} 24 from {{packageName}} import rest 25 from {{packageName}}.exceptions import ApiValueError 26 27 28 class ApiClient(object): 29 """Generic API client for OpenAPI client library builds. 30 31 OpenAPI generic API client. This client handles the client- 32 server communication, and is invariant across implementations. Specifics of 33 the methods and models for each application are generated from the OpenAPI 34 templates. 35 36 NOTE: This class is auto generated by OpenAPI Generator. 37 Ref: https://openapi-generator.tech 38 Do not edit the class manually. 39 40 :param configuration: .Configuration object for this client 41 :param header_name: a header to pass when making calls to the API. 42 :param header_value: a header value to pass when making calls to 43 the API. 44 :param cookie: a cookie to include in the header when making calls 45 to the API 46 :param pool_threads: The number of threads to use for async requests 47 to the API. More threads means more concurrent API requests. 48 """ 49 50 PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types 51 NATIVE_TYPES_MAPPING = { 52 'int': int, 53 'long': int if six.PY3 else long, # noqa: F821 54 'float': float, 55 'str': str, 56 'bool': bool, 57 'date': datetime.date, 58 'datetime': datetime.datetime, 59 'object': object, 60 } 61 _pool = None 62 63 def __init__(self, configuration=None, header_name=None, header_value=None, 64 cookie=None, pool_threads=1): 65 if configuration is None: 66 configuration = Configuration.get_default_copy() 67 self.configuration = configuration 68 self.pool_threads = pool_threads 69 70 self.rest_client = rest.RESTClientObject(configuration) 71 self.default_headers = {} 72 if header_name is not None: 73 self.default_headers[header_name] = header_value 74 self.cookie = cookie 75 # Set default User-Agent. 76 self.user_agent = '{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/{{{packageVersion}}}/python{{/httpUserAgent}}' 77 self.client_side_validation = configuration.client_side_validation 78 79 {{#asyncio}} 80 async def __aenter__(self): 81 return self 82 83 async def __aexit__(self, exc_type, exc_value, traceback): 84 await self.close() 85 {{/asyncio}} 86 {{^asyncio}} 87 def __enter__(self): 88 return self 89 90 def __exit__(self, exc_type, exc_value, traceback): 91 self.close() 92 {{/asyncio}} 93 94 {{#asyncio}}async {{/asyncio}}def close(self): 95 {{#asyncio}} 96 await self.rest_client.close() 97 {{/asyncio}} 98 if self._pool: 99 self._pool.close() 100 self._pool.join() 101 self._pool = None 102 if hasattr(atexit, 'unregister'): 103 atexit.unregister(self.close) 104 105 @property 106 def pool(self): 107 """Create thread pool on first request 108 avoids instantiating unused threadpool for blocking clients. 109 """ 110 if self._pool is None: 111 atexit.register(self.close) 112 self._pool = ThreadPool(self.pool_threads) 113 return self._pool 114 115 @property 116 def user_agent(self): 117 """User agent for this API client""" 118 return self.default_headers['User-Agent'] 119 120 @user_agent.setter 121 def user_agent(self, value): 122 self.default_headers['User-Agent'] = value 123 124 def set_default_header(self, header_name, header_value): 125 self.default_headers[header_name] = header_value 126 127 {{#tornado}} 128 @tornado.gen.coroutine 129 {{/tornado}} 130 {{#asyncio}}async {{/asyncio}}def __call_api( 131 self, resource_path, method, path_params=None, 132 query_params=None, header_params=None, body=None, post_params=None, 133 files=None, response_type=None, auth_settings=None, 134 _return_http_data_only=None, collection_formats=None, 135 _preload_content=True, _request_timeout=None, _host=None): 136 137 config = self.configuration 138 139 # header parameters 140 header_params = header_params or {} 141 header_params.update(self.default_headers) 142 if self.cookie: 143 header_params['Cookie'] = self.cookie 144 if header_params: 145 header_params = self.sanitize_for_serialization(header_params) 146 header_params = dict(self.parameters_to_tuples(header_params, 147 collection_formats)) 148 149 # path parameters 150 if path_params: 151 path_params = self.sanitize_for_serialization(path_params) 152 path_params = self.parameters_to_tuples(path_params, 153 collection_formats) 154 for k, v in path_params: 155 # specified safe chars, encode everything 156 resource_path = resource_path.replace( 157 '{%s}' % k, 158 quote(str(v), safe=config.safe_chars_for_path_param) 159 ) 160 161 # query parameters 162 if query_params: 163 query_params = self.sanitize_for_serialization(query_params) 164 query_params = self.parameters_to_tuples(query_params, collection_formats) 165 query_params = self.flatten_query_params(query_params) 166 167 # post parameters 168 if post_params or files: 169 post_params = post_params if post_params else [] 170 post_params = self.sanitize_for_serialization(post_params) 171 post_params = self.parameters_to_tuples(post_params, 172 collection_formats) 173 post_params.extend(self.files_parameters(files)) 174 175 # auth setting 176 self.update_params_for_auth(header_params, query_params, auth_settings) 177 178 # body 179 if body: 180 body = self.sanitize_for_serialization(body) 181 182 # request url 183 if _host is None: 184 url = self.configuration.host + resource_path 185 else: 186 # use server/host defined in path or operation instead 187 url = _host + resource_path 188 189 # perform request and return response 190 response_data = {{#asyncio}}await {{/asyncio}}{{#tornado}}yield {{/tornado}}self.request( 191 method, url, query_params=query_params, headers=header_params, 192 post_params=post_params, body=body, 193 _preload_content=_preload_content, 194 _request_timeout=_request_timeout) 195 196 self.last_response = response_data 197 198 return_data = response_data 199 if _preload_content: 200 # deserialize response data 201 if response_type: 202 return_data = self.deserialize(response_data, response_type) 203 else: 204 return_data = None 205 206 {{^tornado}} 207 if _return_http_data_only: 208 return (return_data) 209 else: 210 return (return_data, response_data.status, 211 response_data.getheaders()) 212 {{/tornado}} 213 {{#tornado}} 214 if _return_http_data_only: 215 raise tornado.gen.Return(return_data) 216 else: 217 raise tornado.gen.Return((return_data, response_data.status, 218 response_data.getheaders())) 219 {{/tornado}} 220 221 def sanitize_for_serialization(self, obj): 222 """Builds a JSON POST object. 223 224 If obj is None, return None. 225 If obj is str, int, long, float, bool, return directly. 226 If obj is datetime.datetime, datetime.date 227 convert to string in iso8601 format. 228 If obj is list, sanitize each element in the list. 229 If obj is dict, return the dict. 230 If obj is OpenAPI model, return the properties dict. 231 232 :param obj: The data to serialize. 233 :return: The serialized form of data. 234 """ 235 if obj is None: 236 return None 237 elif isinstance(obj, self.PRIMITIVE_TYPES): 238 return obj 239 elif isinstance(obj, list): 240 return [self.sanitize_for_serialization(sub_obj) 241 for sub_obj in obj] 242 elif isinstance(obj, tuple): 243 return tuple(self.sanitize_for_serialization(sub_obj) 244 for sub_obj in obj) 245 elif isinstance(obj, (datetime.datetime, datetime.date)): 246 return obj.isoformat() 247 248 if isinstance(obj, dict): 249 obj_dict = obj 250 else: 251 # Convert model obj to dict except 252 # attributes `openapi_types`, `attribute_map` 253 # and attributes which value is not None. 254 # Convert attribute name to json key in 255 # model definition for request. 256 obj_dict = {obj.attribute_map[attr]: getattr(obj, attr) 257 for attr, _ in six.iteritems(obj.openapi_types) 258 if getattr(obj, attr) is not None} 259 260 return {key: self.sanitize_for_serialization(val) 261 for key, val in six.iteritems(obj_dict)} 262 263 def deserialize(self, response, response_type): 264 """Deserializes response into an object. 265 266 :param response: RESTResponse object to be deserialized. 267 :param response_type: class literal for 268 deserialized object, or string of class name. 269 270 :return: deserialized object. 271 """ 272 # handle file downloading 273 # save response body into a tmp file and return the instance 274 if response_type == "bytearray": 275 return self.__deserialize_file(response) 276 277 # fetch data from response object 278 try: 279 data = json.loads(response.data) 280 except ValueError: 281 data = response.data 282 283 return self.__deserialize(data, response_type) 284 285 def __deserialize(self, data, klass): 286 """Deserializes dict, list, str into an object. 287 288 :param data: dict, list or str. 289 :param klass: class literal, or string of class name. 290 291 :return: object. 292 """ 293 if data is None: 294 return None 295 296 if type(klass) == str: 297 if klass.startswith('List['): 298 sub_kls = re.match(r'List\[(.*)\]', klass).group(1) 299 return [self.__deserialize(sub_data, sub_kls) 300 for sub_data in data] 301 302 if klass.startswith('Dict('): 303 sub_kls = re.match(r'Dict\(([^,]*), (.*)\)', klass).group(2) 304 return {k: self.__deserialize(v, sub_kls) 305 for k, v in six.iteritems(data)} 306 307 # convert str to class 308 if klass in self.NATIVE_TYPES_MAPPING: 309 klass = self.NATIVE_TYPES_MAPPING[klass] 310 else: 311 klass = getattr({{modelPackage}}, klass) 312 313 if klass in self.PRIMITIVE_TYPES: 314 return self.__deserialize_primitive(data, klass) 315 elif klass == object: 316 return self.__deserialize_object(data) 317 elif klass == datetime.date: 318 return self.__deserialize_date(data) 319 elif klass == datetime.datetime: 320 return self.__deserialize_datetime(data) 321 else: 322 return self.__deserialize_model(data, klass) 323 324 def call_api(self, resource_path, method, 325 path_params=None, query_params=None, header_params=None, 326 body=None, post_params=None, files=None, 327 response_type=None, auth_settings=None, async_req=None, 328 _return_http_data_only=None, collection_formats=None, 329 _preload_content=True, _request_timeout=None, _host=None): 330 """Makes the HTTP request (synchronous) and returns deserialized data. 331 332 To make an async_req request, set the async_req parameter. 333 334 :param resource_path: Path to method endpoint. 335 :param method: Method to call. 336 :param path_params: Path parameters in the url. 337 :param query_params: Query parameters in the url. 338 :param header_params: Header parameters to be 339 placed in the request header. 340 :param body: Request body. 341 :param post_params dict: Request post form parameters, 342 for `application/x-www-form-urlencoded`, `multipart/form-data`. 343 :param auth_settings list: Auth Settings names for the request. 344 :param response: Response data type. 345 :param files dict: key -> filename, value -> filepath, 346 for `multipart/form-data`. 347 :param async_req bool: execute request asynchronously 348 :param _return_http_data_only: response data without head status code 349 and headers 350 :param collection_formats: dict of collection formats for path, query, 351 header, and post parameters. 352 :param _preload_content: if False, the urllib3.HTTPResponse object will 353 be returned without reading/decoding response 354 data. Default is True. 355 :param _request_timeout: timeout setting for this request. If one 356 number provided, it will be total request 357 timeout. It can also be a pair (tuple) of 358 (connection, read) timeouts. 359 :return: 360 If async_req parameter is True, 361 the request will be called asynchronously. 362 The method will return the request thread. 363 If parameter async_req is False or missing, 364 then the method will return the response directly. 365 """ 366 if not async_req: 367 return self.__call_api(resource_path, method, 368 path_params, query_params, header_params, 369 body, post_params, files, 370 response_type, auth_settings, 371 _return_http_data_only, collection_formats, 372 _preload_content, _request_timeout, _host) 373 374 return self.pool.apply_async(self.__call_api, (resource_path, 375 method, path_params, 376 query_params, 377 header_params, body, 378 post_params, files, 379 response_type, 380 auth_settings, 381 _return_http_data_only, 382 collection_formats, 383 _preload_content, 384 _request_timeout, 385 _host)) 386 387 def request(self, method, url, query_params=None, headers=None, 388 post_params=None, body=None, _preload_content=True, 389 _request_timeout=None): 390 """Makes the HTTP request using RESTClient.""" 391 if method == "GET": 392 return self.rest_client.GET(url, 393 query_params=query_params, 394 _preload_content=_preload_content, 395 _request_timeout=_request_timeout, 396 headers=headers) 397 elif method == "HEAD": 398 return self.rest_client.HEAD(url, 399 query_params=query_params, 400 _preload_content=_preload_content, 401 _request_timeout=_request_timeout, 402 headers=headers) 403 elif method == "OPTIONS": 404 return self.rest_client.OPTIONS(url, 405 query_params=query_params, 406 headers=headers, 407 _preload_content=_preload_content, 408 _request_timeout=_request_timeout) 409 elif method == "POST": 410 return self.rest_client.POST(url, 411 query_params=query_params, 412 headers=headers, 413 post_params=post_params, 414 _preload_content=_preload_content, 415 _request_timeout=_request_timeout, 416 body=body) 417 elif method == "PUT": 418 return self.rest_client.PUT(url, 419 query_params=query_params, 420 headers=headers, 421 post_params=post_params, 422 _preload_content=_preload_content, 423 _request_timeout=_request_timeout, 424 body=body) 425 elif method == "PATCH": 426 return self.rest_client.PATCH(url, 427 query_params=query_params, 428 headers=headers, 429 post_params=post_params, 430 _preload_content=_preload_content, 431 _request_timeout=_request_timeout, 432 body=body) 433 elif method == "DELETE": 434 return self.rest_client.DELETE(url, 435 query_params=query_params, 436 headers=headers, 437 _preload_content=_preload_content, 438 _request_timeout=_request_timeout, 439 body=body) 440 else: 441 raise ApiValueError( 442 "http method must be `GET`, `HEAD`, `OPTIONS`," 443 " `POST`, `PATCH`, `PUT` or `DELETE`." 444 ) 445 446 def parameters_to_tuples(self, params, collection_formats): 447 """Get parameters as list of tuples, formatting collections. 448 449 :param params: Parameters as dict or list of two-tuples 450 :param dict collection_formats: Parameter collection formats 451 :return: Parameters as list of tuples, collections formatted 452 """ 453 new_params = [] 454 if collection_formats is None: 455 collection_formats = {} 456 for k, v in six.iteritems(params) if isinstance(params, dict) else params: # noqa: E501 457 if k in collection_formats: 458 collection_format = collection_formats[k] 459 if collection_format == 'multi': 460 new_params.extend((k, value) for value in v) 461 else: 462 if collection_format == 'ssv': 463 delimiter = ' ' 464 elif collection_format == 'tsv': 465 delimiter = '\t' 466 elif collection_format == 'pipes': 467 delimiter = '|' 468 else: # csv is the default 469 delimiter = ',' 470 new_params.append( 471 (k, delimiter.join(str(value) for value in v))) 472 else: 473 new_params.append((k, v)) 474 return new_params 475 476 def flatten_query_params(self, params): 477 result_params = {} 478 for key, value in params: 479 if isinstance(value, dict): 480 self.flatten_dict(result_params, value, key) 481 else: 482 result_params[key] = value 483 484 return [(key, value) for (key,value) in result_params.items()] 485 486 def flatten_dict(self, params, param, namespace): 487 for key, value in param.items(): 488 new_key = "{0}[{1}]".format(namespace, key) 489 if isinstance(value, dict): 490 params = self.flatten_dict(params, value, new_key) 491 else: 492 params[new_key] = value 493 494 return params 495 496 497 def files_parameters(self, files=None): 498 """Builds form parameters. 499 500 :param files: File parameters. 501 :return: Form parameters with files. 502 """ 503 params = [] 504 505 if files: 506 for k, v in six.iteritems(files): 507 if not v: 508 continue 509 file_names = v if type(v) is list else [v] 510 for n in file_names: 511 with open(n, 'rb') as f: 512 filename = os.path.basename(f.name) 513 filedata = f.read() 514 mimetype = (mimetypes.guess_type(filename)[0] or 515 'application/octet-stream') 516 params.append( 517 tuple([k, tuple([filename, filedata, mimetype])])) 518 519 return params 520 521 def select_header_accept(self, accepts): 522 """Returns `Accept` based on an array of accepts provided. 523 524 :param accepts: List of headers. 525 :return: Accept (e.g. application/json). 526 """ 527 if not accepts: 528 return 529 530 accepts = [x.lower() for x in accepts] 531 532 if 'application/json' in accepts: 533 return 'application/json' 534 else: 535 return ', '.join(accepts) 536 537 def select_header_content_type(self, content_types): 538 """Returns `Content-Type` based on an array of content_types provided. 539 540 :param content_types: List of content-types. 541 :return: Content-Type (e.g. application/json). 542 """ 543 if not content_types: 544 return 'application/json' 545 546 content_types = [x.lower() for x in content_types] 547 548 if 'application/json' in content_types or '*/*' in content_types: 549 return 'application/json' 550 else: 551 return content_types[0] 552 553 def update_params_for_auth(self, headers, querys, auth_settings): 554 """Updates header and query params based on authentication setting. 555 556 :param headers: Header parameters dict to be updated. 557 :param querys: Query parameters tuple list to be updated. 558 :param auth_settings: Authentication setting identifiers list. 559 """ 560 if not auth_settings: 561 return 562 563 for auth in auth_settings: 564 auth_setting = self.configuration.auth_settings().get(auth) 565 if auth_setting: 566 if auth_setting['in'] == 'cookie': 567 headers['Cookie'] = auth_setting['value'] 568 elif auth_setting['in'] == 'header': 569 headers[auth_setting['key']] = auth_setting['value'] 570 elif auth_setting['in'] == 'query': 571 querys.append((auth_setting['key'], auth_setting['value'])) 572 else: 573 raise ApiValueError( 574 'Authentication token must be in `query` or `header`' 575 ) 576 577 def __deserialize_file(self, response): 578 """Deserializes body to file 579 580 Saves response body into a file in a temporary folder, 581 using the filename from the `Content-Disposition` header if provided. 582 583 :param response: RESTResponse. 584 :return: file path. 585 """ 586 fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path) 587 os.close(fd) 588 os.remove(path) 589 590 content_disposition = response.getheader("Content-Disposition") 591 if content_disposition: 592 filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', 593 content_disposition).group(1) 594 path = os.path.join(os.path.dirname(path), filename) 595 596 # In Python 3 response.data is a string 597 if six.PY3: 598 with open(path, "w", encoding=response.getencoding()) as f: 599 f.write(response.data) 600 else: 601 with open(path, "wb", encoding=response.getencoding()) as f: 602 f.write(response.data) 603 604 return path 605 606 def __deserialize_primitive(self, data, klass): 607 """Deserializes string to primitive type. 608 609 :param data: str. 610 :param klass: class literal. 611 612 :return: int, long, float, str, bool. 613 """ 614 try: 615 return klass(data) 616 except UnicodeEncodeError: 617 return six.text_type(data) 618 except TypeError: 619 return data 620 621 def __deserialize_object(self, value): 622 """Return an original value. 623 624 :return: object. 625 """ 626 return value 627 628 def __deserialize_date(self, string): 629 """Deserializes string to date. 630 631 :param string: str. 632 :return: date. 633 """ 634 try: 635 return parse(string).date() 636 except ImportError: 637 return string 638 except ValueError: 639 raise rest.ApiException( 640 status=0, 641 reason="Failed to parse `{0}` as date object".format(string) 642 ) 643 644 def __deserialize_datetime(self, string): 645 """Deserializes string to datetime. 646 647 The string should be in iso8601 datetime format. 648 649 :param string: str. 650 :return: datetime. 651 """ 652 try: 653 return parse(string) 654 except ImportError: 655 return string 656 except ValueError: 657 raise rest.ApiException( 658 status=0, 659 reason=( 660 "Failed to parse `{0}` as datetime object" 661 .format(string) 662 ) 663 ) 664 665 def __deserialize_model(self, data, klass): 666 """Deserializes list or dict to model. 667 668 :param data: dict, list. 669 :param klass: class literal. 670 :return: model object. 671 """ 672 673 if not klass.openapi_types and not hasattr(klass, 674 'get_real_child_model'): 675 return data 676 677 kwargs = {} 678 if (data is not None and 679 klass.openapi_types is not None and 680 isinstance(data, (list, dict))): 681 for attr, attr_type in six.iteritems(klass.openapi_types): 682 if klass.attribute_map[attr] in data: 683 value = data[klass.attribute_map[attr]] 684 kwargs[attr] = self.__deserialize(value, attr_type) 685 686 instance = klass(**kwargs) 687 688 if hasattr(instance, 'get_real_child_model'): 689 klass_name = instance.get_real_child_model(data) 690 if klass_name: 691 instance = self.__deserialize(data, klass_name) 692 return instance