docusign_esign.client.api_client

DocuSign REST API

The DocuSign REST API provides you with a powerful, convenient, and simple Web services API for interacting with DocuSign.

OpenAPI spec version: v2 Contact: devcenter@docusign.com Generated by: https://github.com/swagger-api/swagger-codegen.git

  1# coding: utf-8
  2"""
  3    DocuSign REST API
  4
  5    The DocuSign REST API provides you with a powerful, convenient, and simple Web services API for interacting with DocuSign.
  6
  7    OpenAPI spec version: v2
  8    Contact: devcenter@docusign.com
  9    Generated by: https://github.com/swagger-api/swagger-codegen.git
 10"""
 11
 12from __future__ import absolute_import
 13
 14import base64
 15import json
 16import math
 17import mimetypes
 18import os
 19import re
 20import tempfile
 21import threading
 22from datetime import date, datetime
 23from time import time
 24
 25import jwt
 26# python 2 and python 3 compatibility library
 27from six import PY3, integer_types, iteritems, text_type
 28from six.moves.urllib.parse import quote
 29
 30from docusign_esign import client
 31from docusign_esign import models
 32from .configuration import Configuration
 33from .api_exception import ApiException, ArgumentException
 34from .api_response import RESTClientObject, RESTResponse
 35from .auth.oauth import OAuthUserInfo, OAuthToken, OAuth, Account, Organization, Link
 36
 37
 38class ApiClient(object):
 39    """
 40    Generic API client for Swagger client library builds.
 41
 42    Swagger generic API client. This client handles the client-
 43    server communication, and is invariant across implementations. Specifics of
 44    the methods and models for each application are generated from the Swagger
 45    templates.
 46
 47    NOTE: This class is auto generated by the swagger code generator program.
 48    Ref: https://github.com/swagger-api/swagger-codegen
 49    Do not edit the class manually.
 50
 51    :param host: The base path for the server to call.
 52    :param header_name: a header to pass when making calls to the API.
 53    :param header_value: a header value to pass when making calls to the API.
 54    """
 55
 56    PRIMITIVE_TYPES = (float, bool, bytes, text_type) + integer_types
 57    NATIVE_TYPES_MAPPING = {
 58        'int': int,
 59        'long': int if PY3 else long,
 60        'float': float,
 61        'str': str,
 62        'bool': bool,
 63        'date': date,
 64        'datetime': datetime,
 65        'object': object,
 66    }
 67    OAUTH_TYPES = (OAuthToken.__name__, OAuthUserInfo.__name__, Account.__name__, Organization.__name__, Link.__name__)
 68
 69    def __init__(self, host=None, header_name=None, header_value=None, cookie=None, oauth_host_name=None,
 70                 base_path=None):
 71        """
 72        Constructor of the class.
 73        """
 74        self.rest_client = RESTClientObject()
 75        self.default_headers = {'X-DocuSign-SDK': 'Python'}
 76
 77        if header_name is not None:
 78            self.default_headers[header_name] = header_value
 79
 80        if host is None:
 81            self.host = Configuration().host
 82        elif host == "":
 83            raise ArgumentException("basePath cannot be empty")
 84        else:
 85            self.host = host
 86
 87        self.cookie = cookie
 88        self.oauth_host_name = oauth_host_name
 89        self.base_path = base_path
 90        # Set default User-Agent.
 91        self.user_agent = Configuration().user_agent
 92
 93    @property
 94    def user_agent(self):
 95        """
 96        Gets user agent.
 97        """
 98        return self.default_headers['User-Agent']
 99
