pumpwood_communication.microservice_abc.base
Module with base classes for requests on Pumpwood Server.
41class PumpWoodMicroServiceBase: 42 """Base class for Pumpwood MicroService. 43 44 Environment variables can be used to set MicroService parameters. 45 Correct spelling is ``PUMPWOOD_COMMUNICATION__*``; legacy typo 46 ``PUMPWOOD_COMUNICATION__*`` is still supported as fallback: 47 - **PUMPWOOD_COMMUNICATION__DEFAULT_TIMEOUT:** Default requests timeout 48 in seconds. 49 - **PUMPWOOD_COMMUNICATION__DEBUG:** If object will be initiated using 50 debug parameter. It will have more verbosity and login at each 51 request. Options 'TRUE', 'FALSE'. 52 - **PUMPWOOD_COMMUNICATION__VERIFY_SSL:** If requests will validate SSL 53 certificate. 54 """ 55 56 __base_header = {'Content-Type': 'application/json'} 57 """Base header for the requests.""" 58 59 @staticmethod 60 def _adjust_server_url(server_url): 61 """Remove tralling / if present on server URL.""" 62 if server_url is None: 63 return None 64 if server_url[-1] != '/': 65 return server_url + '/' 66 else: 67 return server_url 68 69 def __init__(self, name: str = None, server_url: str = None, 70 username: str = None, password: str = None, 71 verify_ssl: bool = True, debug: bool = None, 72 default_timeout: int = None, **kwargs): 73 """Create new PumpWoodMicroService object. 74 75 Creates a new microservice object. If just name is passed, object must 76 be initiate after with init() method. 77 78 Args: 79 name: 80 Name of the microservice, helps when exceptions 81 are raised. 82 server_url: 83 URL of the server that will be connected. 84 username: 85 Username that will be logged on. 86 password: 87 Variable to be converted to JSON and posted along 88 with the request. 89 verify_ssl: 90 Set if microservice will verify SSL certificate. 91 debug: 92 If microservice will be used as debug mode. This will obrigate 93 auth token refresh for each call. 94 default_timeout: 95 Default timeout for Pumpwood calls. 96 **kwargs: 97 Other parameters used for compatibility between versions. 98 99 Returns: 100 PumpWoodMicroService: New PumpWoodMicroService object 101 102 Raises: 103 No particular Raises. 104 """ 105 # Create attributes to be set at init function 106 self.name = None 107 """Name of the microservice instance.""" 108 self.server_url = None 109 """Pumpwood server URL.""" 110 self._default_timeout: int = None 111 """Default timeout for Pumpwood requests.""" 112 self._debug: bool = None 113 """Name of the microservice instance.""" 114 self._verify_ssl: bool = None 115 """If microservice should check the certificate.""" 116 self._is_mfa_login: bool = None 117 """Set if is MFA login.""" 118 self.__headers: dict = None 119 """Headers to be used on the requests.""" 120 self.__user: dict = None 121 """Information of the logged user.""" 122 self.__auth_header: dict = None 123 """Authenticated auth header.""" 124 self.__token_expiry: pd.Timedelta = None 125 """Expirity datetime of the authetication token.""" 126 self.__username: str = None 127 """Username associated with microservice.""" 128 self.__password: str = None 129 """Password associated with microservice.""" 130 self.init( 131 name=name, server_url=server_url, 132 username=username, password=password, 133 verify_ssl=verify_ssl, debug=debug, 134 default_timeout=default_timeout) 135 136 def init(self, name: str = None, server_url: str = None, 137 username: str = None, password: str = None, 138 verify_ssl: bool = True, debug: bool = None, 139 default_timeout: int = None, **kwargs): 140 """Lazzy initialization of the MicroService of object. 141 142 This function might be usefull to use the object as a singleton at 143 the backends. Using this function it is possible to instanciate an 144 empty object and them set the attributes latter at the systems. 145 146 Args: 147 name: 148 Name of the microservice, helps when exceptions 149 are raised. 150 server_url: 151 URL of the server that will be connected. 152 username: 153 Username that will be logged on. 154 password: 155 Variable to be converted to JSON and posted along 156 with the request. 157 verify_ssl: 158 Set if microservice will verify SSL certificate. 159 debug: 160 If microservice will be used as debug mode. This will obrigate 161 auth token refresh for each call. 162 default_timeout: 163 Default timeout for Pumpwood calls. 164 **kwargs: 165 Other parameters used for compatibility between versions. 166 167 Returns: 168 No return 169 170 Raises: 171 No particular Raises 172 """ 173 self.name = name 174 """Name of the microservice instance.""" 175 self.server_url = self._adjust_server_url(server_url) 176 """Pumpwood server URL.""" 177 178 # Set parameter using arguments or environment variables 179 if default_timeout is None: 180 self._default_timeout = DEFAULT_TIMEOUT 181 else: 182 self._default_timeout = default_timeout 183 184 if debug is None: 185 self._debug = DEBUG 186 else: 187 self._debug = debug 188 189 if verify_ssl is None: 190 self._verify_ssl = VERIFY_SSL 191 else: 192 self._verify_ssl = verify_ssl 193 194 self._is_mfa_login = False 195 self.__headers = None 196 self.__user = None 197 self.__auth_header = None 198 self.__token_expiry = None 199 self.__username = username 200 self.__password = password 201 202 @staticmethod 203 def angular_json(request_result) -> Any: 204 r"""Convert text to Json removing any XSSI at the beging of JSON. 205 206 Some backends add `)]}',\n` at the beginning of the JSON data to 207 prevent injection of functions. This function remove this characters 208 if present. 209 210 Args: 211 request_result: 212 JSON request to be converted. 213 214 Returns: 215 No return 216 217 Raises: 218 PumpWoodJSONLoadError: 219 If it is not possible to load JSON from request data. 220 """ 221 if request_result.text == '': 222 return None 223 224 string_start = ")]}',\n" 225 try: 226 if request_result.text[:6] == string_start: 227 return (orjson.loads(request_result.text[6:])) 228 else: 229 return (orjson.loads(request_result.text)) 230 except Exception: 231 msg = "Can not decode to Json" 232 raise PumpWoodJSONLoadError( 233 message=msg, payload={"request_data": request_result.text}) 234 235 def time_to_expiry(self) -> pd.Timedelta: 236 """Return time to token expiry. 237 238 Args: 239 No Args. 240 241 Returns: 242 Return time until token expiration. 243 """ 244 if self.__token_expiry is None: 245 return None 246 247 now_datetime = pd.to_datetime( 248 datetime.datetime.now(datetime.UTC), utc=True) 249 time_to_expiry = self.__token_expiry - now_datetime 250 return time_to_expiry 251 252 def is_credential_set(self) -> bool: 253 """Check if username and password are set on object. 254 255 Args: 256 No Args. 257 258 Returns: 259 True if usename and password were set during object creation or 260 later with init function. 261 """ 262 is_username_not_none = self.__username is not None 263 is_password_not_none = self.__password is not None 264 return is_username_not_none and is_password_not_none 265 266 @classmethod 267 def is_invalid_token_response(cls, response: Response) -> bool: 268 """Check if reponse has invalid token error. 269 270 Args: 271 response: 272 Request reponse to check for invalid token. 273 274 Returns: 275 Return True if response has an invalid token status. 276 """ 277 if response.status_code == 401: 278 return True 279 return False 280 281 def _login_resquest(self) -> Response: 282 """Request login with object credentials. 283 284 Args: 285 No Args 286 287 Returns (Response): 288 Request object. 289 """ 290 login_url = urljoin( 291 self.server_url, 'rest/registration/login/') 292 293 # Make a retry loop for authentication 294 login_result = None 295 for i in range(5): 296 login_result = requests.post( 297 login_url, json={ 298 'username': self.__username, 299 'password': self.__password}, 300 verify=self._verify_ssl, timeout=self._default_timeout) 301 if login_result.ok: 302 try: 303 login_data = self.angular_json(login_result) 304 return login_data 305 except Exception: # NOQA 306 pass 307 308 # Handle Unauthorized responses 309 elif self.is_invalid_token_response(login_result): 310 error_data = self.angular_json(login_result) 311 raise PumpWoodUnauthorized( 312 message="Login is not possible", 313 payload=error_data) 314 315 # Handle Forbidden responses 316 elif login_result.status_code == 403: 317 error_data = self.angular_json(login_result) 318 raise PumpWoodForbidden( 319 message="Login resquest is forbidden", 320 payload=error_data) 321 time.sleep(0.2) 322 323 # If response is not returned then something is not ok 324 error_data = self.angular_json(login_result) 325 raise PumpWoodOtherException( 326 message="Login not possible, server is not responding correctly", 327 payload=error_data) 328 329 def confirm_mfa_code(self, mfa_login_data: dict) -> dict: 330 """Ask user to confirm MFA code to login. 331 332 Open an input interface at terminal for user to validate MFA token. 333 334 Args: 335 mfa_login_data: 336 Result from login request with 'mfa_token' 337 as key. 338 339 Returns: 340 Return login returned with MFA confimation. 341 342 Raise: 343 Raise error if reponse is not valid using error_handler. 344 """ 345 code = input("## Please enter MFA code: ") 346 url = urljoin( 347 self.server_url, 'rest/registration/mfa-validate-code/') 348 mfa_response = requests.post(url, headers={ 349 "X-PUMPWOOD-MFA-Autorization": mfa_login_data['mfa_token']}, 350 json={"mfa_code": code}, timeout=self._default_timeout) 351 self.error_handler(mfa_response) 352 353 # Set _is_mfa_login true to indicate that login required MFA 354 self._is_mfa_login = True 355 return self.angular_json(mfa_response) 356 357 def login(self, force_refresh: bool = False) -> None: 358 """Log microservice in using username and password provided. 359 360 Args: 361 force_refresh (bool): 362 Force token refresh despise still valid 363 according to self.__token_expiry. 364 365 Returns: 366 No return 367 368 Raises: 369 Exception: 370 If login response has status diferent from 200. 371 """ 372 if not self.is_credential_set(): 373 raise PumpWoodUnauthorized( 374 message="Microservice username or/and password not set") 375 376 # Check if expiry time is 1h from now 377 refresh_expiry = False 378 if self.__token_expiry is None: 379 refresh_expiry = True 380 else: 381 time_to_expiry = self.time_to_expiry() 382 if time_to_expiry < datetime.timedelta(hours=1): 383 refresh_expiry = True 384 385 # When if debug always refresh token 386 if refresh_expiry or force_refresh or self._debug: 387 login_data = self._login_resquest() 388 if 'mfa_token' in login_data.keys(): 389 login_data = self.confirm_mfa_code( 390 mfa_login_data=login_data) 391 392 self.set_auth_header( 393 auth_token='Token ' + login_data['token'], 394 token_expiry=pd.to_datetime(login_data['expiry']), 395 user=login_data["user"]) 396 else: 397 # Token is not expired or envicted, them keep same token 398 return None 399 400 def _evict_auth_state(self) -> None: 401 """Clear local authentication state after logout. 402 403 Resets token expiry, auth header, and cached user information so 404 ``is_superuser`` and ``login`` behave correctly after logout. 405 """ 406 self.__token_expiry = None 407 self.__auth_header = None 408 self.__user = None 409 410 def logout(self, auth_header: dict = None) -> bool: 411 """Logout token. 412 413 Args: 414 auth_header: 415 Authentication header. 416 417 Returns: 418 True if logout was ok. 419 """ 420 resp = self.request_post( 421 url='rest/registration/logout/', 422 data={}, auth_header=auth_header) 423 self._evict_auth_state() 424 return resp is None 425 426 def logout_all(self, auth_header: dict = None) -> bool: 427 """Logout all tokens from user. 428 429 Args: 430 auth_header (dict): 431 Authentication header. 432 433 Returns: 434 True if logout all was ok. 435 """ 436 resp = self.request_post( 437 url='rest/registration/logoutall/', 438 data={}, auth_header=auth_header) 439 self._evict_auth_state() 440 return resp is None 441 442 def get_auth_header(self) -> dict: 443 """Retrieve auth_header and token_expiry from object. 444 445 Args: 446 No Args. 447 448 Returns: 449 Return authorization header and token_expiry datetime from object. 450 """ 451 # Copy the dictonary to avoid updating the original one 452 return copy.deepcopy({ 453 "auth_header": self.__auth_header, 454 "token_expiry": self.__token_expiry}) 455 456 def set_auth_header(self, auth_token: str, 457 token_expiry: pd.Timestamp, 458 user: dict = None) -> dict: 459 """Retrieve auth_header and token_expiry from object. 460 461 Args: 462 auth_token (str): 463 Auth token that will be set for authentication. 464 token_expiry (pd.Timestamp): 465 Token expiry time. 466 user (dict): 467 User information to be set on authetication. 468 469 Returns: 470 Return authorization header and token_expiry datetime from object. 471 """ 472 # Copy the dictonary to avoid updating the original one 473 self.__auth_header = { 474 'Authorization': auth_token} 475 self.__token_expiry = token_expiry 476 self.__user = user 477 return True 478 479 def is_superuser(self) -> bool: 480 """Check if is superuser. 481 482 Args: 483 No Args. 484 485 Returns: 486 Return True if is superuser. 487 """ 488 user = self.__user 489 if user is None: 490 return False 491 return user.get("is_superuser", False) 492 493 def _resolve_base_filter_skip(self, base_filter_skip): 494 """Resolve base filter skip. 495 496 Args: 497 base_filter_skip: 498 Base filter skip to be resolved. 499 500 Returns: 501 Return base filter skip resolved. 502 """ 503 if base_filter_skip is None: 504 if self.is_superuser(): 505 base_filter_skip = ['ALL'] 506 else: 507 base_filter_skip = [] 508 return base_filter_skip 509 510 def _check_auth_header(self, auth_header: dict, 511 multipart: bool = False) -> dict: 512 """Check if auth_header is set or auth_header if provided. 513 514 Args: 515 auth_header (dict): 516 AuthHeader to substitute the microservice original 517 at the request (user impersonation). 518 multipart (dict): 519 Set if call should be made as a multipart instead of JSON. 520 521 Returns (dict): 522 Return a header dict to be used in requests. 523 524 Raises: 525 PumpWoodUnauthorized: 526 If microservice is not logged and a auth_header method 527 argument is not provided. 528 PumpWoodUnauthorized: 529 If microservice is logged and a auth_header method argument 530 is provided. 531 """ 532 if auth_header is None: 533 # Login will refresh token if it is 1h to expire, it will also 534 # check if credentials are set. 535 self.login() 536 auth_header_data = self.get_auth_header() 537 auth_header = auth_header_data['auth_header'] 538 if multipart: 539 return auth_header 540 else: 541 return self.__base_header | auth_header 542 else: 543 if self.is_credential_set(): 544 msg = ( 545 'Microservice [{object_name}] with credentials and ' 546 'auth_header was provided') 547 raise PumpWoodUnauthorized( 548 message=msg, payload={'object_name': self.name}) 549 550 # Set base header as JSON since unserialization is done using 551 # Pumpwood Communication serialization function 552 temp__auth_header = auth_header.copy() 553 if multipart: 554 return temp__auth_header 555 else: 556 return self.__base_header | temp__auth_header 557 558 @classmethod 559 def error_handler(cls, response): 560 """Handle request error. 561 562 Check if is a Json and propagate the error with 563 same type if possible. If not Json raises the content. 564 565 Args: 566 response: 567 response to be handled, it is a PumpWoodException 568 return it will raise the same exception at microservice 569 object. 570 571 Returns: 572 No return. 573 574 Raises: 575 PumpWoodOtherException: 576 If content-type is not application/json. 577 PumpWoodOtherException: 578 If content-type is application/json, but type not 579 present or not recognisable at `exceptions.exceptions_dict`. 580 Other PumpWoodException sub-types: 581 If content-type is application/json if type is present and 582 recognisable. 583 584 Example: 585 No example 586 """ 587 if not response.ok: 588 utcnow = datetime.datetime.now(datetime.UTC) 589 response_content_type = response.headers['content-type'] 590 591 # Request information 592 url = response.url 593 method = response.request.method 594 if 'application/json' not in response_content_type.lower(): 595 # Raise the exception as first in exception deep. 596 exception_dict = [{ 597 "exception_url": url, 598 "exception_method": method, 599 "exception_utcnow": utcnow.isoformat(), 600 "exception_deep": 1}] 601 raise PumpWoodOtherException( 602 message=response.text, payload={ 603 "!exception_stack!": exception_dict}) 604 605 # Build error stack 606 response_dict = cls.angular_json(response) 607 608 # Removing previous error stack 609 payload = copy.deepcopy( 610 response_dict.get("payload", {})) 611 exception_stack = copy.deepcopy( 612 payload.pop("!exception_stack!", [])) 613 614 exception_deep = len(exception_stack) 615 exception_dict = { 616 "exception_url": url, 617 "exception_method": method, 618 "exception_utcnow": utcnow.isoformat(), 619 "exception_deep": exception_deep + 1 620 } 621 exception_stack.insert(0, exception_dict) 622 payload["!exception_stack!"] = exception_stack 623 624 ################### 625 # Propagate error # 626 # get exception using 'type' key at response data and get the 627 # exception from exceptions_dict at exceptions 628 exception_message = response_dict.get( 629 "message_not_fmt", response_dict.get("message", "")) 630 exception_type = response_dict.get("type") 631 TempPumpwoodException = exceptions_dict\ 632 .get(exception_type, None) 633 if TempPumpwoodException is not None: 634 raise TempPumpwoodException( 635 message=exception_message, 636 status_code=response_dict.get( 637 "status_code", response.status_code), 638 payload=payload, 639 was_translated=response_dict.get("was_translated", False), 640 parallel=response_dict.get("parallel", False)) 641 else: 642 # If token is invalid is at response, return a 643 # PumpWoodUnauthorized error 644 is_invalid_token = cls.is_invalid_token_response(response) 645 response_dict["!exception_stack!"] = exception_stack 646 if is_invalid_token: 647 raise PumpWoodUnauthorized( 648 message="Invalid token", payload=payload) 649 else: 650 # If the error is not mapped return a 651 # PumpWoodOtherException limiting the message size to 1k 652 # characters 653 raise PumpWoodOtherException( 654 message="Not mapped exception JSON", 655 payload=response_dict) 656 657 def _request_post_json(self, post_url: str, data: any, 658 auth_header: dict = None, 659 parameters: None | dict = None) -> any: 660 """Make a POST a request to url with data as JSON payload. 661 662 Args: 663 post_url: 664 URL to make the request, already with server url. 665 data: 666 Data to be used as Json payload. 667 parameters: 668 URL parameters. 669 auth_header: 670 AuthHeader to substitute the microservice original 671 at the request (user impersonation). 672 673 Returns: 674 Return the post response data. 675 676 Raises: 677 PumpWoodException sub-types: 678 Response is passed to error_handler. 679 """ 680 parameters = {} if parameters is None else parameters 681 682 response = None 683 request_header = self._check_auth_header(auth_header=auth_header) 684 dumped_data = pumpJsonDump(data) 685 response = requests.post( 686 url=post_url, data=dumped_data, 687 params=parameters, verify=self._verify_ssl, 688 headers=request_header, timeout=self._default_timeout) 689 690 # Retry request if token is not valid forcing token renew 691 retry_with_login = ( 692 self.is_invalid_token_response(response) and 693 auth_header is None) 694 if not retry_with_login: 695 return response 696 else: 697 # Force token refresh if Unauthorized 698 time.sleep(0.5) 699 self.login(force_refresh=True) 700 request_header = self._check_auth_header(auth_header=auth_header) 701 return requests.post( 702 url=post_url, data=dumped_data, 703 params=parameters, verify=self._verify_ssl, 704 headers=request_header, timeout=self._default_timeout) 705 706 def _request_post_multi(self, post_url: str, data: any, files: list = None, 707 auth_header: dict = None, 708 parameters: None | dict = None) -> any: 709 """Make a POST a request to url with data as multipart payload. 710 711 Args: 712 post_url: 713 URL to make the request, already with server url. 714 data: 715 Data to be used as Json payload. 716 files: 717 A dictonary with file data, files will be set on field 718 corresponding.to dictonary key. 719 `{'file1': open('file1', 'rb'), {'file2': open('file2', 'rb')}` 720 parameters: 721 URL parameters. 722 auth_header: 723 AuthHeader to substitute the microservice original 724 at the request (user impersonation). 725 726 Returns: 727 Return the post response data. 728 729 Raises: 730 PumpWoodException sub-types: 731 Response is passed to error_handler. 732 """ 733 parameters = {} if parameters is None else parameters 734 735 # Request with files are done using multipart serializing all fields 736 # as JSON 737 request_header = self._check_auth_header( 738 auth_header=auth_header, multipart=True) 739 temp_data = {'__json__': pumpJsonDump(data)} 740 741 response = requests.post( 742 url=post_url, data=temp_data, files=files, params=parameters, 743 verify=self._verify_ssl, headers=request_header, 744 timeout=self._default_timeout) 745 retry_with_login = ( 746 self.is_invalid_token_response(response) and 747 auth_header is None) 748 if not retry_with_login: 749 return response 750 else: 751 # Force token refresh if Unauthorized 752 time.sleep(0.5) 753 self.login(force_refresh=True) 754 request_header = self._check_auth_header( 755 auth_header=auth_header, multipart=True) 756 return requests.post( 757 url=post_url, data=temp_data, files=files, 758 params=parameters, verify=self._verify_ssl, 759 headers=request_header, timeout=self._default_timeout) 760 761 @classmethod 762 def _treat_response_for_file(cls, response: Response) -> dict: 763 """Return if response contain a file. 764 765 Args: 766 response (Response): 767 Response to be checked for a file content. 768 769 Returns (bool): 770 Returns if reponse has a file. 771 """ 772 headers = response.headers 773 content_disposition = headers.get('content-disposition') 774 if content_disposition is None: 775 return cls.angular_json(response) 776 else: 777 fname = re.findall("filename=(.+)", content_disposition)[0] 778 return { 779 "__file__": True, 780 "content": response.content, 781 "content-type": response.headers['content-type'], 782 "filename": fname} 783 784 @classmethod 785 def _dump_query_parameters(cls, parameters: dict) -> dict: 786 """Dump query parameters to javascript compatibility. 787 788 Args: 789 parameters (dict): 790 Parameters to be parsed to JSON. 791 792 Returns: 793 pass 794 """ 795 # If parameters are not none convert them to json before 796 # sending information on query string, 'True' is 'true' on javascript 797 # for example 798 if parameters is not None: 799 temp_parameters = copy.deepcopy(parameters) 800 for key in temp_parameters.keys(): 801 # Do not convert str to json, it put extra "" araound string 802 if type(temp_parameters[key]) is not str: 803 temp_parameters[key] = pumpJsonDump(parameters[key]) 804 return temp_parameters 805 else: 806 return None 807 808 def request_post(self, url: str, data: any, files: list = None, 809 auth_header: dict = None, 810 parameters: None | dict = {}) -> any: 811 """Make a POST a request to url with data as multipart/json payload. 812 813 Args: 814 url: 815 URL to make the request, already with server url. 816 data: 817 Data to be used as Json payload. 818 files: 819 A dictonary with file data, files will be set on field 820 corresponding.to dictonary key. 821 `{'file1': open('file1', 'rb'), {'file2': open('file2', 'rb')}` 822 parameters: 823 URL parameters. 824 auth_header: 825 AuthHeader to substitute the microservice original 826 at the request (user impersonation). 827 828 Returns: 829 Return the post response data. 830 831 Raises: 832 PumpWoodException sub-types: 833 Response is passed to error_handler. 834 """ 835 parameters = {} if parameters is None else parameters 836 837 post_url = urljoin(self.server_url, url) 838 dumped_parameters = self._dump_query_parameters(parameters=parameters) 839 response = None 840 if files is None: 841 response = self._request_post_json( 842 post_url=post_url, data=data, auth_header=auth_header, 843 parameters=dumped_parameters) 844 else: 845 response = self._request_post_multi( 846 post_url=post_url, data=data, files=files, 847 auth_header=auth_header, parameters=dumped_parameters) 848 849 # Handle errors and re-raise if Pumpwood Exceptions 850 self.error_handler(response) 851 return self._treat_response_for_file(response=response) 852 853 def request_get(self, url: str, parameters: None | dict = None, 854 auth_header: dict = None, 855 use_disk_cache: bool = False, 856 disk_cache_expire: int = None, 857 disk_cache_tag_dict: dict = None) -> Any: 858 """Make a GET a request to url with data as JSON payload. 859 860 Add the auth_header acording to login information and refresh token 861 if auth_header=None and object token is expired. 862 863 Args: 864 url (str): 865 URL to make the request. 866 parameters (dict): 867 URL parameters to make the request. 868 auth_header (dict): 869 Auth header to substitute the microservice original 870 at the request (user impersonation). 871 use_disk_cache (bool): 872 If set true, get request will use local cache to reduce 873 the requests to the backend. 874 disk_cache_expire (int): 875 Time in seconds to expire the cache, it None it will 876 use de default set be PumpwoodCache. 877 disk_cache_tag_dict (dict): 878 Dictionary to be used as a tag on get request. 879 880 Returns: 881 Return the post reponse data. 882 883 Raises: 884 PumpWoodException sub-types: 885 Raise exception if reponse is not 2XX and if 'type' key on 886 JSON payload if found at exceptions_dict. Use the same 887 exception, message and payload. 888 PumpWoodOtherException: 889 If exception type is not found or return is not a json. 890 """ 891 parameters = {} if parameters is None else parameters 892 893 request_header = self._check_auth_header(auth_header) 894 # If is set to use diskcache, it will create a hash cash using 895 # the query paramerers, url and user access token. The 896 # hash will be used as index, not exposing the token at cache 897 # database 898 hash_dict = None 899 if use_disk_cache: 900 hash_dict = RequestGetCacheHash( 901 context='pumpwood_communication-request_get', 902 authorization=request_header['Authorization'], 903 parameters=parameters, 904 url=url) 905 906 cache_results = default_cache.get(hash_dict=hash_dict) 907 if cache_results is not None: 908 msg = "get from cache url[{url}]".format(url=url) 909 logger.info(msg) 910 return cache_results 911 912 dumped_parameters = self._dump_query_parameters(parameters=parameters) 913 get_url = urljoin(self.server_url, url) 914 response = requests.get( 915 get_url, verify=self._verify_ssl, headers=request_header, 916 params=dumped_parameters, timeout=self._default_timeout) 917 918 # If token is expired, refresh it 919 retry_with_login = ( 920 self.is_invalid_token_response(response) and 921 auth_header is None) 922 if retry_with_login: 923 time.sleep(0.5) 924 self.login(force_refresh=True) 925 request_header = self._check_auth_header(auth_header=auth_header) 926 response = requests.get( 927 get_url, verify=self._verify_ssl, headers=request_header, 928 params=dumped_parameters, timeout=self._default_timeout) 929 930 # Re-raise Pumpwood exceptions 931 self.error_handler(response=response) 932 results = self._treat_response_for_file(response=response) 933 934 # If is set to use cache for this calls, set the local cache 935 if use_disk_cache and not results.get('__file__', False): 936 default_cache.set( 937 hash_dict=hash_dict, value=results, 938 expire=disk_cache_expire, 939 tag_dict=disk_cache_tag_dict) 940 return results 941 942 def request_delete(self, url, parameters: dict = None, 943 auth_header: dict = None): 944 """Make a DELETE a request to url with data as Json payload. 945 946 Args: 947 url: 948 Url to make the request. 949 parameters: 950 Dictionary with Urls parameters. 951 auth_header: 952 Auth header to substitute the microservice original 953 at the request (user impersonation). 954 955 Returns: 956 Return the delete reponse payload. 957 958 Raises: 959 PumpWoodException sub-types: 960 Raise exception if reponse is not 2XX and if 'type' key on 961 JSON payload if found at exceptions_dict. Use the same 962 exception, message and payload. 963 PumpWoodOtherException: 964 If exception type is not found or return is not a json. 965 """ 966 request_header = self._check_auth_header(auth_header) 967 dumped_parameters = self._dump_query_parameters(parameters=parameters) 968 969 post_url = self.server_url + url 970 response = requests.delete( 971 post_url, verify=self._verify_ssl, headers=request_header, 972 params=dumped_parameters, timeout=self._default_timeout) 973 974 # Retry request if token is not valid forcing token renew 975 retry_with_login = ( 976 self.is_invalid_token_response(response) and 977 auth_header is None) 978 if retry_with_login: 979 time.sleep(0.5) 980 self.login(force_refresh=True) 981 request_header = self._check_auth_header(auth_header=auth_header) 982 response = requests.delete( 983 post_url, verify=self._verify_ssl, headers=request_header, 984 params=dumped_parameters, timeout=self._default_timeout) 985 986 # Re-raise Pumpwood Exceptions 987 self.error_handler(response) 988 return self.angular_json(response) 989 990 def get_user_info(self, auth_header: dict = None, 991 use_disk_cache: bool = False, 992 disk_cache_expire: int = None) -> dict: 993 """Get user info. 994 995 Args: 996 auth_header (dict): = None 997 AuthHeader to substitute the microservice original at 998 request. If not passed, microservice object auth_header 999 will be used. 1000 use_disk_cache (bool): 1001 It possible use disk cache. 1002 disk_cache_expire (int): 1003 Set a time to expire the cache. If not passed env variable 1004 ``PUMPWOOD_COMMUNICATION__AUTHORIZATION_CACHE_TIMEOUT`` 1005 env variable (legacy spelling supported), default 60 s. 1006 1007 Returns: 1008 A serialized user object with information of the logged user. 1009 """ 1010 url = "rest/registration/retrieveauthenticateduser/" 1011 temp_disk_cache_expire = ( 1012 disk_cache_expire 1013 if disk_cache_expire is not None else 1014 AUTHORIZATION_CACHE_TIMEOUT) 1015 1016 user_info = self.request_get( 1017 url=url, auth_header=auth_header, use_disk_cache=use_disk_cache, 1018 disk_cache_expire=temp_disk_cache_expire) 1019 return user_info
Base class for Pumpwood MicroService.
Environment variables can be used to set MicroService parameters.
Correct spelling is PUMPWOOD_COMMUNICATION__*; legacy typo
PUMPWOOD_COMUNICATION__* is still supported as fallback:
- PUMPWOOD_COMMUNICATION__DEFAULT_TIMEOUT: Default requests timeout in seconds.
- PUMPWOOD_COMMUNICATION__DEBUG: If object will be initiated using debug parameter. It will have more verbosity and login at each request. Options 'TRUE', 'FALSE'.
- PUMPWOOD_COMMUNICATION__VERIFY_SSL: If requests will validate SSL certificate.
69 def __init__(self, name: str = None, server_url: str = None, 70 username: str = None, password: str = None, 71 verify_ssl: bool = True, debug: bool = None, 72 default_timeout: int = None, **kwargs): 73 """Create new PumpWoodMicroService object. 74 75 Creates a new microservice object. If just name is passed, object must 76 be initiate after with init() method. 77 78 Args: 79 name: 80 Name of the microservice, helps when exceptions 81 are raised. 82 server_url: 83 URL of the server that will be connected. 84 username: 85 Username that will be logged on. 86 password: 87 Variable to be converted to JSON and posted along 88 with the request. 89 verify_ssl: 90 Set if microservice will verify SSL certificate. 91 debug: 92 If microservice will be used as debug mode. This will obrigate 93 auth token refresh for each call. 94 default_timeout: 95 Default timeout for Pumpwood calls. 96 **kwargs: 97 Other parameters used for compatibility between versions. 98 99 Returns: 100 PumpWoodMicroService: New PumpWoodMicroService object 101 102 Raises: 103 No particular Raises. 104 """ 105 # Create attributes to be set at init function 106 self.name = None 107 """Name of the microservice instance.""" 108 self.server_url = None 109 """Pumpwood server URL.""" 110 self._default_timeout: int = None 111 """Default timeout for Pumpwood requests.""" 112 self._debug: bool = None 113 """Name of the microservice instance.""" 114 self._verify_ssl: bool = None 115 """If microservice should check the certificate.""" 116 self._is_mfa_login: bool = None 117 """Set if is MFA login.""" 118 self.__headers: dict = None 119 """Headers to be used on the requests.""" 120 self.__user: dict = None 121 """Information of the logged user.""" 122 self.__auth_header: dict = None 123 """Authenticated auth header.""" 124 self.__token_expiry: pd.Timedelta = None 125 """Expirity datetime of the authetication token.""" 126 self.__username: str = None 127 """Username associated with microservice.""" 128 self.__password: str = None 129 """Password associated with microservice.""" 130 self.init( 131 name=name, server_url=server_url, 132 username=username, password=password, 133 verify_ssl=verify_ssl, debug=debug, 134 default_timeout=default_timeout)
Create new PumpWoodMicroService object.
Creates a new microservice object. If just name is passed, object must be initiate after with init() method.
Arguments:
- name: Name of the microservice, helps when exceptions are raised.
- server_url: URL of the server that will be connected.
- username: Username that will be logged on.
- password: Variable to be converted to JSON and posted along with the request.
- verify_ssl: Set if microservice will verify SSL certificate.
- debug: If microservice will be used as debug mode. This will obrigate auth token refresh for each call.
- default_timeout: Default timeout for Pumpwood calls.
- **kwargs: Other parameters used for compatibility between versions.
Returns:
PumpWoodMicroService: New PumpWoodMicroService object
Raises:
- No particular Raises.
136 def init(self, name: str = None, server_url: str = None, 137 username: str = None, password: str = None, 138 verify_ssl: bool = True, debug: bool = None, 139 default_timeout: int = None, **kwargs): 140 """Lazzy initialization of the MicroService of object. 141 142 This function might be usefull to use the object as a singleton at 143 the backends. Using this function it is possible to instanciate an 144 empty object and them set the attributes latter at the systems. 145 146 Args: 147 name: 148 Name of the microservice, helps when exceptions 149 are raised. 150 server_url: 151 URL of the server that will be connected. 152 username: 153 Username that will be logged on. 154 password: 155 Variable to be converted to JSON and posted along 156 with the request. 157 verify_ssl: 158 Set if microservice will verify SSL certificate. 159 debug: 160 If microservice will be used as debug mode. This will obrigate 161 auth token refresh for each call. 162 default_timeout: 163 Default timeout for Pumpwood calls. 164 **kwargs: 165 Other parameters used for compatibility between versions. 166 167 Returns: 168 No return 169 170 Raises: 171 No particular Raises 172 """ 173 self.name = name 174 """Name of the microservice instance.""" 175 self.server_url = self._adjust_server_url(server_url) 176 """Pumpwood server URL.""" 177 178 # Set parameter using arguments or environment variables 179 if default_timeout is None: 180 self._default_timeout = DEFAULT_TIMEOUT 181 else: 182 self._default_timeout = default_timeout 183 184 if debug is None: 185 self._debug = DEBUG 186 else: 187 self._debug = debug 188 189 if verify_ssl is None: 190 self._verify_ssl = VERIFY_SSL 191 else: 192 self._verify_ssl = verify_ssl 193 194 self._is_mfa_login = False 195 self.__headers = None 196 self.__user = None 197 self.__auth_header = None 198 self.__token_expiry = None 199 self.__username = username 200 self.__password = password
Lazzy initialization of the MicroService of object.
This function might be usefull to use the object as a singleton at the backends. Using this function it is possible to instanciate an empty object and them set the attributes latter at the systems.
Arguments:
- name: Name of the microservice, helps when exceptions are raised.
- server_url: URL of the server that will be connected.
- username: Username that will be logged on.
- password: Variable to be converted to JSON and posted along with the request.
- verify_ssl: Set if microservice will verify SSL certificate.
- debug: If microservice will be used as debug mode. This will obrigate auth token refresh for each call.
- default_timeout: Default timeout for Pumpwood calls.
- **kwargs: Other parameters used for compatibility between versions.
Returns:
No return
Raises:
- No particular Raises
202 @staticmethod 203 def angular_json(request_result) -> Any: 204 r"""Convert text to Json removing any XSSI at the beging of JSON. 205 206 Some backends add `)]}',\n` at the beginning of the JSON data to 207 prevent injection of functions. This function remove this characters 208 if present. 209 210 Args: 211 request_result: 212 JSON request to be converted. 213 214 Returns: 215 No return 216 217 Raises: 218 PumpWoodJSONLoadError: 219 If it is not possible to load JSON from request data. 220 """ 221 if request_result.text == '': 222 return None 223 224 string_start = ")]}',\n" 225 try: 226 if request_result.text[:6] == string_start: 227 return (orjson.loads(request_result.text[6:])) 228 else: 229 return (orjson.loads(request_result.text)) 230 except Exception: 231 msg = "Can not decode to Json" 232 raise PumpWoodJSONLoadError( 233 message=msg, payload={"request_data": request_result.text})
Convert text to Json removing any XSSI at the beging of JSON.
Some backends add )]}',\n at the beginning of the JSON data to
prevent injection of functions. This function remove this characters
if present.
Arguments:
- request_result: JSON request to be converted.
Returns:
No return
Raises:
- PumpWoodJSONLoadError: If it is not possible to load JSON from request data.
235 def time_to_expiry(self) -> pd.Timedelta: 236 """Return time to token expiry. 237 238 Args: 239 No Args. 240 241 Returns: 242 Return time until token expiration. 243 """ 244 if self.__token_expiry is None: 245 return None 246 247 now_datetime = pd.to_datetime( 248 datetime.datetime.now(datetime.UTC), utc=True) 249 time_to_expiry = self.__token_expiry - now_datetime 250 return time_to_expiry
Return time to token expiry.
Arguments:
- No Args.
Returns:
Return time until token expiration.
252 def is_credential_set(self) -> bool: 253 """Check if username and password are set on object. 254 255 Args: 256 No Args. 257 258 Returns: 259 True if usename and password were set during object creation or 260 later with init function. 261 """ 262 is_username_not_none = self.__username is not None 263 is_password_not_none = self.__password is not None 264 return is_username_not_none and is_password_not_none
Check if username and password are set on object.
Arguments:
- No Args.
Returns:
True if usename and password were set during object creation or later with init function.
266 @classmethod 267 def is_invalid_token_response(cls, response: Response) -> bool: 268 """Check if reponse has invalid token error. 269 270 Args: 271 response: 272 Request reponse to check for invalid token. 273 274 Returns: 275 Return True if response has an invalid token status. 276 """ 277 if response.status_code == 401: 278 return True 279 return False
Check if reponse has invalid token error.
Arguments:
- response: Request reponse to check for invalid token.
Returns:
Return True if response has an invalid token status.
329 def confirm_mfa_code(self, mfa_login_data: dict) -> dict: 330 """Ask user to confirm MFA code to login. 331 332 Open an input interface at terminal for user to validate MFA token. 333 334 Args: 335 mfa_login_data: 336 Result from login request with 'mfa_token' 337 as key. 338 339 Returns: 340 Return login returned with MFA confimation. 341 342 Raise: 343 Raise error if reponse is not valid using error_handler. 344 """ 345 code = input("## Please enter MFA code: ") 346 url = urljoin( 347 self.server_url, 'rest/registration/mfa-validate-code/') 348 mfa_response = requests.post(url, headers={ 349 "X-PUMPWOOD-MFA-Autorization": mfa_login_data['mfa_token']}, 350 json={"mfa_code": code}, timeout=self._default_timeout) 351 self.error_handler(mfa_response) 352 353 # Set _is_mfa_login true to indicate that login required MFA 354 self._is_mfa_login = True 355 return self.angular_json(mfa_response)
Ask user to confirm MFA code to login.
Open an input interface at terminal for user to validate MFA token.
Arguments:
- mfa_login_data: Result from login request with 'mfa_token' as key.
Returns:
Return login returned with MFA confimation.
Raises:
- Raise error if reponse is not valid using error_handler.
357 def login(self, force_refresh: bool = False) -> None: 358 """Log microservice in using username and password provided. 359 360 Args: 361 force_refresh (bool): 362 Force token refresh despise still valid 363 according to self.__token_expiry. 364 365 Returns: 366 No return 367 368 Raises: 369 Exception: 370 If login response has status diferent from 200. 371 """ 372 if not self.is_credential_set(): 373 raise PumpWoodUnauthorized( 374 message="Microservice username or/and password not set") 375 376 # Check if expiry time is 1h from now 377 refresh_expiry = False 378 if self.__token_expiry is None: 379 refresh_expiry = True 380 else: 381 time_to_expiry = self.time_to_expiry() 382 if time_to_expiry < datetime.timedelta(hours=1): 383 refresh_expiry = True 384 385 # When if debug always refresh token 386 if refresh_expiry or force_refresh or self._debug: 387 login_data = self._login_resquest() 388 if 'mfa_token' in login_data.keys(): 389 login_data = self.confirm_mfa_code( 390 mfa_login_data=login_data) 391 392 self.set_auth_header( 393 auth_token='Token ' + login_data['token'], 394 token_expiry=pd.to_datetime(login_data['expiry']), 395 user=login_data["user"]) 396 else: 397 # Token is not expired or envicted, them keep same token 398 return None
Log microservice in using username and password provided.
Arguments:
- force_refresh (bool): Force token refresh despise still valid according to self.__token_expiry.
Returns:
No return
Raises:
- Exception: If login response has status diferent from 200.
410 def logout(self, auth_header: dict = None) -> bool: 411 """Logout token. 412 413 Args: 414 auth_header: 415 Authentication header. 416 417 Returns: 418 True if logout was ok. 419 """ 420 resp = self.request_post( 421 url='rest/registration/logout/', 422 data={}, auth_header=auth_header) 423 self._evict_auth_state() 424 return resp is None
Logout token.
Arguments:
- auth_header: Authentication header.
Returns:
True if logout was ok.
426 def logout_all(self, auth_header: dict = None) -> bool: 427 """Logout all tokens from user. 428 429 Args: 430 auth_header (dict): 431 Authentication header. 432 433 Returns: 434 True if logout all was ok. 435 """ 436 resp = self.request_post( 437 url='rest/registration/logoutall/', 438 data={}, auth_header=auth_header) 439 self._evict_auth_state() 440 return resp is None
Logout all tokens from user.
Arguments:
- auth_header (dict): Authentication header.
Returns:
True if logout all was ok.
442 def get_auth_header(self) -> dict: 443 """Retrieve auth_header and token_expiry from object. 444 445 Args: 446 No Args. 447 448 Returns: 449 Return authorization header and token_expiry datetime from object. 450 """ 451 # Copy the dictonary to avoid updating the original one 452 return copy.deepcopy({ 453 "auth_header": self.__auth_header, 454 "token_expiry": self.__token_expiry})
Retrieve auth_header and token_expiry from object.
Arguments:
- No Args.
Returns:
Return authorization header and token_expiry datetime from object.
456 def set_auth_header(self, auth_token: str, 457 token_expiry: pd.Timestamp, 458 user: dict = None) -> dict: 459 """Retrieve auth_header and token_expiry from object. 460 461 Args: 462 auth_token (str): 463 Auth token that will be set for authentication. 464 token_expiry (pd.Timestamp): 465 Token expiry time. 466 user (dict): 467 User information to be set on authetication. 468 469 Returns: 470 Return authorization header and token_expiry datetime from object. 471 """ 472 # Copy the dictonary to avoid updating the original one 473 self.__auth_header = { 474 'Authorization': auth_token} 475 self.__token_expiry = token_expiry 476 self.__user = user 477 return True
Retrieve auth_header and token_expiry from object.
Arguments:
- auth_token (str): Auth token that will be set for authentication.
- token_expiry (pd.Timestamp): Token expiry time.
- user (dict): User information to be set on authetication.
Returns:
Return authorization header and token_expiry datetime from object.
479 def is_superuser(self) -> bool: 480 """Check if is superuser. 481 482 Args: 483 No Args. 484 485 Returns: 486 Return True if is superuser. 487 """ 488 user = self.__user 489 if user is None: 490 return False 491 return user.get("is_superuser", False)
Check if is superuser.
Arguments:
- No Args.
Returns:
Return True if is superuser.
558 @classmethod 559 def error_handler(cls, response): 560 """Handle request error. 561 562 Check if is a Json and propagate the error with 563 same type if possible. If not Json raises the content. 564 565 Args: 566 response: 567 response to be handled, it is a PumpWoodException 568 return it will raise the same exception at microservice 569 object. 570 571 Returns: 572 No return. 573 574 Raises: 575 PumpWoodOtherException: 576 If content-type is not application/json. 577 PumpWoodOtherException: 578 If content-type is application/json, but type not 579 present or not recognisable at `exceptions.exceptions_dict`. 580 Other PumpWoodException sub-types: 581 If content-type is application/json if type is present and 582 recognisable. 583 584 Example: 585 No example 586 """ 587 if not response.ok: 588 utcnow = datetime.datetime.now(datetime.UTC) 589 response_content_type = response.headers['content-type'] 590 591 # Request information 592 url = response.url 593 method = response.request.method 594 if 'application/json' not in response_content_type.lower(): 595 # Raise the exception as first in exception deep. 596 exception_dict = [{ 597 "exception_url": url, 598 "exception_method": method, 599 "exception_utcnow": utcnow.isoformat(), 600 "exception_deep": 1}] 601 raise PumpWoodOtherException( 602 message=response.text, payload={ 603 "!exception_stack!": exception_dict}) 604 605 # Build error stack 606 response_dict = cls.angular_json(response) 607 608 # Removing previous error stack 609 payload = copy.deepcopy( 610 response_dict.get("payload", {})) 611 exception_stack = copy.deepcopy( 612 payload.pop("!exception_stack!", [])) 613 614 exception_deep = len(exception_stack) 615 exception_dict = { 616 "exception_url": url, 617 "exception_method": method, 618 "exception_utcnow": utcnow.isoformat(), 619 "exception_deep": exception_deep + 1 620 } 621 exception_stack.insert(0, exception_dict) 622 payload["!exception_stack!"] = exception_stack 623 624 ################### 625 # Propagate error # 626 # get exception using 'type' key at response data and get the 627 # exception from exceptions_dict at exceptions 628 exception_message = response_dict.get( 629 "message_not_fmt", response_dict.get("message", "")) 630 exception_type = response_dict.get("type") 631 TempPumpwoodException = exceptions_dict\ 632 .get(exception_type, None) 633 if TempPumpwoodException is not None: 634 raise TempPumpwoodException( 635 message=exception_message, 636 status_code=response_dict.get( 637 "status_code", response.status_code), 638 payload=payload, 639 was_translated=response_dict.get("was_translated", False), 640 parallel=response_dict.get("parallel", False)) 641 else: 642 # If token is invalid is at response, return a 643 # PumpWoodUnauthorized error 644 is_invalid_token = cls.is_invalid_token_response(response) 645 response_dict["!exception_stack!"] = exception_stack 646 if is_invalid_token: 647 raise PumpWoodUnauthorized( 648 message="Invalid token", payload=payload) 649 else: 650 # If the error is not mapped return a 651 # PumpWoodOtherException limiting the message size to 1k 652 # characters 653 raise PumpWoodOtherException( 654 message="Not mapped exception JSON", 655 payload=response_dict)
Handle request error.
Check if is a Json and propagate the error with same type if possible. If not Json raises the content.
Arguments:
- response: response to be handled, it is a PumpWoodException return it will raise the same exception at microservice object.
Returns:
No return.
Raises:
- PumpWoodOtherException: If content-type is not application/json.
- PumpWoodOtherException: If content-type is application/json, but type not
present or not recognisable at
exceptions.exceptions_dict. - Other PumpWoodException sub-types: If content-type is application/json if type is present and recognisable.
Example:
No example
808 def request_post(self, url: str, data: any, files: list = None, 809 auth_header: dict = None, 810 parameters: None | dict = {}) -> any: 811 """Make a POST a request to url with data as multipart/json payload. 812 813 Args: 814 url: 815 URL to make the request, already with server url. 816 data: 817 Data to be used as Json payload. 818 files: 819 A dictonary with file data, files will be set on field 820 corresponding.to dictonary key. 821 `{'file1': open('file1', 'rb'), {'file2': open('file2', 'rb')}` 822 parameters: 823 URL parameters. 824 auth_header: 825 AuthHeader to substitute the microservice original 826 at the request (user impersonation). 827 828 Returns: 829 Return the post response data. 830 831 Raises: 832 PumpWoodException sub-types: 833 Response is passed to error_handler. 834 """ 835 parameters = {} if parameters is None else parameters 836 837 post_url = urljoin(self.server_url, url) 838 dumped_parameters = self._dump_query_parameters(parameters=parameters) 839 response = None 840 if files is None: 841 response = self._request_post_json( 842 post_url=post_url, data=data, auth_header=auth_header, 843 parameters=dumped_parameters) 844 else: 845 response = self._request_post_multi( 846 post_url=post_url, data=data, files=files, 847 auth_header=auth_header, parameters=dumped_parameters) 848 849 # Handle errors and re-raise if Pumpwood Exceptions 850 self.error_handler(response) 851 return self._treat_response_for_file(response=response)
Make a POST a request to url with data as multipart/json payload.
Arguments:
- url: URL to make the request, already with server url.
- data: Data to be used as Json payload.
- files: A dictonary with file data, files will be set on field
corresponding.to dictonary key.
{'file1': open('file1', 'rb'), {'file2': open('file2', 'rb')} - parameters: URL parameters.
- auth_header: AuthHeader to substitute the microservice original at the request (user impersonation).
Returns:
Return the post response data.
Raises:
- PumpWoodException sub-types: Response is passed to error_handler.
853 def request_get(self, url: str, parameters: None | dict = None, 854 auth_header: dict = None, 855 use_disk_cache: bool = False, 856 disk_cache_expire: int = None, 857 disk_cache_tag_dict: dict = None) -> Any: 858 """Make a GET a request to url with data as JSON payload. 859 860 Add the auth_header acording to login information and refresh token 861 if auth_header=None and object token is expired. 862 863 Args: 864 url (str): 865 URL to make the request. 866 parameters (dict): 867 URL parameters to make the request. 868 auth_header (dict): 869 Auth header to substitute the microservice original 870 at the request (user impersonation). 871 use_disk_cache (bool): 872 If set true, get request will use local cache to reduce 873 the requests to the backend. 874 disk_cache_expire (int): 875 Time in seconds to expire the cache, it None it will 876 use de default set be PumpwoodCache. 877 disk_cache_tag_dict (dict): 878 Dictionary to be used as a tag on get request. 879 880 Returns: 881 Return the post reponse data. 882 883 Raises: 884 PumpWoodException sub-types: 885 Raise exception if reponse is not 2XX and if 'type' key on 886 JSON payload if found at exceptions_dict. Use the same 887 exception, message and payload. 888 PumpWoodOtherException: 889 If exception type is not found or return is not a json. 890 """ 891 parameters = {} if parameters is None else parameters 892 893 request_header = self._check_auth_header(auth_header) 894 # If is set to use diskcache, it will create a hash cash using 895 # the query paramerers, url and user access token. The 896 # hash will be used as index, not exposing the token at cache 897 # database 898 hash_dict = None 899 if use_disk_cache: 900 hash_dict = RequestGetCacheHash( 901 context='pumpwood_communication-request_get', 902 authorization=request_header['Authorization'], 903 parameters=parameters, 904 url=url) 905 906 cache_results = default_cache.get(hash_dict=hash_dict) 907 if cache_results is not None: 908 msg = "get from cache url[{url}]".format(url=url) 909 logger.info(msg) 910 return cache_results 911 912 dumped_parameters = self._dump_query_parameters(parameters=parameters) 913 get_url = urljoin(self.server_url, url) 914 response = requests.get( 915 get_url, verify=self._verify_ssl, headers=request_header, 916 params=dumped_parameters, timeout=self._default_timeout) 917 918 # If token is expired, refresh it 919 retry_with_login = ( 920 self.is_invalid_token_response(response) and 921 auth_header is None) 922 if retry_with_login: 923 time.sleep(0.5) 924 self.login(force_refresh=True) 925 request_header = self._check_auth_header(auth_header=auth_header) 926 response = requests.get( 927 get_url, verify=self._verify_ssl, headers=request_header, 928 params=dumped_parameters, timeout=self._default_timeout) 929 930 # Re-raise Pumpwood exceptions 931 self.error_handler(response=response) 932 results = self._treat_response_for_file(response=response) 933 934 # If is set to use cache for this calls, set the local cache 935 if use_disk_cache and not results.get('__file__', False): 936 default_cache.set( 937 hash_dict=hash_dict, value=results, 938 expire=disk_cache_expire, 939 tag_dict=disk_cache_tag_dict) 940 return results
Make a GET a request to url with data as JSON payload.
Add the auth_header acording to login information and refresh token if auth_header=None and object token is expired.
Arguments:
- url (str): URL to make the request.
- parameters (dict): URL parameters to make the request.
- auth_header (dict): Auth header to substitute the microservice original at the request (user impersonation).
- use_disk_cache (bool): If set true, get request will use local cache to reduce the requests to the backend.
- disk_cache_expire (int): Time in seconds to expire the cache, it None it will use de default set be PumpwoodCache.
- disk_cache_tag_dict (dict): Dictionary to be used as a tag on get request.
Returns:
Return the post reponse data.
Raises:
- PumpWoodException sub-types: Raise exception if reponse is not 2XX and if 'type' key on JSON payload if found at exceptions_dict. Use the same exception, message and payload.
- PumpWoodOtherException: If exception type is not found or return is not a json.
942 def request_delete(self, url, parameters: dict = None, 943 auth_header: dict = None): 944 """Make a DELETE a request to url with data as Json payload. 945 946 Args: 947 url: 948 Url to make the request. 949 parameters: 950 Dictionary with Urls parameters. 951 auth_header: 952 Auth header to substitute the microservice original 953 at the request (user impersonation). 954 955 Returns: 956 Return the delete reponse payload. 957 958 Raises: 959 PumpWoodException sub-types: 960 Raise exception if reponse is not 2XX and if 'type' key on 961 JSON payload if found at exceptions_dict. Use the same 962 exception, message and payload. 963 PumpWoodOtherException: 964 If exception type is not found or return is not a json. 965 """ 966 request_header = self._check_auth_header(auth_header) 967 dumped_parameters = self._dump_query_parameters(parameters=parameters) 968 969 post_url = self.server_url + url 970 response = requests.delete( 971 post_url, verify=self._verify_ssl, headers=request_header, 972 params=dumped_parameters, timeout=self._default_timeout) 973 974 # Retry request if token is not valid forcing token renew 975 retry_with_login = ( 976 self.is_invalid_token_response(response) and 977 auth_header is None) 978 if retry_with_login: 979 time.sleep(0.5) 980 self.login(force_refresh=True) 981 request_header = self._check_auth_header(auth_header=auth_header) 982 response = requests.delete( 983 post_url, verify=self._verify_ssl, headers=request_header, 984 params=dumped_parameters, timeout=self._default_timeout) 985 986 # Re-raise Pumpwood Exceptions 987 self.error_handler(response) 988 return self.angular_json(response)
Make a DELETE a request to url with data as Json payload.
Arguments:
- url: Url to make the request.
- parameters: Dictionary with Urls parameters.
- auth_header: Auth header to substitute the microservice original at the request (user impersonation).
Returns:
Return the delete reponse payload.
Raises:
- PumpWoodException sub-types: Raise exception if reponse is not 2XX and if 'type' key on JSON payload if found at exceptions_dict. Use the same exception, message and payload.
- PumpWoodOtherException: If exception type is not found or return is not a json.
990 def get_user_info(self, auth_header: dict = None, 991 use_disk_cache: bool = False, 992 disk_cache_expire: int = None) -> dict: 993 """Get user info. 994 995 Args: 996 auth_header (dict): = None 997 AuthHeader to substitute the microservice original at 998 request. If not passed, microservice object auth_header 999 will be used. 1000 use_disk_cache (bool): 1001 It possible use disk cache. 1002 disk_cache_expire (int): 1003 Set a time to expire the cache. If not passed env variable 1004 ``PUMPWOOD_COMMUNICATION__AUTHORIZATION_CACHE_TIMEOUT`` 1005 env variable (legacy spelling supported), default 60 s. 1006 1007 Returns: 1008 A serialized user object with information of the logged user. 1009 """ 1010 url = "rest/registration/retrieveauthenticateduser/" 1011 temp_disk_cache_expire = ( 1012 disk_cache_expire 1013 if disk_cache_expire is not None else 1014 AUTHORIZATION_CACHE_TIMEOUT) 1015 1016 user_info = self.request_get( 1017 url=url, auth_header=auth_header, use_disk_cache=use_disk_cache, 1018 disk_cache_expire=temp_disk_cache_expire) 1019 return user_info
Get user info.
Arguments:
- auth_header (dict): = None AuthHeader to substitute the microservice original at request. If not passed, microservice object auth_header will be used.
- use_disk_cache (bool): It possible use disk cache.
- disk_cache_expire (int): Set a time to expire the cache. If not passed env variable
PUMPWOOD_COMMUNICATION__AUTHORIZATION_CACHE_TIMEOUTenv variable (legacy spelling supported), default 60 s.
Returns:
A serialized user object with information of the logged user.