from enum import Enum from typing import Callable, Sequence, Text from boltons.iterutils import remap from jsonmodels import models from jsonmodels.errors import FieldNotSupported from schema import schema from .apicall import APICall from .base import PartialVersion from .schema_validator import SchemaValidator EndpointFunc = Callable[[APICall, Text, models.Base], None] class Endpoint(object): _endpoint_config_cache = {} """ Endpoints configuration cache, in the format of {full endpoint name: dict} """ def __init__( self, name: Text, func: EndpointFunc, min_version: Text = "1.0", required_fields: Sequence[Text] = None, request_data_model: models.Base = None, response_data_model: models.Base = None, validate_schema: bool = False, ): """ Endpoint configuration :param name: full endpoint name :param func: endpoint implementation :param min_version: minimum supported version :param required_fields: required request fields, can not be used with validate_schema :param request_data_model: request jsonschema model, will be validated if validate_schema=False :param response_data_model: response jsonschema model, will be validated if validate_schema=False :param validate_schema: whether request and response schema should be validated """ super(Endpoint, self).__init__() self.name = name self.min_version = PartialVersion(min_version) self.func = func self.required_fields = required_fields self.request_data_model = request_data_model self.response_data_model = response_data_model service, _, endpoint_name = self.name.partition(".") try: self.endpoint_group = schema.services[service].endpoint_groups[ endpoint_name ] except KeyError: raise RuntimeError( f"schema for endpoint {service}.{endpoint_name} not found" ) if validate_schema: if self.required_fields: raise ValueError( f"endpoint {self.name}: can not use 'required_fields' with 'validate_schema'" ) endpoint = self.endpoint_group.get_for_version(self.min_version) request_schema = endpoint.request_schema response_schema = endpoint.response_schema else: request_schema = None response_schema = None self.request_schema_validator = SchemaValidator(request_schema) self.response_schema_validator = SchemaValidator(response_schema) def __repr__(self): return f"{type(self).__name__}<{self.name}>" def to_dict(self): """ Used by `server.endpoints` endpoint. Provided endpoints and their schemas on a best-effort basis. """ d = { "min_version": str(self.min_version), "required_fields": self.required_fields, "request_data_model": None, "response_data_model": None, } def safe_to_json_schema(data_model: models.Base): """ Provided data_model schema if available """ try: res = data_model.to_json_schema() def visit(path, key, value): if isinstance(value, Enum): value = str(value) return key, value return remap(res, visit=visit) except (FieldNotSupported, TypeError): return str(data_model.__name__) if self.request_data_model: d["request_data_model"] = safe_to_json_schema(self.request_data_model) if self.response_data_model: d["response_data_model"] = safe_to_json_schema(self.response_data_model) if self.request_schema_validator.enabled: d["request_schema"] = self.request_schema_validator.schema if self.response_schema_validator.enabled: d["response_schema"] = self.response_schema_validator.schema return d @property def authorize(self): return self.endpoint_group.authorize @property def allow_roles(self): return self.endpoint_group.allow_roles def allows(self, role): return self.endpoint_group.allows(role) @property def is_internal(self): return self.endpoint_group.internal