100    @user_agent.setter
101    def user_agent(self, value):
102        """
103        Sets user agent.
104        """
105        self.default_headers['User-Agent'] = value
106
107    def set_default_header(self, header_name, header_value):
108        self.default_headers[header_name] = header_value
109
110    def __call_api(self, resource_path, method,
111                   path_params=None, query_params=None, header_params=None,
112                   body=None, post_params=None, files=None,
113                   response_type=None, auth_settings=None, callback=None,
114                   _return_http_data_only=None, collection_formats=None, _preload_content=True,
115                   _request_timeout=None):
116        """
117        :param _preload_content: if False, the urllib3.HTTPResponse object will be returned without
118                                 reading/decoding response data. Default is True. 
119        :return: 
120        """
121
122        # header parameters
123        header_params = header_params or {}
124        header_params.update(self.default_headers)
125        if self.cookie:
126            header_params['Cookie'] = self.cookie
127        if header_params:
128            header_params = self.sanitize_for_serialization(header_params)
129            header_params = dict(self.parameters_to_tuples(header_params,
130                                                           collection_formats))
131
132        # path parameters
133        if path_params:
134            path_params = self.sanitize_for_serialization(path_params)
135            path_params = self.parameters_to_tuples(path_params,
136                                                    collection_formats)
137            for k, v in path_params:
138                resource_path = resource_path.replace(
139                    '{%s}' % k, quote(str(v), safe=""))
140
141        # query parameters
142        if query_params:
143            query_params = self.sanitize_for_serialization(query_params)
144            query_params = self.parameters_to_tuples(query_params,
145                                                     collection_formats)
146
147        # post parameters
148        if post_params or files:
149            post_params = self.prepare_post_parameters(post_params, files)
150            post_params = self.sanitize_for_serialization(post_params)
151            post_params = self.parameters_to_tuples(post_params,
152                                                    collection_formats)
153
154        # auth setting
155        self.update_params_for_auth(header_params, query_params, auth_settings)
156
157        # body
158        if body:
159            body = self.sanitize_for_serialization(body)
160
161        # request url
162        url = self.host + resource_path
163
164        # perform request and return response
165        response_data = self.request(method, url,
166                                     query_params=query_params,
167                                     headers=header_params,
168                                     post_params=post_params, body=body,
169                                     _preload_content=_preload_content,
170                                     _request_timeout=_request_timeout)
171
172        self.last_response = response_data
173
174        return_data = response_data
175
176        if _preload_content:
177            r = RESTResponse(response_data)
178            # deserialize response data
179            if response_type and response_type != "file":
180                # In the python 3, the response.data is bytes.
181                # we need to decode it to string.
182                if PY3:
183                    r.data = r.data.decode('utf8', 'replace')
184                return_data = self.deserialize(r, response_type)
185            elif response_type:
186                return_data = self.deserialize(r, response_type)
187            else:
188                return_data = None
189
190        if callback:
191            if _return_http_data_only:
192                callback(return_data)
193            else:
194                callback((return_data, response_data.status, response_data.getheaders()))
195        elif _return_http_data_only:
196            return (return_data)
197        else:
198            return (return_data, response_data.status, response_data.getheaders())
199
200    def sanitize_for_serialization(self, obj):
201        """
202        Builds a JSON POST object.
203
204        If obj is None, return None.
205        If obj is str, int, long, float, bool, return directly.
206        If obj is datetime.datetime, datetime.date
207            convert to string in iso8601 format.
208        If obj is list, sanitize each element in the list.
209        If obj is dict, return the dict.
210        If obj is swagger model, return the properties dict.
211
212        :param obj: The data to serialize.
213        :return: The serialized form of data.
214        """
215        if obj is None:
216            return None
217        elif isinstance(obj, self.PRIMITIVE_TYPES):
218            return obj
219        elif isinstance(obj, list):
220            return [self.sanitize_for_serialization(sub_obj)
221                    for sub_obj in obj]
222        elif isinstance(obj, tuple):
223            return tuple(self.sanitize_for_serialization(sub_obj)
224                         for sub_obj in obj)
225        elif isinstance(obj, (datetime, date)):
226            return obj.isoformat()
227
228        if isinstance(obj, dict):
229            obj_dict = obj
230        else:
231            # Convert model obj to dict except
232            # attributes `swagger_types`, `attribute_map`
233            # and attributes which value is not None.
234            # Convert attribute name to json key in
235            # model definition for request.
236            obj_dict = {obj.attribute_map[attr]: getattr(obj, attr)
237                        for attr, _ in iteritems(obj.swagger_types)
238                        if getattr(obj, attr) is not None}
239
240        return {key: self.sanitize_for_serialization(val)
241                for key, val in iteritems(obj_dict)}
242
243    def deserialize(self, response, response_type):
244        """
245        Deserializes response into an object.
246
247        :param response: RESTResponse object to be deserialized.
248        :param response_type: class literal for
249            deserialized object, or string of class name.
250
251        :return: deserialized object.
252        """
253        # handle file downloading
254        # save response body into a tmp file and return the instance
255        if response_type == "file":
256            return self.__deserialize_file(response)
257
258        # fetch data from response object
259        try:
260            data = json.loads(response.data)
261        except ValueError:
262            data = response.data
263
264        return self.__deserialize(data, response_type)
265
266    def __deserialize(self, data, klass):
267        """
268        Deserializes dict, list, str into an object.
269
270        :param data: dict, list or str.
271        :param klass: class literal, or string of class name.
272
273        :return: object.
274        """
275        if data is None:
276            return None
277
278        if type(klass) == str:
279            if klass.startswith('list['):
280                sub_kls = re.match('list\[(.*)\]', klass).group(1)
281                return [self.__deserialize(sub_data, sub_kls)
282                        for sub_data in data]
283
284            if klass.startswith('dict('):
285                sub_kls = re.match('dict\(([^,]*), (.*)\)', klass).group(2)
286                return {k: self.__deserialize(v, sub_kls)
287                        for k, v in iteritems(data)}
288
289            # convert str to class
290            if klass in self.NATIVE_TYPES_MAPPING:
291                klass = self.NATIVE_TYPES_MAPPING[klass]
292            else:
293                if klass in self.OAUTH_TYPES:
294                    klass = getattr(client, klass)
295                else:
296                    klass = getattr(models, klass)
297
298        if klass in self.PRIMITIVE_TYPES:
299            return self.__deserialize_primitive(data, klass)
300        elif klass == object:
301            return self.__deserialize_object(data)
302        elif klass == date:
303            return self.__deserialize_date(data)
304        elif klass == datetime:
305            return self.__deserialize_datatime(data)
306        else:
307            return self.__deserialize_model(data, klass)
308
309    def call_api(self, resource_path, method,
310                 path_params=None, query_params=None, header_params=None,
311                 body=None, post_params=None, files=None,
312                 response_type=None, auth_settings=None, callback=None,
313                 _return_http_data_only=None, collection_formats=None, _preload_content=True,
314                 _request_timeout=None):
315        """
316        Makes the HTTP request (synchronous) and return the deserialized data.
317        To make an async request, define a function for callback.
318
319        :param resource_path: Path to method endpoint.
320        :param method: Method to call.
321        :param path_params: Path parameters in the url.
322        :param query_params: Query parameters in the url.
323        :param header_params: Header parameters to be
324            placed in the request header.
325        :param body: Request body.
326        :param post_params dict: Request post form parameters,
327            for `application/x-www-form-urlencoded`, `multipart/form-data`.
328        :param auth_settings list: Auth Settings names for the request.
329        :param response: Response data type.
330        :param files dict: key -> filename, value -> filepath,
331            for `multipart/form-data`.
332        :param callback function: Callback function for asynchronous request.
333            If provide this parameter,
334            the request will be called asynchronously.
335        :param _return_http_data_only: response data without head status code and headers
336        :param collection_formats: dict of collection formats for path, query,
337            header, and post parameters.
338        :param _preload_content: if False, the urllib3.HTTPResponse object will be returned without
339                                 reading/decoding response data. Default is True.
340        :param _request_timeout: timeout setting for this request. If one number provided, it will be total request
341                                 timeout. It can also be a pair (tuple) of (connection, read) timeouts.
342        :return:
343            If provide parameter callback,
344            the request will be called asynchronously.
345            The method will return the request thread.
346            If parameter callback is None,
347            then the method will return the response directly.
348        """
349        if callback is None:
350            return self.__call_api(resource_path, method,
351                                   path_params, query_params, header_params,
352                                   body, post_params, files,
353                                   response_type, auth_settings, callback,
354                                   _return_http_data_only, collection_formats, _preload_content, _request_timeout)
355        else:
356            thread = threading.Thread(target=self.__call_api,
357                                      args=(resource_path, method,
358                                            path_params, query_params,
359                                            header_params, body,
360                                            post_params, files,
361                                            response_type, auth_settings,
362                                            callback, _return_http_data_only,
363                                            collection_formats, _preload_content, _request_timeout))
364        thread.start()
365        return thread
366
367    def request(self, method, url, query_params=None, headers=None,
368                post_params=None, body=None, _preload_content=True, _request_timeout=None):
369        """
370        Makes the HTTP request using RESTClient.
371        """
372        if method == "GET":
373            return self.rest_client.GET(url,
374                                        query_params=query_params,
375                                        _preload_content=_preload_content,
376                                        _request_timeout=_request_timeout,
377                                        headers=headers)
378        elif method == "HEAD":
379            return self.rest_client.HEAD(url,
380                                         query_params=query_params,
381                                         _preload_content=_preload_content,
382                                         _request_timeout=_request_timeout,
383                                         headers=headers)
384        elif method == "OPTIONS":
385            return self.rest_client.OPTIONS(url,
386                                            query_params=query_params,
387                                            headers=headers,
388                                            post_params=post_params,
389                                            _preload_content=_preload_content,
390                                            _request_timeout=_request_timeout,
391                                            body=body)
392        elif method == "POST":
393            return self.rest_client.POST(url,
394                                         query_params=query_params,
395                                         headers=headers,
396                                         post_params=post_params,
397                                         _preload_content=_preload_content,
398                                         _request_timeout=_request_timeout,
399                                         body=body)
400        elif method == "PUT":
401            return self.rest_client.PUT(url,
402                                        query_params=query_params,
403                                        headers=headers,
404                                        post_params=post_params,
405                                        _preload_content=_preload_content,
406                                        _request_timeout=_request_timeout,
407                                        body=body)
408        elif method == "PATCH":
409            return self.rest_client.PATCH(url,
410                                          query_params=query_params,
411                                          headers=headers,
412                                          post_params=post_params,
413                                          _preload_content=_preload_content,
414                                          _request_timeout=_request_timeout,
415                                          body=body)
416        elif method == "DELETE":
417            return self.rest_client.DELETE(url,
418                                           query_params=query_params,
419                                           headers=headers,
420                                           _preload_content=_preload_content,
421                                           _request_timeout=_request_timeout,
422                                           body=body)
423        else:
424            raise ValueError(
425                "http method must be `GET`, `HEAD`, `OPTIONS`,"
426                " `POST`, `PATCH`, `PUT` or `DELETE`."
427            )
428
429    def parameters_to_tuples(self, params, collection_formats):
430        """
431        Get parameters as list of tuples, formatting collections.
432
433        :param params: Parameters as dict or list of two-tuples
434        :param dict collection_formats: Parameter collection formats
435        :return: Parameters as list of tuples, collections formatted
436        """
437        new_params = []
438        if collection_formats is None:
439            collection_formats = {}
440        for k, v in iteritems(params) if isinstance(params, dict) else params:
441            if k in collection_formats:
442                collection_format = collection_formats[k]
443                if collection_format == 'multi':
444                    new_params.extend((k, value) for value in v)
445                else:
446                    if collection_format == 'ssv':
447                        delimiter = ' '
448                    elif collection_format == 'tsv':
449                        delimiter = '\t'
450                    elif collection_format == 'pipes':
451                        delimiter = '|'
452                    else:  # csv is the default
453                        delimiter = ','
454                    new_params.append(
455                        (k, delimiter.join(str(value) for value in v)))
456            else:
457                new_params.append((k, v))
458        return new_params
459
460    def prepare_post_parameters(self, post_params=None, files=None):
461        """
462        Builds form parameters.
463
464        :param post_params: Normal form parameters.
465        :param files: File parameters.
466        :return: Form parameters with files.
467        """
468        params = []
469
470        if post_params:
471            params = post_params
472
473        if files:
474            for k, v in iteritems(files):
475                if not v:
476                    continue
477                file_names = v if type(v) is list else [v]
478                for n in file_names:
479                    with open(n, 'rb') as f:
480                        filename = os.path.basename(f.name)
481                        filedata = f.read()
482                        mimetype = mimetypes. \
483                                       guess_type(filename)[0] or 'application/octet-stream'
484                        params.append(tuple([k, tuple([filename, filedata, mimetype])]))
485
486        return params
487
488    def select_header_accept(self, accepts):
489        """
490        Returns `Accept` based on an array of accepts provided.
491
492        :param accepts: List of headers.
493        :return: Accept (e.g. application/json).
494        """
495        if not accepts:
496            return
497
498        accepts = [x.lower() for x in accepts]
499
500        if 'application/json' in accepts:
501            return 'application/json'
502        else:
503            return ', '.join(accepts)
504
505    def select_header_content_type(self, content_types):
506        """
507        Returns `Content-Type` based on an array of content_types provided.
508
509        :param content_types: List of content-types.
510        :return: Content-Type (e.g. application/json).
511        """
512        if not content_types:
513            return 'application/json'
514
515        content_types = [x.lower() for x in content_types]
516
517        if 'application/json' in content_types or '*/*' in content_types:
518            return 'application/json'
519        else:
520            return content_types[0]
521
522    def update_params_for_auth(self, headers, querys, auth_settings):
523        """
524        Updates header and query params based on authentication setting.
525
526        :param headers: Header parameters dict to be updated.
527        :param querys: Query parameters tuple list to be updated.
528        :param auth_settings: Authentication setting identifiers list.
529        """
530        config = Configuration()
531
532        if not auth_settings:
533            return
534
535        for auth in auth_settings:
536            auth_setting = config.auth_settings().get(auth)
537            if auth_setting:
538                if not auth_setting['value']:
539                    continue
540                elif auth_setting['in'] == 'header':
541                    headers[auth_setting['key']] = auth_setting['value']
542                elif auth_setting['in'] == 'query':
543                    querys.append((auth_setting['key'], auth_setting['value']))
544                else:
545                    raise ValueError(
546                        'Authentication token must be in `query` or `header`'
547                    )
548
549    def __deserialize_file(self, response):
550        """
551        Saves response body into a file in a temporary folder,
552        using the filename from the `Content-Disposition` header if provided.
553
554        :param response:  RESTResponse.
555        :return: file path.
556        """
557        config = Configuration()
558
559        fd, path = tempfile.mkstemp(dir=config.temp_folder_path)
560        os.close(fd)
561        os.remove(path)
562
563        content_disposition = response.getheader("Content-Disposition")
564        if content_disposition:
565            filename = re. \
566                search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', content_disposition). \
567                group(1)
568
569            curr_time = datetime.now()
570            formatted_time = curr_time.strftime('%m%d%Y_%H%M%S_%f')
571            filename = "{}_{}".format(formatted_time, filename)
572            path = os.path.join(os.path.dirname(path), filename)
573
574        with open(path, "wb") as f:
575            f.write(response.data)
576        return path
577
578    def __deserialize_primitive(self, data, klass):
579        """
580        Deserializes string to primitive type.
581
582        :param data: str.
583        :param klass: class literal.
584
585        :return: int, long, float, str, bool.
586        """
587        try:
588            return klass(data)
589        except UnicodeEncodeError:
590            return unicode(data)
591        except TypeError:
592            return data
593
594    def __deserialize_object(self, value):
595        """
596        Return a original value.
597
598        :return: object.
599        """
600        return value
601
602    def __deserialize_date(self, string):
603        """
604        Deserializes string to date.
605
606        :param string: str.
607        :return: date.
608        """
609        try:
610            from dateutil.parser import parse
611            return parse(string).date()
612        except ImportError:
613            return string
614        except ValueError:
615            raise ApiException(
616                status=0,
617                reason="Failed to parse `{0}` into a date object".format(string)
618            )
619
620    def __deserialize_datatime(self, string):
621        """
622        Deserializes string to datetime.
623
624        The string should be in iso8601 datetime format.
625
626        :param string: str.
627        :return: datetime.
628        """
629        try:
630            from dateutil.parser import parse
631            return parse(string)
632        except ImportError:
633            return string
634        except ValueError:
635            raise ApiException(
636                status=0,
637                reason=(
638                    "Failed to parse `{0}` into a datetime object"
639                        .format(string)
640                )
641            )
642
643    def __deserialize_model(self, data, klass):
644        """
645        Deserializes list or dict to model.
646
647        :param data: dict, list.
648        :param klass: class literal.
649        :return: model object.
650        """
651        instance = klass()
652
653        if not instance.swagger_types:
654            return data
655
656        for attr, attr_type in iteritems(instance.swagger_types):
657            if data is not None \
658                    and instance.attribute_map[attr] in data \
659                    and isinstance(data, (list, dict)):
660                value = data[instance.attribute_map[attr]]
661                setattr(instance, attr, self.__deserialize(value, attr_type))
662
663        return instance
664
665    def request_jwt_user_token(self, client_id, user_id, oauth_host_name, private_key_bytes, expires_in,
666                               scopes=(OAuth.SCOPE_SIGNATURE,)):
667        """
668        Request JWT User Token
669        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
670        :param user_id: DocuSign user Id to be impersonated
671        :param oauth_host_name: DocuSign OAuth host name
672        :param private_key_bytes: the byte contents of the RSA private key
673        :param expires_in: number of seconds remaining before the JWT assertion is considered as invalid
674        :param scopes: Optional. The list of requested scopes may include (but not limited to) You can also pass any
675        advanced scope.
676        :return: OAuthToken object
677        """
678        if not private_key_bytes:
679            raise ArgumentException("Private key not supplied or is invalid!")
680        if not user_id:
681            raise ArgumentException("User Id not supplied or is invalid!")
682        if not oauth_host_name:
683            raise ArgumentException("oAuthBasePath cannot be empty")
684
685        now = math.floor(time())
686        later = now + (expires_in * 1)
687        claim = {"iss": client_id, "sub": user_id, "aud": oauth_host_name, "iat": now, "exp": later,
688                 "scope": " ".join(scopes)}
689        token = jwt.encode(payload=claim, key=private_key_bytes, algorithm='RS256')
690        response = self.request("POST", "https://" + oauth_host_name + "/oauth/token",
691                                headers=self.sanitize_for_serialization(
692                                    {"Content-Type": "application/x-www-form-urlencoded"}),
693                                post_params=self.sanitize_for_serialization(
694                                    {"assertion": token, "grant_type": OAuth.GRANT_TYPE_JWT}))
695
696        response_data = json.loads(response.data)
697
698        if 'token_type' in response_data and 'access_token' in response_data:
699            self.set_default_header("Authorization", response_data['token_type'] + " " + response_data['access_token'])
700        else:
701            raise ApiException(status=response.status,
702                               reason="Error while requesting server, received a non successful HTTP code {}"
703                                       " with response Body: {}".format(response.status, response.data)
704                               )
705
706        return self.deserialize(response=response, response_type=OAuthToken)
707
708    def request_jwt_application_token(self, client_id, oauth_host_name, private_key_bytes, expires_in,
709                                      scopes=(OAuth.SCOPE_SIGNATURE,)):
710        """
711        Request JWT Application Token
712        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
713        :param oauth_host_name: DocuSign OAuth host name
714        :param private_key_bytes: the byte contents of the RSA private key
715        :param expires_in: number of seconds remaining before the JWT assertion is considered as invalid
716        :param scopes: Optional. The list of requested scopes may include (but not limited to) You can also pass any
717        advanced scope.
718        :return: OAuthToken object
719        """
720
721        if not private_key_bytes:
722            raise ArgumentException("Private key not supplied or is invalid!")
723        if not oauth_host_name:
724            raise ArgumentException("oAuthBasePath cannot be empty")
725
726        now = math.floor(time())
727        later = now + (expires_in * 1)
728        claim = {"iss": client_id, "aud": oauth_host_name, "iat": now, "exp": later,
729                 "scope": " ".join(scopes)}
730        token = jwt.encode(payload=claim, key=private_key_bytes, algorithm='RS256')
731
732        response = self.request("POST", "https://" + oauth_host_name + "/oauth/token",
733                                headers=self.sanitize_for_serialization(
734                                    {"Content-Type": "application/x-www-form-urlencoded"}),
735                                post_params=self.sanitize_for_serialization(
736                                    {"assertion": token, "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer"}))
737        response_data = json.loads(response.data)
738
739        if 'token_type' in response_data and 'access_token' in response_data:
740            self.set_default_header("Authorization", response_data['token_type'] + " " + response_data['access_token'])
741        else:
742            ApiException(status=response.status,
743                         reason="Error while requesting server, received a non successful HTTP code {}"
744                                " with response Body: {}".format(response.status, response.data)
745                         )
746
747        return self.deserialize(response=response, response_type=OAuthToken)
748
749    def get_user_info(self, access_token):
750        """
751        Get User Info method takes the accessToken to retrieve User Account Data.
752        :param access_token:
753        :return: The User Info model.
754        """
755        if not access_token:
756            raise ArgumentException("Cannot find a valid access token."
757                                    " Make sure OAuth is configured before you try again.")
758        if not self.oauth_host_name:
759            raise ArgumentException("oAuthBasePath cannot be empty")
760
761        resource_path = '/oauth/userinfo'
762        headers = {"Authorization": "Bearer " + access_token}
763
764        response = self.request("GET", "https://" + self.oauth_host_name + resource_path, headers=headers)
765        return self.deserialize(response=response, response_type=OAuthUserInfo)
766
767    def generate_access_token(self, client_id, client_secret, code):
768        """
769        GenerateAccessToken will exchange the authorization code for an access token and refresh tokens.
770        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
771        :param client_secret: The secret key you generated when you set up the integration in DocuSign Admin console.
772        :param code: The authorization code
773        :return: OAuthToken object
774        """
775        if not client_id or not client_secret or not code:
776            raise ArgumentException
777        url = "https://{0}/oauth/token".format(self.oauth_host_name)
778        integrator_and_secret_key = b"Basic " + base64.b64encode(str.encode("{}:{}".format(client_id, client_secret)))
779        headers = {
780            "Authorization": integrator_and_secret_key.decode("utf-8"),
781            "Content-Type": "application/x-www-form-urlencoded",
782        }
783        post_params = self.sanitize_for_serialization({
784            "grant_type": "authorization_code",
785            "code": code
786        })
787        response = self.rest_client.POST(url, headers=headers, post_params=post_params)
788
789        return self.deserialize(response=response, response_type=OAuthToken)
790
791    def set_base_path(self, base_path):
792        """
793        :param base_path:
794        :return:
795        """
796        self.base_path = base_path
797
798    def set_oauth_host_name(self, oauth_host_name=None):
799        """
800        :param oauth_host_name:
801        :return:
802        """
803        if oauth_host_name:
804            self.oauth_host_name = oauth_host_name
805            return
806
807        if not oauth_host_name:
808            raise ArgumentException('oAuthBasePath cannot be empty')
809
810        # Derive OAuth Base Path if not given
811        if self.base_path.startswith("https://demo") or self.base_path.startswith("http://demo"):
812            self.oauth_host_name = OAuth.DEMO_OAUTH_BASE_PATH
813        elif self.base_path.startswith("https://stage") or self.base_path.startswith("http://stage"):
814            self.oauth_host_name = OAuth.STAGE_OAUTH_BASE_PATH
815        else:
816            self.oauth_host_name = OAuth.PRODUCTION_OAUTH_BASE_PATH
817
818    def set_access_token(self, token_obj):
819        """
820
821        :param token_obj: 
822        :return: 
823        """
824        self.default_headers['Authorization'] = token_obj.access_token
825
826    def get_authorization_uri(self, client_id, scopes, redirect_uri, response_type, state=None):
827        """
828        Helper method to configure the OAuth accessCode/implicit flow parameters
829        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
830        :param scopes: The list of requested scopes.  Client applications may be scoped to a limited set of system access.
831        :param redirect_uri: This determines where to deliver the response containing the authorization code
832        :param response_type: Determines the response type of the authorization request, NOTE: these response types are
833        mutually exclusive for a client application. A public/native client application may only request a response type
834         of "token". A private/trusted client application may only request a response type of "code".
835        :param state: Allows for arbitrary state that may be useful to your application. The value in this parameter
836        will be round-tripped along with the response so you can make sure it didn't change.
837        :return: string
838        """
839        if not self.oauth_host_name:
840            self.oauth_host_name = self.get_oauth_host_name()
841        scopes = " ".join(scopes) if scopes else ""
842        uri = "https://{}/oauth/auth?response_type={}&scope={}&client_id={}&redirect_uri={}"
843        if state:
844            uri += "&state={}"
845        return uri.format(self.oauth_host_name, response_type, quote(scopes), client_id, redirect_uri, state)
846
847    def get_oauth_host_name(self):
848        """
849        :return: string
850        """
851        if not self.oauth_host_name:
852            self.set_oauth_host_name()
853
854        return self.oauth_host_name
class ApiClient:
 39class ApiClient(object):
 40    """
 41    Generic API client for Swagger client library builds.
 42
 43    Swagger generic API client. This client handles the client-
 44    server communication, and is invariant across implementations. Specifics of
 45    the methods and models for each application are generated from the Swagger
 46    templates.
 47
 48    NOTE: This class is auto generated by the swagger code generator program.
 49    Ref: https://github.com/swagger-api/swagger-codegen
 50    Do not edit the class manually.
 51
 52    :param host: The base path for the server to call.
 53    :param header_name: a header to pass when making calls to the API.
 54    :param header_value: a header value to pass when making calls to the API.
 55    """
 56
 57    PRIMITIVE_TYPES = (float, bool, bytes, text_type) + integer_types
 58    NATIVE_TYPES_MAPPING = {
 59        'int': int,
 60        'long': int if PY3 else long,
 61        'float': float,
 62        'str': str,
 63        'bool': bool,
 64        'date': date,
 65        'datetime': datetime,
 66        'object': object,
 67    }
 68    OAUTH_TYPES = (OAuthToken.__name__, OAuthUserInfo.__name__, Account.__name__, Organization.__name__, Link.__name__)
 69
 70    def __init__(self, host=None, header_name=None, header_value=None, cookie=None, oauth_host_name=None,
 71                 base_path=None):
 72        """
 73        Constructor of the class.
 74        """
 75        self.rest_client = RESTClientObject()
 76        self.default_headers = {'X-DocuSign-SDK': 'Python'}
 77
 78        if header_name is not None:
 79            self.default_headers[header_name] = header_value
 80
 81        if host is None:
 82            self.host = Configuration().host
 83        elif host == "":
 84            raise ArgumentException("basePath cannot be empty")
 85        else:
 86            self.host = host
 87
 88        self.cookie = cookie
 89        self.oauth_host_name = oauth_host_name
 90        self.base_path = base_path
 91        # Set default User-Agent.
 92        self.user_agent = Configuration().user_agent
 93
 94    @property
 95    def user_agent(self):
 96        """
 97        Gets user agent.
 98        """
 99        return self.default_headers['User-Agent']
