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
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)
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
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)
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
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