예제 #1
0
def load_api(data):
    schemas = data['components']['schemas']
    objects = dict()

    for name, schema in schemas.items():
        if 'allOf' in schema:
            model = {}
            for item in schema['allOf']:
                if '$ref' in item:
                    ref = item['$ref'].split('/')[-1]
                    if 'base' not in model:
                        model['base'] = list()
                    model['base'].append(ref)

                elif 'properties' in item:
                    model['properties'] = item['properties']

            schema.update(model)

            del schema['allOf']

        if 'properties' in schema:
            model = {}
            mapping = {}

            for key, value in schema['properties'].items():
                new_key = to_snake_case(key)
                model[new_key] = value
                mapping[new_key] = key

            schema['properties'] = model
            schema['mapping'] = mapping

            if 'required' in schema:
                schema['required'] = [
                    to_snake_case(v) for v in schema['required']
                ]

            objects[name] = Model(**schema)

        elif 'enum' in schema:
            objects[name] = Enum(**schema)

        schemas[name] = schema
    return objects
예제 #2
0
def make(session):
    apispec = get_api(session)
    for uri, properties in apispec['paths'].items():
        for method, attrs in properties.items():
            if 'operationId' in attrs:
                name = to_snake_case(attrs['operationId'])
                func = partial(request, session, method, uri)
                func.__doc__ = attrs.get('summary')
                func.__name__ = name
                log.debug('adding function {}'.format(name))
                setattr(session, name, func)
예제 #3
0
def send_request(session,
                 url,
                 method='GET',
                 status_codes=None,
                 variables=None,
                 query=None,
                 **kwargs):
    """Sends a request to the Pureport REST API

    This function will invoke a HTTP method when calling the
    Pureport API and return the response.  Any kwargs provided
    will be converted to JSON and included as the body of the
    request

    :param method: The HTTP method to call
    :type method: string

    :param url: The HTTP realitive URL to invoke
    :type url: string

    :param kwargs: Unordered key/value pair to be included in
        request as a JSON payload
    :type kwargs: dict

    :returns: The HTTP response from the server
    :rtype: :class:`pureport.transport.Response`
    """
    status_codes = to_list(status_codes) or (200, )

    body = json.dumps(kwargs.get('body', {})) if kwargs else None

    # convert any url variables to snake case
    # for instance:
    #   /accounts/{accountId} becomes /accounts/{account_id}
    #
    snake_case = lambda m: "{{{}}}".format(to_snake_case(m.group(1)))
    url = re.sub(r"\{(\S+)\}", snake_case, url)
    url = url.format(**(variables or {}))

    log.debug("calling session with url {}".format(url))
    response = session(method, url, body=body, query=query)

    if response.status not in status_codes:
        log.debug("invalid status code: got {}".format(response.status))
        log.debug("   expected: {}".format(','.join(
            [str(s) for s in status_codes])))
        raise PureportError("invalid status code: {}".format(response.status))

    assert hasattr(response, 'json'), "missing required attribute `json`"

    return response.json
예제 #4
0
def load(clsname, data):
    """Serialize data to an instance of Model

    :param clsname: the name of the model to create
    :type clsname: str

    :param data: key value data to load
    :type data: dict

    :returns: an instance of Model
    :rtype: `pureport.models.Model`
    """
    kwargs = {}

    model = globals().get(clsname)
    schema = model._schema
    properties = model._schema.properties

    data = dict([(to_snake_case(key), value) for key, value in data.items()])

    if schema.discriminator:
        value = data.get(schema.discriminator['propertyName'])
        if value:
            clsname = schema.discriminator['mapping'][value].split('/')[-1]
            schema = globals().get(clsname)._schema
            properties.update(schema.properties)

    for key, value in schema.parents.items():
        properties.update(value.properties)

    for key, value in properties.items():
        if data.get(key) is not None:
            if '$ref' in value:
                ref = value['$ref'].split('/')[-1]
                obj = globals().get(ref)._schema

                if isinstance(obj, Enum):
                    kwargs[key] = data[key]
                else:
                    kwargs[key] = load(ref, data[key])
            else:
                kwargs[key] = data[key]

    return globals().get(clsname)(**kwargs)
