def decorator(fn): location = get_callsite_location() # this will be the lineno of the last decorator, so we want one # below it for the actual function location['lineno'] += 1 # convert older style version strings if introduced_at == '1.0': self.introduced_at = 1 elif introduced_at is not None: self.introduced_at = int(introduced_at) self.register_view(fn, location, introduced_at) # support for legacy @validate_{body,output} decorators # we don't know the order of decorators, so allow for both. # Note that if these schemas come from the @validate decorators, # they are already validated, so we set directly. fn._acceptable_metadata = self if self._request_schema is None: self._request_schema = getattr(fn, '_request_schema', None) self._request_schema_location = getattr( fn, '_request_schema_location', None) if self._response_schema is None: self._response_schema = getattr(fn, '_response_schema', None) self._response_schema_location = getattr( fn, '_response_schema_location', None) return fn
def __init__(self, service, name, url, introduced_at, options={}, location=None, undocumented=False, deprecated_at=None, title=None): self.service = service self.name = name self.url = url self.introduced_at = introduced_at self.options = options self.view_fn = None self.view_fn_location = None self.docs = None self._request_schema = None self._request_schema_location = None self._response_schema = None self._response_schema_location = None self._params_schema = None self._params_schema_location = None self._changelog = OrderedDict() self._changelog_locations = OrderedDict() if location is None: self.location = get_callsite_location() else: self.location = location self.undocumented = undocumented self.deprecated_at = deprecated_at self.title = title
def django_api(self, name, introduced_at, undocumented=False, deprecated_at=None, title=None, **options): """Add a django API handler to the service. :param name: This is the name of the django url to use. The 'methods' paramater can be supplied as normal, you can also user the @api.handler decorator to link this API to its handler. """ from acceptable.djangoutil import DjangoAPI location = get_callsite_location() api = DjangoAPI( self, name, introduced_at, options, location=location, undocumented=undocumented, deprecated_at=deprecated_at, title=title, ) self.metadata.register_api(self.name, self.group, api) return api
def api(self, url, name, introduced_at=None, undocumented=False, deprecated_at=None, title=None, **options): """Add an API to the service. :param url: This is the url that the API should be registered at. :param name: This is the name of the api, and will be registered with flask apps under. Other keyword arguments may be used, and they will be passed to the flask application when initialised. Of particular interest is the 'methods' keyword argument, which can be used to specify the HTTP method the URL will be added for. """ location = get_callsite_location() api = AcceptableAPI( self, name, url, introduced_at, options, undocumented=undocumented, deprecated_at=deprecated_at, title=title, location=location, ) self.metadata.register_api(self.name, self.group, api) return api
def validate_body(schema): """Validate the body of incoming requests for a flask view. An example usage might look like this:: from snapstore_schemas import validate_body @validate_body({ 'type': 'array', 'items': { 'type': 'object', 'properties': { 'snap_id': {'type': 'string'}, 'series': {'type': 'string'}, 'name': {'type': 'string'}, 'title': {'type': 'string'}, 'keywords': { 'type': 'array', 'items': {'type': 'string'} }, 'summary': {'type': 'string'}, 'description': {'type': 'string'}, 'created_at': {'type': 'string'}, }, 'required': ['snap_id', 'series'], 'additionalProperties': False } }) def my_flask_view(): # view code here return "Hello World", 200 All incoming request that have been routed to this view will be matched against the specified schema. If the request body does not match the schema an instance of `DataValidationError` will be raised. By default this will cause the flask application to return a 500 response, but this can be customised by telling flask how to handle these exceptions. The exception instance has an 'error_list' attribute that contains a list of all the errors encountered while processing the request body. """ location = get_callsite_location() def decorator(fn): validate_schema(schema) wrapper = wrap_request(fn, schema) record_schemas(fn, wrapper, location, request_schema=sort_schema(schema)) return wrapper return decorator
def __call__(self, fn): wrapped = fn if self.response_schema: wrapped = _validation.wrap_response(wrapped, self.response_schema) if self.request_schema: wrapped = _validation.wrap_request(wrapped, self.request_schema) location = get_callsite_location() # this will be the lineno of the last decorator, so we want one # below it for the actual function location['lineno'] += 1 self.register_view(wrapped, location) return wrapped
def __init__(self, name, group=None, metadata=None): """Create an instance of AcceptableService. :param name: The service name. :param group: An arbitrary API group within a service. """ self.name = name self.group = group if metadata is None: self.metadata = get_metadata() else: self.metadata = metadata self.location = get_callsite_location() self.metadata.register_service(name, group)
def __init__(self, name, group=None, metadata=Metadata): """Create an instance of AcceptableService. :param name: The service name. :param group: An arbitrary API group within a service. :raises TypeError: If the name string is something other than a string. """ if not isinstance(name, str): raise TypeError("name must be a string, not %s" % type(name).__name__) self.name = name self.group = group self.metadata = metadata self.metadata.register_service(name, group) self.location = get_callsite_location()
def __init__(self, name, group=None, title=None, metadata=None): """Create an instance of AcceptableService. :param name: The service name. :param group: An arbitrary API group within a service. """ self.name = name self.group = group if metadata is None: self.metadata = get_metadata() else: self.metadata = metadata self.location = get_callsite_location() self.doc = None module = self.location['module'] docs = None if module and module.__doc__: docs = clean_docstring(module.__doc__) self.metadata.register_service(name, group, docs, title)
def validate_params(schema): """Validate the request parameters. The request parameters (request.args) are validated against the schema. The root of the schema should be an object and each of its properties is a parameter. An example usage might look like this:: from snapstore_schemas import validate_params @validate_params({ "type": "object", "properties": { "id": { "type": "string", "description": "A test property.", "pattern": "[0-9A-F]{8}", } }, required: ["id"] }) def my_flask_view(): ... """ location = get_callsite_location() def decorator(fn): validate_schema(schema) wrapper = wrap_request_params(fn, schema) record_schemas(fn, wrapper, location, params_schema=sort_schema(schema)) return wrapper return decorator
def validate_output(schema): """Validate the body of a response from a flask view. Like `validate_body`, this function compares a json document to a jsonschema specification. However, this function applies the schema to the view response. Instead of the view returning a flask response object, it should instead return a Python list or dictionary. For example:: from snapstore_schemas import validate_output @validate_output({ 'type': 'object', 'properties': { 'ok': {'type': 'boolean'}, }, 'required': ['ok'], 'additionalProperties': False } def my_flask_view(): # view code here return {'ok': True} Every view response will be evaluated against the schema. Any that do not comply with the schema will cause DataValidationError to be raised. """ location = get_callsite_location() def decorator(fn): validate_schema(schema) wrapper = wrap_response(fn, schema) record_schemas(fn, wrapper, location, response_schema=sort_schema(schema)) return wrapper return decorator
def __init__(self, name, url, introduced_at, options={}, location=None, undocumented=False): self.name = name self.url = url self.introduced_at = introduced_at self.options = options self.view_fn = None self.docs = None self._request_schema = None self._request_schema_location = None self._response_schema = None self._response_schema_location = None self._changelog = OrderedDict() self._changelog_locations = OrderedDict() if location is None: self.location = get_callsite_location() else: self.location = location self.undocumented = undocumented
def changelog(self, api_version, doc): """Add a changelog entry for this api.""" doc = textwrap.dedent(doc).strip() self._changelog[api_version] = doc self._changelog_locations[api_version] = get_callsite_location()
def response_schema(self, schema): if schema is not None: _validation.validate_schema(schema) self._response_schema = schema # this location is the last item in the dict, sadly self._response_schema_location = get_callsite_location()
def params_schema(self, schema): if schema is not None: _validation.validate_schema(schema) self._params_schema = sort_schema(schema) self._params_schema_location = get_callsite_location()