100
101    @user_agent.setter
102    def user_agent(self, value):
103        """
104        Sets user agent.
105        """
106        self.default_headers['User-Agent'] = value
107
108    def set_default_header(self, header_name, header_value):
109        self.default_headers[header_name] = header_value
110
111    def __call_api(self, resource_path, method,
112                   path_params=None, query_params=None, header_params=None,
113                   body=None, post_params=None, files=None,
114                   response_type=None, auth_settings=None, callback=None,
115                   _return_http_data_only=None, collection_formats=None, _preload_content=True,
116                   _request_timeout=None):
117        """
118        :param _preload_content: if False, the urllib3.HTTPResponse object will be returned without
119                                 reading/decoding response data. Default is True. 
120        :return: 
121        """
122
123        # header parameters
124        header_params = header_params or {}
125        header_params.update(self.default_headers)
126        if self.cookie:
127            header_params['Cookie'] = self.cookie
128        if header_params:
129            header_params = self.sanitize_for_serialization(header_params)
130            header_params = dict(self.parameters_to_tuples(header_params,
131                                                           collection_formats))
132
133        # path parameters
134        if path_params:
135            path_params = self.sanitize_for_serialization(path_params)
136            path_params = self.parameters_to_tuples(path_params,
137                                                    collection_formats)
138            for k, v in path_params:
139                resource_path = resource_path.replace(
140                    '{%s}' % k, quote(str(v), safe=""))
141
142        # query parameters
143        if query_params:
144            query_params = self.sanitize_for_serialization(query_params)
145            query_params = self.parameters_to_tuples(query_params,
146                                                     collection_formats)
147
148        # post parameters
149        if post_params or files:
150            post_params = self.prepare_post_parameters(post_params, files)
151            post_params = self.sanitize_for_serialization(post_params)
152            post_params = self.parameters_to_tuples(post_params,
153                                                    collection_formats)
154
155        # auth setting
156        self.update_params_for_auth(header_params, query_params, auth_settings)
157
158        # body
159        if body:
160            body = self.sanitize_for_serialization(body)
161
162        # request url
163        url = self.host + resource_path
164
165        # perform request and return response
166        response_data = self.request(method, url,
167                                     query_params=query_params,
168                                     headers=header_params,
169                                     post_params=post_params, body=body,
170                                     _preload_content=_preload_content,
171                                     _request_timeout=_request_timeout)
172
173        self.last_response = response_data
174
175        return_data = response_data
176
177        if _preload_content:
178            r = RESTResponse(response_data)
179            # deserialize response data
180            if response_type and response_type != "file":
181                # In the python 3, the response.data is bytes.
182                # we need to decode it to string.
183                if PY3:
184                    r.data = r.data.decode('utf8', 'replace')
185                return_data = self.deserialize(r, response_type)
186            elif response_type:
187                return_data = self.deserialize(r, response_type)
188            else:
189                return_data = None
190
191        if callback:
192            if _return_http_data_only:
193                callback(return_data)
194            else:
195                callback((return_data, response_data.status, response_data.getheaders()))
196        elif _return_http_data_only:
197            return (return_data)
198        else:
199            return (return_data, response_data.status, response_data.getheaders())
200
201    def sanitize_for_serialization(self, obj):
202        """
203        Builds a JSON POST object.
204
205        If obj is None, return None.
206        If obj is str, int, long, float, bool, return directly.
207        If obj is datetime.datetime, datetime.date
208            convert to string in iso8601 format.
209        If obj is list, sanitize each element in the list.
210        If obj is dict, return the dict.
211        If obj is swagger model, return the properties dict.
212
213        :param obj: The data to serialize.
214        :return: The serialized form of data.
215        """
216        if obj is None:
217            return None
218        elif isinstance(obj, self.PRIMITIVE_TYPES):
219            return obj
220        elif isinstance(obj, list):
221            return [self.sanitize_for_serialization(sub_obj)
222                    for sub_obj in obj]
223        elif isinstance(obj, tuple):
224            return tuple(self.sanitize_for_serialization(sub_obj)
225                         for sub_obj in obj)
226        elif isinstance(obj, (datetime, date)):
227            return obj.isoformat()
228
229        if isinstance(obj, dict):
230            obj_dict = obj
231        else:
232            # Convert model obj to dict except
233            # attributes `swagger_types`, `attribute_map`
234            # and attributes which value is not None.
235            # Convert attribute name to json key in
236            # model definition for request.
237            obj_dict = {obj.attribute_map[attr]: getattr(obj, attr)
238                        for attr, _ in iteritems(obj.swagger_types)
239                        if getattr(obj, attr) is not None}
240
241        return {key: self.sanitize_for_serialization(val)
242                for key, val in iteritems(obj_dict)}
243
244    def deserialize(self, response, response_type):
245        """
246        Deserializes response into an object.
247
248        :param response: RESTResponse object to be deserialized.
249        :param response_type: class literal for
250            deserialized object, or string of class name.
251
252        :return: deserialized object.
253        """
254        # handle file downloading
255        # save response body into a tmp file and return the instance
256        if response_type == "file":
257            return self.__deserialize_file(response)
258
259        # fetch data from response object
260        try:
261            data = json.loads(response.data)
262        except ValueError:
263            data = response.data
264
265        return self.__deserialize(data, response_type)
266
267    def __deserialize(self, data, klass):
268        """
269        Deserializes dict, list, str into an object.
270
271        :param data: dict, list or str.
272        :param klass: class literal, or string of class name.
273
274        :return: object.
275        """
276        if data is None:
277            return None
278
279        if type(klass) == str:
280            if klass.startswith('list['):
281                sub_kls = re.match('list\[(.*)\]', klass).group(1)
282                return [self.__deserialize(sub_data, sub_kls)
283                        for sub_data in data]
284
285            if klass.startswith('dict('):
286                sub_kls = re.match('dict\(([^,]*), (.*)\)', klass).group(2)
287                return {k: self.__deserialize(v, sub_kls)
288                        for k, v in iteritems(data)}
289
290            # convert str to class
291            if klass in self.NATIVE_TYPES_MAPPING:
292                klass = self.NATIVE_TYPES_MAPPING[klass]
293            else:
294                if klass in self.OAUTH_TYPES:
295                    klass = getattr(client, klass)
296                else:
297                    klass = getattr(models, klass)
298
299        if klass in self.PRIMITIVE_TYPES:
300            return self.__deserialize_primitive(data, klass)
301        elif klass == object:
302            return self.__deserialize_object(data)
303        elif klass == date:
304            return self.__deserialize_date(data)
305        elif klass == datetime:
306            return self.__deserialize_datatime(data)
307        else:
308            return self.__deserialize_model(data, klass)
309
310    def call_api(self, resource_path, method,
311                 path_params=None, query_params=None, header_params=None,
312                 body=None, post_params=None, files=None,
313                 response_type=None, auth_settings=None, callback=None,
314                 _return_http_data_only=None, collection_formats=None, _preload_content=True,
315                 _request_timeout=None):
316        """
317        Makes the HTTP request (synchronous) and return the deserialized data.
318        To make an async request, define a function for callback.
319
320        :param resource_path: Path to method endpoint.
321        :param method: Method to call.
322        :param path_params: Path parameters in the url.
323        :param query_params: Query parameters in the url.
324        :param header_params: Header parameters to be
325            placed in the request header.
326        :param body: Request body.
327        :param post_params dict: Request post form parameters,
328            for `application/x-www-form-urlencoded`, `multipart/form-data`.
329        :param auth_settings list: Auth Settings names for the request.
330        :param response: Response data type.
331        :param files dict: key -> filename, value -> filepath,
332            for `multipart/form-data`.
333        :param callback function: Callback function for asynchronous request.
334            If provide this parameter,
335            the request will be called asynchronously.
336        :param _return_http_data_only: response data without head status code and headers
337        :param collection_formats: dict of collection formats for path, query,
338            header, and post parameters.
339        :param _preload_content: if False, the urllib3.HTTPResponse object will be returned without
340                                 reading/decoding response data. Default is True.
341        :param _request_timeout: timeout setting for this request. If one number provided, it will be total request
342                                 timeout. It can also be a pair (tuple) of (connection, read) timeouts.
343        :return:
344            If provide parameter callback,
345            the request will be called asynchronously.
346            The method will return the request thread.
347            If parameter callback is None,
348            then the method will return the response directly.
349        """
350        if callback is None:
351            return self.__call_api(resource_path, method,
352                                   path_params, query_params, header_params,
353                                   body, post_params, files,
354                                   response_type, auth_settings, callback,
355                                   _return_http_data_only, collection_formats, _preload_content, _request_timeout)
356        else:
357            thread = threading.Thread(target=self.__call_api,
358                                      args=(resource_path, method,
359                                            path_params, query_params,
360                                            header_params, body,
361                                            post_params, files,
362                                            response_type, auth_settings,
363                                            callback, _return_http_data_only,
364                                            collection_formats, _preload_content, _request_timeout))
365        thread.start()
366        return thread
367
368    def request(self, method, url, query_params=None, headers=None,
369                post_params=None, body=None, _preload_content=True, _request_timeout=None):
370        """
371        Makes the HTTP request using RESTClient.
372        """
373        if method == "GET":
374            return self.rest_client.GET(url,
375                                        query_params=query_params,
376                                        _preload_content=_preload_content,
377                                        _request_timeout=_request_timeout,
378                                        headers=headers)
379        elif method == "HEAD":
380            return self.rest_client.HEAD(url,
381                                         query_params=query_params,
382                                         _preload_content=_preload_content,
383                                         _request_timeout=_request_timeout,
384                                         headers=headers)
385        elif method == "OPTIONS":
386            return self.rest_client.OPTIONS(url,
387                                            query_params=query_params,
388                                            headers=headers,
389                                            post_params=post_params,
390                                            _preload_content=_preload_content,
391                                            _request_timeout=_request_timeout,
392                                            body=body)
393        elif method == "POST":
394            return self.rest_client.POST(url,
395                                         query_params=query_params,
396                                         headers=headers,
397                                         post_params=post_params,
398                                         _preload_content=_preload_content,
399                                         _request_timeout=_request_timeout,
400                                         body=body)
401        elif method == "PUT":
402            return self.rest_client.PUT(url,
403                                        query_params=query_params,
404                                        headers=headers,
405                                        post_params=post_params,
406                                        _preload_content=_preload_content,
407                                        _request_timeout=_request_timeout,
408                                        body=body)
409        elif method == "PATCH":
410            return self.rest_client.PATCH(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 == "DELETE":
418            return self.rest_client.DELETE(url,
419                                           query_params=query_params,
420                                           headers=headers,
421                                           _preload_content=_preload_content,
422                                           _request_timeout=_request_timeout,
423                                           body=body)
424        else:
425            raise ValueError(
426                "http method must be `GET`, `HEAD`, `OPTIONS`,"
427                " `POST`, `PATCH`, `PUT` or `DELETE`."
428            )
429
430    def parameters_to_tuples(self, params, collection_formats):
431        """
432        Get parameters as list of tuples, formatting collections.
433
434        :param params: Parameters as dict or list of two-tuples
435        :param dict collection_formats: Parameter collection formats
436        :return: Parameters as list of tuples, collections formatted
437        """
438        new_params = []
439        if collection_formats is None:
440            collection_formats = {}
441        for k, v in iteritems(params) if isinstance(params, dict) else params:
442            if k in collection_formats:
443                collection_format = collection_formats[k]
444                if collection_format == 'multi':
445                    new_params.extend((k, value) for value in v)
446                else:
447                    if collection_format == 'ssv':
448                        delimiter = ' '
449                    elif collection_format == 'tsv':
450                        delimiter = '\t'
451                    elif collection_format == 'pipes':
452                        delimiter = '|'
453                    else:  # csv is the default
454                        delimiter = ','
455                    new_params.append(
456                        (k, delimiter.join(str(value) for value in v)))
457            else:
458                new_params.append((k, v))
459        return new_params
460
461    def prepare_post_parameters(self, post_params=None, files=None):
462        """
463        Builds form parameters.
464
465        :param post_params: Normal form parameters.
466        :param files: File parameters.
467        :return: Form parameters with files.
468        """
469        params = []
470
471        if post_params:
472            params = post_params
473
474        if files:
475            for k, v in iteritems(files):
476                if not v:
477                    continue
478                file_names = v if type(v) is list else [v]
479                for n in file_names:
480                    with open(n, 'rb') as f:
481                        filename = os.path.basename(f.name)
482                        filedata = f.read()
483                        mimetype = mimetypes. \
484                                       guess_type(filename)[0] or 'application/octet-stream'
485                        params.append(tuple([k, tuple([filename, filedata, mimetype])]))
486
487        return params
488
489    def select_header_accept(self, accepts):
490        """
491        Returns `Accept` based on an array of accepts provided.
492
493        :param accepts: List of headers.
494        :return: Accept (e.g. application/json).
495        """
496        if not accepts:
497            return
498
499        accepts = [x.lower() for x in accepts]
500
501        if 'application/json' in accepts:
502            return 'application/json'
503        else:
504            return ', '.join(accepts)
505
506    def select_header_content_type(self, content_types):
507        """
508        Returns `Content-Type` based on an array of content_types provided.
509
510        :param content_types: List of content-types.
511        :return: Content-Type (e.g. application/json).
512        """
513        if not content_types:
514            return 'application/json'
515
516        content_types = [x.lower() for x in content_types]
517
518        if 'application/json' in content_types or '*/*' in content_types:
519            return 'application/json'
520        else:
521            return content_types[0]
522
523    def update_params_for_auth(self, headers, querys, auth_settings):
524        """
525        Updates header and query params based on authentication setting.
526
527        :param headers: Header parameters dict to be updated.
528        :param querys: Query parameters tuple list to be updated.
529        :param auth_settings: Authentication setting identifiers list.
530        """
531        config = Configuration()
532
533        if not auth_settings:
534            return
535
536        for auth in auth_settings:
537            auth_setting = config.auth_settings().get(auth)
538            if auth_setting:
539                if not auth_setting['value']:
540                    continue
541                elif auth_setting['in'] == 'header':
542                    headers[auth_setting['key']] = auth_setting['value']
543                elif auth_setting['in'] == 'query':
544                    querys.append((auth_setting['key'], auth_setting['value']))
545                else:
546                    raise ValueError(
547                        'Authentication token must be in `query` or `header`'
548                    )
549
550    def __deserialize_file(self, response):
551        """
552        Saves response body into a file in a temporary folder,
553        using the filename from the `Content-Disposition` header if provided.
554
555        :param response:  RESTResponse.
556        :return: file path.
557        """
558        config = Configuration()
559
560        fd, path = tempfile.mkstemp(dir=config.temp_folder_path)
561        os.close(fd)
562        os.remove(path)
563
564        content_disposition = response.getheader("Content-Disposition")
565        if content_disposition:
566            filename = re. \
567                search(r'filename=[\'"]?([^\'"\s]+)[\'"]?', content_disposition). \
568                group(1)
569
570            curr_time = datetime.now()
571            formatted_time = curr_time.strftime('%m%d%Y_%H%M%S_%f')
572            filename = "{}_{}".format(formatted_time, filename)
573            path = os.path.join(os.path.dirname(path), filename)
574
575        with open(path, "wb") as f:
576            f.write(response.data)
577        return path
578
579    def __deserialize_primitive(self, data, klass):
580        """
581        Deserializes string to primitive type.
582
583        :param data: str.
584        :param klass: class literal.
585
586        :return: int, long, float, str, bool.
587        """
588        try:
589            return klass(data)
590        except UnicodeEncodeError:
591            return unicode(data)
592        except TypeError:
593            return data
594
595    def __deserialize_object(self, value):
596        """
597        Return a original value.
598
599        :return: object.
600        """
601        return value
602
603    def __deserialize_date(self, string):
604        """
605        Deserializes string to date.
606
607        :param string: str.
608        :return: date.
609        """
610        try:
611            from dateutil.parser import parse
612            return parse(string).date()
613        except ImportError:
614            return string
615        except ValueError:
616            raise ApiException(
617                status=0,
618                reason="Failed to parse `{0}` into a date object".format(string)
619            )
620
621    def __deserialize_datatime(self, string):
622        """
623        Deserializes string to datetime.
624
625        The string should be in iso8601 datetime format.
626
627        :param string: str.
628        :return: datetime.
629        """
630        try:
631            from dateutil.parser import parse
632            return parse(string)
633        except ImportError:
634            return string
635        except ValueError:
636            raise ApiException(
637                status=0,
638                reason=(
639                    "Failed to parse `{0}` into a datetime object"
640                        .format(string)
641                )
642            )
643
644    def __deserialize_model(self, data, klass):
645        """
646        Deserializes list or dict to model.
647
648        :param data: dict, list.
649        :param klass: class literal.
650        :return: model object.
651        """
652        instance = klass()
653
654        if not instance.swagger_types:
655            return data
656
657        for attr, attr_type in iteritems(instance.swagger_types):
658            if data is not None \
659                    and instance.attribute_map[attr] in data \
660                    and isinstance(data, (list, dict)):
661                value = data[instance.attribute_map[attr]]
662                setattr(instance, attr, self.__deserialize(value, attr_type))
663
664        return instance
665
666    def request_jwt_user_token(self, client_id, user_id, oauth_host_name, private_key_bytes, expires_in,
667                               scopes=(OAuth.SCOPE_SIGNATURE,)):
668        """
669        Request JWT User Token
670        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
671        :param user_id: DocuSign user Id to be impersonated
672        :param oauth_host_name: DocuSign OAuth host name
673        :param private_key_bytes: the byte contents of the RSA private key
674        :param expires_in: number of seconds remaining before the JWT assertion is considered as invalid
675        :param scopes: Optional. The list of requested scopes may include (but not limited to) You can also pass any
676        advanced scope.
677        :return: OAuthToken object
678        """
679        if not private_key_bytes:
680            raise ArgumentException("Private key not supplied or is invalid!")
681        if not user_id:
682            raise ArgumentException("User Id not supplied or is invalid!")
683        if not oauth_host_name:
684            raise ArgumentException("oAuthBasePath cannot be empty")
685
686        now = math.floor(time())
687        later = now + (expires_in * 1)
688        claim = {"iss": client_id, "sub": user_id, "aud": oauth_host_name, "iat": now, "exp": later,
689                 "scope": " ".join(scopes)}
690        token = jwt.encode(payload=claim, key=private_key_bytes, algorithm='RS256')
691        response = self.request("POST", "https://" + oauth_host_name + "/oauth/token",
692                                headers=self.sanitize_for_serialization(
693                                    {"Content-Type": "application/x-www-form-urlencoded"}),
694                                post_params=self.sanitize_for_serialization(
695                                    {"assertion": token, "grant_type": OAuth.GRANT_TYPE_JWT}))
696
697        response_data = json.loads(response.data)
698
699        if 'token_type' in response_data and 'access_token' in response_data:
700            self.set_default_header("Authorization", response_data['token_type'] + " " + response_data['access_token'])
701        else:
702            raise ApiException(status=response.status,
703                               reason="Error while requesting server, received a non successful HTTP code {}"
704                                       " with response Body: {}".format(response.status, response.data)
705                               )
706
707        return self.deserialize(response=response, response_type=OAuthToken)
708
709    def request_jwt_application_token(self, client_id, oauth_host_name, private_key_bytes, expires_in,
710                                      scopes=(OAuth.SCOPE_SIGNATURE,)):
711        """
712        Request JWT Application Token
713        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
714        :param oauth_host_name: DocuSign OAuth host name
715        :param private_key_bytes: the byte contents of the RSA private key
716        :param expires_in: number of seconds remaining before the JWT assertion is considered as invalid
717        :param scopes: Optional. The list of requested scopes may include (but not limited to) You can also pass any
718        advanced scope.
719        :return: OAuthToken object
720        """
721
722        if not private_key_bytes:
723            raise ArgumentException("Private key not supplied or is invalid!")
724        if not oauth_host_name:
725            raise ArgumentException("oAuthBasePath cannot be empty")
726
727        now = math.floor(time())
728        later = now + (expires_in * 1)
729        claim = {"iss": client_id, "aud": oauth_host_name, "iat": now, "exp": later,
730                 "scope": " ".join(scopes)}
731        token = jwt.encode(payload=claim, key=private_key_bytes, algorithm='RS256')
732
733        response = self.request("POST", "https://" + oauth_host_name + "/oauth/token",
734                                headers=self.sanitize_for_serialization(
735                                    {"Content-Type": "application/x-www-form-urlencoded"}),
736                                post_params=self.sanitize_for_serialization(
737                                    {"assertion": token, "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer"}))
738        response_data = json.loads(response.data)
739
740        if 'token_type' in response_data and 'access_token' in response_data:
741            self.set_default_header("Authorization", response_data['token_type'] + " " + response_data['access_token'])
742        else:
743            ApiException(status=response.status,
744                         reason="Error while requesting server, received a non successful HTTP code {}"
745                                " with response Body: {}".format(response.status, response.data)
746                         )
747
748        return self.deserialize(response=response, response_type=OAuthToken)
749
750    def get_user_info(self, access_token):
751        """
752        Get User Info method takes the accessToken to retrieve User Account Data.
753        :param access_token:
754        :return: The User Info model.
755        """
756        if not access_token:
757            raise ArgumentException("Cannot find a valid access token."
758                                    " Make sure OAuth is configured before you try again.")
759        if not self.oauth_host_name:
760            raise ArgumentException("oAuthBasePath cannot be empty")
761
762        resource_path = '/oauth/userinfo'
763        headers = {"Authorization": "Bearer " + access_token}
764
765        response = self.request("GET", "https://" + self.oauth_host_name + resource_path, headers=headers)
766        return self.deserialize(response=response, response_type=OAuthUserInfo)
767
768    def generate_access_token(self, client_id, client_secret, code):
769        """
770        GenerateAccessToken will exchange the authorization code for an access token and refresh tokens.
771        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
772        :param client_secret: The secret key you generated when you set up the integration in DocuSign Admin console.
773        :param code: The authorization code
774        :return: OAuthToken object
775        """
776        if not client_id or not client_secret or not code:
777            raise ArgumentException
778        url = "https://{0}/oauth/token".format(self.oauth_host_name)
779        integrator_and_secret_key = b"Basic " + base64.b64encode(str.encode("{}:{}".format(client_id, client_secret)))
780        headers = {
781            "Authorization": integrator_and_secret_key.decode("utf-8"),
782            "Content-Type": "application/x-www-form-urlencoded",
783        }
784        post_params = self.sanitize_for_serialization({
785            "grant_type": "authorization_code",
786            "code": code
787        })
788        response = self.rest_client.POST(url, headers=headers, post_params=post_params)
789
790        return self.deserialize(response=response, response_type=OAuthToken)
791
792    def set_base_path(self, base_path):
793        """
794        :param base_path:
795        :return:
796        """
797        self.base_path = base_path
798
799    def set_oauth_host_name(self, oauth_host_name=None):
800        """
801        :param oauth_host_name:
802        :return:
803        """
804        if oauth_host_name:
805            self.oauth_host_name = oauth_host_name
806            return
807
808        if not oauth_host_name:
809            raise ArgumentException('oAuthBasePath cannot be empty')
810
811        # Derive OAuth Base Path if not given
812        if self.base_path.startswith("https://demo") or self.base_path.startswith("http://demo"):
813            self.oauth_host_name = OAuth.DEMO_OAUTH_BASE_PATH
814        elif self.base_path.startswith("https://stage") or self.base_path.startswith("http://stage"):
815            self.oauth_host_name = OAuth.STAGE_OAUTH_BASE_PATH
816        else:
817            self.oauth_host_name = OAuth.PRODUCTION_OAUTH_BASE_PATH
818
819    def set_access_token(self, token_obj):
820        """
821
822        :param token_obj: 
823        :return: 
824        """
825        self.default_headers['Authorization'] = token_obj.access_token
826
827    def get_authorization_uri(self, client_id, scopes, redirect_uri, response_type, state=None):
828        """
829        Helper method to configure the OAuth accessCode/implicit flow parameters
830        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
831        :param scopes: The list of requested scopes.  Client applications may be scoped to a limited set of system access.
832        :param redirect_uri: This determines where to deliver the response containing the authorization code
833        :param response_type: Determines the response type of the authorization request, NOTE: these response types are
834        mutually exclusive for a client application. A public/native client application may only request a response type
835         of "token". A private/trusted client application may only request a response type of "code".
836        :param state: Allows for arbitrary state that may be useful to your application. The value in this parameter
837        will be round-tripped along with the response so you can make sure it didn't change.
838        :return: string
839        """
840        if not self.oauth_host_name:
841            self.oauth_host_name = self.get_oauth_host_name()
842        scopes = " ".join(scopes) if scopes else ""
843        uri = "https://{}/oauth/auth?response_type={}&scope={}&client_id={}&redirect_uri={}"
844        if state:
845            uri += "&state={}"
846        return uri.format(self.oauth_host_name, response_type, quote(scopes), client_id, redirect_uri, state)
847
848    def get_oauth_host_name(self):
849        """
850        :return: string
851        """
852        if not self.oauth_host_name:
853            self.set_oauth_host_name()
854
855        return self.oauth_host_name

Generic API client for Swagger client library builds.

Swagger generic API client. This client handles the client- server communication, and is invariant across implementations. Specifics of the methods and models for each application are generated from the Swagger templates.

NOTE: This class is auto generated by the swagger code generator program. Ref: https://github.com/swagger-api/swagger-codegen Do not edit the class manually.

Parameters
  • host: The base path for the server to call.
  • header_name: a header to pass when making calls to the API.
  • header_value: a header value to pass when making calls to the API.
ApiClient( host=None, header_name=None, header_value=None, cookie=None, oauth_host_name=None, base_path=None)
70    def __init__(self, host=None, header_name=None, header_value=None, cookie=None, oauth_host_name=None,
71                 base_path=None):
72        """
73        Constructor of the class.
74        """
75        self.rest_client = RESTClientObject()
76        self.default_headers = {'X-DocuSign-SDK': 'Python'}
77
78        if header_name is not None:
79            self.default_headers[header_name] = header_value
80
81        if host is None:
82            self.host = Configuration().host
83        elif host == "":
84            raise ArgumentException("basePath cannot be empty")
85        else:
86            self.host = host
87
88        self.cookie = cookie
89        self.oauth_host_name = oauth_host_name
90        self.base_path = base_path
91        # Set default User-Agent.
92        self.user_agent = Configuration().user_agent

Constructor of the class.

PRIMITIVE_TYPES = (<class 'float'>, <class 'bool'>, <class 'bytes'>, <class 'str'>, <class 'int'>)
NATIVE_TYPES_MAPPING = {'int': <class 'int'>, 'long': <class 'int'>, 'float': <class 'float'>, 'str': <class 'str'>, 'bool': <class 'bool'>, 'date': <class 'datetime.date'>, 'datetime': <class 'datetime.datetime'>, 'object': <class 'object'>}
OAUTH_TYPES = ('OAuthToken', 'OAuthUserInfo', 'Account', 'Organization', 'Link')
user_agent

Gets user agent.

def set_default_header(self, header_name, header_value)
108    def set_default_header(self, header_name, header_value):
109        self.default_headers[header_name] = header_value
def sanitize_for_serialization(self, obj)
201    def sanitize_for_serialization(self, obj):
202        """
203        Builds a JSON POST object.
204
205        If obj is None, return None.
206        If obj is str, int, long, float, bool, return directly.
207        If obj is datetime.datetime, datetime.date
208            convert to string in iso8601 format.
209        If obj is list, sanitize each element in the list.
210        If obj is dict, return the dict.
211        If obj is swagger model, return the properties dict.
212
213        :param obj: The data to serialize.
214        :return: The serialized form of data.
215        """
216        if obj is None:
217            return None
218        elif isinstance(obj, self.PRIMITIVE_TYPES):
219            return obj
220        elif isinstance(obj, list):
221            return [self.sanitize_for_serialization(sub_obj)
222                    for sub_obj in obj]
223        elif isinstance(obj, tuple):
224            return tuple(self.sanitize_for_serialization(sub_obj)
225                         for sub_obj in obj)
226        elif isinstance(obj, (datetime, date)):
227            return obj.isoformat()
228
229        if isinstance(obj, dict):
230            obj_dict = obj
231        else:
232            # Convert model obj to dict except
233            # attributes `swagger_types`, `attribute_map`
234            # and attributes which value is not None.
235            # Convert attribute name to json key in
236            # model definition for request.
237            obj_dict = {obj.attribute_map[attr]: getattr(obj, attr)
238                        for attr, _ in iteritems(obj.swagger_types)
239                        if getattr(obj, attr) is not None}
240
241        return {key: self.sanitize_for_serialization(val)
242                for key, val in iteritems(obj_dict)}

Builds a JSON POST object.

If obj is None, return None. If obj is str, int, long, float, bool, return directly. If obj is datetime.datetime, datetime.date convert to string in iso8601 format. If obj is list, sanitize each element in the list. If obj is dict, return the dict. If obj is swagger model, return the properties dict.

Parameters
  • obj: The data to serialize.
Returns

The serialized form of data.

def deserialize(self, response, response_type)
244    def deserialize(self, response, response_type):
245        """
246        Deserializes response into an object.
247
248        :param response: RESTResponse object to be deserialized.
249        :param response_type: class literal for
250            deserialized object, or string of class name.
251
252        :return: deserialized object.
253        """
254        # handle file downloading
255        # save response body into a tmp file and return the instance
256        if response_type == "file":
257            return self.__deserialize_file(response)
258
259        # fetch data from response object
260        try:
261            data = json.loads(response.data)
262        except ValueError:
263            data = response.data
264
265        return self.__deserialize(data, response_type)

Deserializes response into an object.

Parameters
  • response: RESTResponse object to be deserialized.
  • response_type: class literal for deserialized object, or string of class name.
Returns

deserialized object.

def call_api( self, resource_path, method, path_params=None, query_params=None, header_params=None, body=None, post_params=None, files=None, response_type=None, auth_settings=None, callback=None, _return_http_data_only=None, collection_formats=None, _preload_content=True, _request_timeout=None)
310    def call_api(self, resource_path, method,
311                 path_params=None, query_params=None, header_params=None,
312                 body=None, post_params=None, files=None,
313                 response_type=None, auth_settings=None, callback=None,
314                 _return_http_data_only=None, collection_formats=None, _preload_content=True,
315                 _request_timeout=None):
316        """
317        Makes the HTTP request (synchronous) and return the deserialized data.
318        To make an async request, define a function for callback.
319
320        :param resource_path: Path to method endpoint.
321        :param method: Method to call.
322        :param path_params: Path parameters in the url.
323        :param query_params: Query parameters in the url.
324        :param header_params: Header parameters to be
325            placed in the request header.
326        :param body: Request body.
327        :param post_params dict: Request post form parameters,
328            for `application/x-www-form-urlencoded`, `multipart/form-data`.
329        :param auth_settings list: Auth Settings names for the request.
330        :param response: Response data type.
331        :param files dict: key -> filename, value -> filepath,
332            for `multipart/form-data`.
333        :param callback function: Callback function for asynchronous request.
334            If provide this parameter,
335            the request will be called asynchronously.
336        :param _return_http_data_only: response data without head status code and headers
337        :param collection_formats: dict of collection formats for path, query,
338            header, and post parameters.
339        :param _preload_content: if False, the urllib3.HTTPResponse object will be returned without
340                                 reading/decoding response data. Default is True.
341        :param _request_timeout: timeout setting for this request. If one number provided, it will be total request
342                                 timeout. It can also be a pair (tuple) of (connection, read) timeouts.
343        :return:
344            If provide parameter callback,
345            the request will be called asynchronously.
346            The method will return the request thread.
347            If parameter callback is None,
348            then the method will return the response directly.
349        """
350        if callback is None:
351            return self.__call_api(resource_path, method,
352                                   path_params, query_params, header_params,
353                                   body, post_params, files,
354                                   response_type, auth_settings, callback,
355                                   _return_http_data_only, collection_formats, _preload_content, _request_timeout)
356        else:
357            thread = threading.Thread(target=self.__call_api,
358                                      args=(resource_path, method,
359                                            path_params, query_params,
360                                            header_params, body,
361                                            post_params, files,
362                                            response_type, auth_settings,
363                                            callback, _return_http_data_only,
364                                            collection_formats, _preload_content, _request_timeout))
365        thread.start()
366        return thread

Makes the HTTP request (synchronous) and return the deserialized data. To make an async request, define a function for callback.

Parameters
  • resource_path: Path to method endpoint.
  • method: Method to call.
  • path_params: Path parameters in the url.
  • query_params: Query parameters in the url.
  • header_params: Header parameters to be placed in the request header.
  • body: Request body.
  • post_params dict: Request post form parameters, for application/x-www-form-urlencoded, multipart/form-data.
  • auth_settings list: Auth Settings names for the request.
  • response: Response data type.
  • files dict: key -> filename, value -> filepath, for multipart/form-data.
  • callback function: Callback function for asynchronous request. If provide this parameter, the request will be called asynchronously.
  • _return_http_data_only: response data without head status code and headers
  • collection_formats: dict of collection formats for path, query, header, and post parameters.
  • _preload_content: if False, the urllib3.HTTPResponse object will be returned without reading/decoding response data. Default is True.
  • _request_timeout: timeout setting for this request. If one number provided, it will be total request timeout. It can also be a pair (tuple) of (connection, read) timeouts.
Returns
If provide parameter callback,
the request will be called asynchronously.
The method will return the request thread.
If parameter callback is None,
then the method will return the response directly.
def request( self, method, url, query_params=None, headers=None, post_params=None, body=None, _preload_content=True, _request_timeout=None)
368    def request(self, method, url, query_params=None, headers=None,
369                post_params=None, body=None, _preload_content=True, _request_timeout=None):
370        """
371        Makes the HTTP request using RESTClient.
372        """
373        if method == "GET":
374            return self.rest_client.GET(url,
375                                        query_params=query_params,
376                                        _preload_content=_preload_content,
377                                        _request_timeout=_request_timeout,
378                                        headers=headers)
379        elif method == "HEAD":
380            return self.rest_client.HEAD(url,
381                                         query_params=query_params,
382                                         _preload_content=_preload_content,
383                                         _request_timeout=_request_timeout,
384                                         headers=headers)
385        elif method == "OPTIONS":
386            return self.rest_client.OPTIONS(url,
387                                            query_params=query_params,
388                                            headers=headers,
389                                            post_params=post_params,
390                                            _preload_content=_preload_content,
391                                            _request_timeout=_request_timeout,
392                                            body=body)
393        elif method == "POST":
394            return self.rest_client.POST(url,
395                                         query_params=query_params,
396                                         headers=headers,
397                                         post_params=post_params,
398                                         _preload_content=_preload_content,
399                                         _request_timeout=_request_timeout,
400                                         body=body)
401        elif method == "PUT":
402            return self.rest_client.PUT(url,
403                                        query_params=query_params,
404                                        headers=headers,
405                                        post_params=post_params,
406                                        _preload_content=_preload_content,
407                                        _request_timeout=_request_timeout,
408                                        body=body)
409        elif method == "PATCH":
410            return self.rest_client.PATCH(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 == "DELETE":
418            return self.rest_client.DELETE(url,
419                                           query_params=query_params,
420                                           headers=headers,
421                                           _preload_content=_preload_content,
422                                           _request_timeout=_request_timeout,
423                                           body=body)
424        else:
425            raise ValueError(
426                "http method must be `GET`, `HEAD`, `OPTIONS`,"
427                " `POST`, `PATCH`, `PUT` or `DELETE`."
428            )

Makes the HTTP request using RESTClient.

def parameters_to_tuples(self, params, collection_formats)
430    def parameters_to_tuples(self, params, collection_formats):
431        """
432        Get parameters as list of tuples, formatting collections.
433
434        :param params: Parameters as dict or list of two-tuples
435        :param dict collection_formats: Parameter collection formats
436        :return: Parameters as list of tuples, collections formatted
437        """
438        new_params = []
439        if collection_formats is None:
440            collection_formats = {}
441        for k, v in iteritems(params) if isinstance(params, dict) else params:
442            if k in collection_formats:
443                collection_format = collection_formats[k]
444                if collection_format == 'multi':
445                    new_params.extend((k, value) for value in v)
446                else:
447                    if collection_format == 'ssv':
448                        delimiter = ' '
449                    elif collection_format == 'tsv':
450                        delimiter = '\t'
451                    elif collection_format == 'pipes':
452                        delimiter = '|'
453                    else:  # csv is the default
454                        delimiter = ','
455                    new_params.append(
456                        (k, delimiter.join(str(value) for value in v)))
457            else:
458                new_params.append((k, v))
459        return new_params

Get parameters as list of tuples, formatting collections.

Parameters
  • params: Parameters as dict or list of two-tuples
  • dict collection_formats: Parameter collection formats
Returns

Parameters as list of tuples, collections formatted

def prepare_post_parameters(self, post_params=None, files=None)
461    def prepare_post_parameters(self, post_params=None, files=None):
462        """
463        Builds form parameters.
464
465        :param post_params: Normal form parameters.
466        :param files: File parameters.
467        :return: Form parameters with files.
468        """
469        params = []
470
471        if post_params:
472            params = post_params
473
474        if files:
475            for k, v in iteritems(files):
476                if not v:
477                    continue
478                file_names = v if type(v) is list else [v]
479                for n in file_names:
480                    with open(n, 'rb') as f:
481                        filename = os.path.basename(f.name)
482                        filedata = f.read()
483                        mimetype = mimetypes. \
484                                       guess_type(filename)[0] or 'application/octet-stream'
485                        params.append(tuple([k, tuple([filename, filedata, mimetype])]))
486
487        return params

Builds form parameters.

Parameters
  • post_params: Normal form parameters.
  • files: File parameters.
Returns

Form parameters with files.

def select_header_accept(self, accepts)
489    def select_header_accept(self, accepts):
490        """
491        Returns `Accept` based on an array of accepts provided.
492
493        :param accepts: List of headers.
494        :return: Accept (e.g. application/json).
495        """
496        if not accepts:
497            return
498
499        accepts = [x.lower() for x in accepts]
500
501        if 'application/json' in accepts:
502            return 'application/json'
503        else:
504            return ', '.join(accepts)

Returns Accept based on an array of accepts provided.

Parameters
  • accepts: List of headers.
Returns

Accept (e.g. application/json).

def select_header_content_type(self, content_types)
506    def select_header_content_type(self, content_types):
507        """
508        Returns `Content-Type` based on an array of content_types provided.
509
510        :param content_types: List of content-types.
511        :return: Content-Type (e.g. application/json).
512        """
513        if not content_types:
514            return 'application/json'
515
516        content_types = [x.lower() for x in content_types]
517
518        if 'application/json' in content_types or '*/*' in content_types:
519            return 'application/json'
520        else:
521            return content_types[0]

Returns Content-Type based on an array of content_types provided.

Parameters
  • content_types: List of content-types.
Returns

Content-Type (e.g. application/json).

def update_params_for_auth(self, headers, querys, auth_settings)
523    def update_params_for_auth(self, headers, querys, auth_settings):
524        """
525        Updates header and query params based on authentication setting.
526
527        :param headers: Header parameters dict to be updated.
528        :param querys: Query parameters tuple list to be updated.
529        :param auth_settings: Authentication setting identifiers list.
530        """
531        config = Configuration()
532
533        if not auth_settings:
534            return
535
536        for auth in auth_settings:
537            auth_setting = config.auth_settings().get(auth)
538            if auth_setting:
539                if not auth_setting['value']:
540                    continue
541                elif auth_setting['in'] == 'header':
542                    headers[auth_setting['key']] = auth_setting['value']
543                elif auth_setting['in'] == 'query':
544                    querys.append((auth_setting['key'], auth_setting['value']))
545                else:
546                    raise ValueError(
547                        'Authentication token must be in `query` or `header`'
548                    )

Updates header and query params based on authentication setting.

Parameters
  • headers: Header parameters dict to be updated.
  • querys: Query parameters tuple list to be updated.
  • auth_settings: Authentication setting identifiers list.
def request_jwt_user_token( self, client_id, user_id, oauth_host_name, private_key_bytes, expires_in, scopes=('signature',))
666    def request_jwt_user_token(self, client_id, user_id, oauth_host_name, private_key_bytes, expires_in,
667                               scopes=(OAuth.SCOPE_SIGNATURE,)):
668        """
669        Request JWT User Token
670        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
671        :param user_id: DocuSign user Id to be impersonated
672        :param oauth_host_name: DocuSign OAuth host name
673        :param private_key_bytes: the byte contents of the RSA private key
674        :param expires_in: number of seconds remaining before the JWT assertion is considered as invalid
675        :param scopes: Optional. The list of requested scopes may include (but not limited to) You can also pass any
676        advanced scope.
677        :return: OAuthToken object
678        """
679        if not private_key_bytes:
680            raise ArgumentException("Private key not supplied or is invalid!")
681        if not user_id:
682            raise ArgumentException("User Id not supplied or is invalid!")
683        if not oauth_host_name:
684            raise ArgumentException("oAuthBasePath cannot be empty")
685
686        now = math.floor(time())
687        later = now + (expires_in * 1)
688        claim = {"iss": client_id, "sub": user_id, "aud": oauth_host_name, "iat": now, "exp": later,
689                 "scope": " ".join(scopes)}
690        token = jwt.encode(payload=claim, key=private_key_bytes, algorithm='RS256')
691        response = self.request("POST", "https://" + oauth_host_name + "/oauth/token",
692                                headers=self.sanitize_for_serialization(
693                                    {"Content-Type": "application/x-www-form-urlencoded"}),
694                                post_params=self.sanitize_for_serialization(
695                                    {"assertion": token, "grant_type": OAuth.GRANT_TYPE_JWT}))
696
697        response_data = json.loads(response.data)
698
699        if 'token_type' in response_data and 'access_token' in response_data:
700            self.set_default_header("Authorization", response_data['token_type'] + " " + response_data['access_token'])
701        else:
702            raise ApiException(status=response.status,
703                               reason="Error while requesting server, received a non successful HTTP code {}"
704                                       " with response Body: {}".format(response.status, response.data)
705                               )
706
707        return self.deserialize(response=response, response_type=OAuthToken)

Request JWT User Token

Parameters
  • client_id: DocuSign OAuth Client Id(AKA Integrator Key)
  • user_id: DocuSign user Id to be impersonated
  • oauth_host_name: DocuSign OAuth host name
  • private_key_bytes: the byte contents of the RSA private key
  • expires_in: number of seconds remaining before the JWT assertion is considered as invalid
  • scopes: Optional. The list of requested scopes may include (but not limited to) You can also pass any advanced scope.
Returns

OAuthToken object

def request_jwt_application_token( self, client_id, oauth_host_name, private_key_bytes, expires_in, scopes=('signature',))
709    def request_jwt_application_token(self, client_id, oauth_host_name, private_key_bytes, expires_in,
710                                      scopes=(OAuth.SCOPE_SIGNATURE,)):
711        """
712        Request JWT Application Token
713        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
714        :param oauth_host_name: DocuSign OAuth host name
715        :param private_key_bytes: the byte contents of the RSA private key
716        :param expires_in: number of seconds remaining before the JWT assertion is considered as invalid
717        :param scopes: Optional. The list of requested scopes may include (but not limited to) You can also pass any
718        advanced scope.
719        :return: OAuthToken object
720        """
721
722        if not private_key_bytes:
723            raise ArgumentException("Private key not supplied or is invalid!")
724        if not oauth_host_name:
725            raise ArgumentException("oAuthBasePath cannot be empty")
726
727        now = math.floor(time())
728        later = now + (expires_in * 1)
729        claim = {"iss": client_id, "aud": oauth_host_name, "iat": now, "exp": later,
730                 "scope": " ".join(scopes)}
731        token = jwt.encode(payload=claim, key=private_key_bytes, algorithm='RS256')
732
733        response = self.request("POST", "https://" + oauth_host_name + "/oauth/token",
734                                headers=self.sanitize_for_serialization(
735                                    {"Content-Type": "application/x-www-form-urlencoded"}),
736                                post_params=self.sanitize_for_serialization(
737                                    {"assertion": token, "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer"}))
738        response_data = json.loads(response.data)
739
740        if 'token_type' in response_data and 'access_token' in response_data:
741            self.set_default_header("Authorization", response_data['token_type'] + " " + response_data['access_token'])
742        else:
743            ApiException(status=response.status,
744                         reason="Error while requesting server, received a non successful HTTP code {}"
745                                " with response Body: {}".format(response.status, response.data)
746                         )
747
748        return self.deserialize(response=response, response_type=OAuthToken)

Request JWT Application Token

Parameters
  • client_id: DocuSign OAuth Client Id(AKA Integrator Key)
  • oauth_host_name: DocuSign OAuth host name
  • private_key_bytes: the byte contents of the RSA private key
  • expires_in: number of seconds remaining before the JWT assertion is considered as invalid
  • scopes: Optional. The list of requested scopes may include (but not limited to) You can also pass any advanced scope.
Returns

OAuthToken object

def get_user_info(self, access_token)
750    def get_user_info(self, access_token):
751        """
752        Get User Info method takes the accessToken to retrieve User Account Data.
753        :param access_token:
754        :return: The User Info model.
755        """
756        if not access_token:
757            raise ArgumentException("Cannot find a valid access token."
758                                    " Make sure OAuth is configured before you try again.")
759        if not self.oauth_host_name:
760            raise ArgumentException("oAuthBasePath cannot be empty")
761
762        resource_path = '/oauth/userinfo'
763        headers = {"Authorization": "Bearer " + access_token}
764
765        response = self.request("GET", "https://" + self.oauth_host_name + resource_path, headers=headers)
766        return self.deserialize(response=response, response_type=OAuthUserInfo)

Get User Info method takes the accessToken to retrieve User Account Data.

Parameters
  • access_token:
Returns

The User Info model.

def generate_access_token(self, client_id, client_secret, code)
768    def generate_access_token(self, client_id, client_secret, code):
769        """
770        GenerateAccessToken will exchange the authorization code for an access token and refresh tokens.
771        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
772        :param client_secret: The secret key you generated when you set up the integration in DocuSign Admin console.
773        :param code: The authorization code
774        :return: OAuthToken object
775        """
776        if not client_id or not client_secret or not code:
777            raise ArgumentException
778        url = "https://{0}/oauth/token".format(self.oauth_host_name)
779        integrator_and_secret_key = b"Basic " + base64.b64encode(str.encode("{}:{}".format(client_id, client_secret)))
780        headers = {
781            "Authorization": integrator_and_secret_key.decode("utf-8"),
782            "Content-Type": "application/x-www-form-urlencoded",
783        }
784        post_params = self.sanitize_for_serialization({
785            "grant_type": "authorization_code",
786            "code": code
787        })
788        response = self.rest_client.POST(url, headers=headers, post_params=post_params)
789
790        return self.deserialize(response=response, response_type=OAuthToken)

GenerateAccessToken will exchange the authorization code for an access token and refresh tokens.

Parameters
  • client_id: DocuSign OAuth Client Id(AKA Integrator Key)
  • client_secret: The secret key you generated when you set up the integration in DocuSign Admin console.
  • code: The authorization code
Returns

OAuthToken object

def set_base_path(self, base_path)
792    def set_base_path(self, base_path):
793        """
794        :param base_path:
795        :return:
796        """
797        self.base_path = base_path
Parameters
  • base_path:
Returns
def set_oauth_host_name(self, oauth_host_name=None)
799    def set_oauth_host_name(self, oauth_host_name=None):
800        """
801        :param oauth_host_name:
802        :return:
803        """
804        if oauth_host_name:
805            self.oauth_host_name = oauth_host_name
806            return
807
808        if not oauth_host_name:
809            raise ArgumentException('oAuthBasePath cannot be empty')
810
811        # Derive OAuth Base Path if not given
812        if self.base_path.startswith("https://demo") or self.base_path.startswith("http://demo"):
813            self.oauth_host_name = OAuth.DEMO_OAUTH_BASE_PATH
814        elif self.base_path.startswith("https://stage") or self.base_path.startswith("http://stage"):
815            self.oauth_host_name = OAuth.STAGE_OAUTH_BASE_PATH
816        else:
817            self.oauth_host_name = OAuth.PRODUCTION_OAUTH_BASE_PATH
Parameters
  • oauth_host_name:
Returns
def set_access_token(self, token_obj)
819    def set_access_token(self, token_obj):
820        """
821
822        :param token_obj: 
823        :return: 
824        """
825        self.default_headers['Authorization'] = token_obj.access_token
Parameters
  • token_obj:
Returns
def get_authorization_uri(self, client_id, scopes, redirect_uri, response_type, state=None)
827    def get_authorization_uri(self, client_id, scopes, redirect_uri, response_type, state=None):
828        """
829        Helper method to configure the OAuth accessCode/implicit flow parameters
830        :param client_id: DocuSign OAuth Client Id(AKA Integrator Key)
831        :param scopes: The list of requested scopes.  Client applications may be scoped to a limited set of system access.
832        :param redirect_uri: This determines where to deliver the response containing the authorization code
833        :param response_type: Determines the response type of the authorization request, NOTE: these response types are
834        mutually exclusive for a client application. A public/native client application may only request a response type
835         of "token". A private/trusted client application may only request a response type of "code".
836        :param state: Allows for arbitrary state that may be useful to your application. The value in this parameter
837        will be round-tripped along with the response so you can make sure it didn't change.
838        :return: string
839        """
840        if not self.oauth_host_name:
841            self.oauth_host_name = self.get_oauth_host_name()
842        scopes = " ".join(scopes) if scopes else ""
843        uri = "https://{}/oauth/auth?response_type={}&scope={}&client_id={}&redirect_uri={}"
844        if state:
845            uri += "&state={}"
846        return uri.format(self.oauth_host_name, response_type, quote(scopes), client_id, redirect_uri, state)

Helper method to configure the OAuth accessCode/implicit flow parameters

Parameters
  • client_id: DocuSign OAuth Client Id(AKA Integrator Key)
  • scopes: The list of requested scopes. Client applications may be scoped to a limited set of system access.
  • redirect_uri: This determines where to deliver the response containing the authorization code
  • response_type: Determines the response type of the authorization request, NOTE: these response types are mutually exclusive for a client application. A public/native client application may only request a response type of "token". A private/trusted client application may only request a response type of "code".
  • state: Allows for arbitrary state that may be useful to your application. The value in this parameter will be round-tripped along with the response so you can make sure it didn't change.
Returns

string

def get_oauth_host_name(self)
848    def get_oauth_host_name(self):
849        """
850        :return: string
851        """
852        if not self.oauth_host_name:
853            self.set_oauth_host_name()
854
855        return self.oauth_host_name
Returns

string