예제 #5
0
def request(session, method, uri, *args, **kwargs):
    """Send a request to the URI and return the response

    The generic form of the function signature is as follows:

    .. code-block:: python

        args = [o['name'] for o in parameters if o['in'] == 'path']
        function(*args, model=None, query=None)


    If accountId is in the list of args and the value is not supplied then
    the function will automatically insert the discovered account_id
    for the session.

    :param method: the http method to call
    :type method: str

    :param uri: the relative uri to call
    :type: uri: str
    """
    api = get_api(session)

    method = method.lower()
    path = api['paths'][uri].get(method)

    if path is None:
        raise PureportError("method {} not supported for uri {}".format(
            method, uri))

    parameters = list()
    query = {}

    for item in path.get('parameters', []):
        if item.get('in',
                    'path') == 'path' and item.get('required', True) is True:
            parameters.append(to_snake_case(item['name']))
        elif item.get('in') == 'query':
            query[to_snake_case(item['name'])] = None

    cls = None

    ref = get_value('requestBody.content.application/json.schema.$ref', path)
    if ref:
        clsname = ref.split('/')[-1]
        schema = getattr(models, clsname)._schema

        if schema.discriminator:
            propval = getattr(kwargs['model'],
                              schema.discriminator['propertyName'])
            clsname = schema.discriminator['mapping'].get(propval).split(
                '/')[-1]

        cls = getattr(models, clsname, None)
        log.debug("connection class is {}".format(cls))
        parameters.append('model')

    query_values = kwargs.pop('query', None)
    if query_values:
        # TODO need to validate query inputs against api spec
        if not set(query_values).issubset(query):
            raise PureportError("unknown query value provided")

    variables = dict(zip(parameters, args))

    for item in parameters:
        if item not in variables:
            variables[item] = kwargs.pop(to_snake_case(item), None)

    model = variables.get('model')

    body = None

    if cls and isinstance(model, cls):
        body = models.dump(model)

    if kwargs:
        raise PureportError("unexpected keyword arguments")

    if set(args).issubset(variables.values()) is False:
        raise PureportError("unexpected positional arguments")

    for p in parameters:
        if variables.get(p) is None:
            # inject the session accountId automatically into the variables
            # if it is the only parameter that doesn't have a supplied value.
            if p == 'account_id':
                log.debug("automatically injecting account_id argument")
                variables['account_id'] = session.account_id
            else:
                import q
                q.d()
                raise PureportError("missing required argument: {}".format(p))

    func = globals().get(method)
    data = func(session,
                uri,
                body=body,
                variables=variables,
                query=query_values)

    schema = get_value('responses.default.content.application/json.schema',
                       path)
    if schema:
        if '$ref' in schema:
            clsname = schema['$ref'].split('/')[-1]
        elif schema.get('type') == 'array' and 'items' in schema:
            clsname = schema['items']['$ref'].split('/')[-1]

        if isinstance(data, list):
            data = [models.load(clsname, item) for item in data]
        else:
            data = models.load(clsname, data)

    return data
예제 #6
0
def _set_property(self, value, name):
    val = None

    this = self._schema.properties.get(name)

    if this is None and self._schema.base:
        for item in self._schema.base:
            this = self._schema.parents[item].properties.get(name)
            if this:
                break
        else:
            raise KeyError(name)

    if this is None:
        raise KeyError(name)

    if '$ref' in this:
        ref = this['$ref'].split('/')[-1]
        schema = globals().get(ref)._schema

        if isinstance(schema, Model):
            if schema.discriminator:
                prop = getattr(value, schema.discriminator['propertyName'])
                ref = schema.discriminator['mapping'][prop].split('/')[-1]

            clstype = globals().get(ref)

            if value is not None and not isinstance(value, clstype):
                raise TypeError("invalid object type")

            val = value

        elif isinstance(schema, Enum):
            if value is not None:
                value = value.upper() if schema.type == 'string' else value
                if value not in schema.values:
                    raise ValueError("invalid enum value")
                val = value

        else:
            val = value

    else:
        value = this.get('default') if value is None else value

        if value is not None:
            if this.get('type') == 'string':
                val = str(value)

                maxlen = this.get('maxLength') or sys.maxsize
                minlen = this.get('minLength') or 0

                if minlen > len(val) > maxlen:
                    raise ValueError("invalid length")

            elif this.get('type') == 'boolean':
                if not isinstance(value, bool):
                    raise ValueError("property must be of type <bool>")
                val = bool(value)

            elif this.get('type') == 'array':
                if not isinstance(value, (list, set, tuple, dict)):
                    raise ValueError("invalid value for type <array>")

                if this.get('uniqueItems') is True:
                    if value and isinstance(value[0], dict):
                        val = [
                            dict(o) for o in set(
                                frozenset(item.items()) for item in value)
                        ]
                        val = [
                            dict([(to_snake_case(k), v)
                                  for k, v in val.items()])
                        ]
                    else:
                        val = list(set(value))
                else:
                    val = list(value)

                if this.get('items'):
                    if '$ref' in this['items']:
                        ref = this['items']['$ref'].split('/')[-1]
                        if globals().get(ref).properties:
                            val = [
                                type(ref, (Base, ), {})(**item)
                                for item in list(val)
                            ]

            elif this.get('type') == 'object':
                if not isinstance(value, dict):
                    raise ValueError("value must be of type <dict>")
                val = value

            else:
                val = value

    self.__dict__[name] = val