def parse_path(self, route): from starlette.routing import compile_path _, path, variables = compile_path(route.path) parameters = [] for name, conv in variables.items(): schema = None typ = self.conv2type[conv] if typ == 'int': schema = {'type': 'integer', 'format': 'int32'} elif typ == 'float': schema = { 'type': 'number', 'format': 'float', } elif typ == 'path': schema = { 'type': 'string', 'format': 'path', } elif typ == 'str': schema = {'type': 'string'} parameters.append({ 'name': name, 'in': 'path', 'required': True, 'schema': schema, }) return path, parameters
def parse_path(self, route): from starlette.routing import compile_path _, path, variables = compile_path(route.path) parameters = [] for name, conv in variables.items(): schema = None typ = self.conv2type[conv] if typ == "int": schema = {"type": "integer", "format": "int32"} elif typ == "float": schema = { "type": "number", "format": "float", } elif typ == "path": schema = { "type": "string", "format": "path", } elif typ == "str": schema = {"type": "string"} parameters.append({ "name": name, "in": "path", "required": True, "schema": schema, }) return path, parameters
def wrap(func): func_name = func.__name__ # This check fails in Cythonized apps. # Related: # - https://bugs.python.org/issue38225 # - https://github.com/cython/cython/issues/2273 # if not asyncio.iscoroutinefunction(func): # raise ValueError(f"@on function '{func_name}' must be async") if predicate: if not callable(predicate): raise ValueError(f"@on predicate must be callable for '{func_name}'") if isinstance(arg, str) and len(arg): if arg.startswith('#'): # location hash rx, _, conv = compile_path(arg[1:]) _path_handlers.append((rx, conv, func, _get_arity(func))) elif '.' in arg: # event source, event = arg.split('.', 1) if not len(source): raise ValueError(f"@on event source cannot be empty in '{arg}' for '{func_name}'") if not len(event): raise ValueError(f"@on event type cannot be empty in '{arg}' for '{func_name}'") _add_event_handler(source, event, func, predicate) else: _add_handler(arg, func, predicate) else: _add_handler(func_name, func, predicate) logger.debug(f'Registered event handler for {func_name}') return func
def _get_param_tags(starlette_route: Route, scope: dict) -> Dict[str, str]: tags = {} # parse query string query_string = scope.get('query_string') if query_string: try: qs_parsed = parse_qs(query_string.decode()) for query_key, query_value in qs_parsed.items(): tags['data.%s' % query_key] = query_value.pop() except Exception as err: logging.exception(err) # parse route params request_path = scope.get('path') path_regex, path_format, param_convertors = compile_path( starlette_route.path ) res = path_regex.match(request_path) if res: res_dict = res.groupdict() for param_key in param_convertors.keys(): param_value = res_dict.get(param_key) if param_value is not None: tags['data.%s' % param_key] = param_value return tags
def __init__(self, path: str, endpoint: Callable, *, name: str = None) -> None: self.path = path self.endpoint = endpoint self.name = get_name(endpoint) if name is None else name self.dependant = get_dependant(path=path, call=self.endpoint) self.app = websocket_session(get_websocket_app(dependant=self.dependant)) regex = "^" + path + "$" regex = re.sub("{([a-zA-Z_][a-zA-Z0-9_]*)}", r"(?P<\1>[^/]+)", regex) self.path_regex, self.path_format, self.param_convertors = compile_path(path)
def get_endpoints( self, routes: typing.List[routing.BaseRoute], base_path: str = "" ) -> typing.Dict[str, typing.Sequence[EndpointInfo]]: """ Given the routes, yields the following information: - path eg: /users/ - http_method one of 'get', 'post', 'put', 'patch', 'delete', 'options' - func method ready to extract the docstring """ endpoints_info: typing.Dict[str, typing.Sequence[EndpointInfo]] = defaultdict(list) for route in routes: _, path, _ = routing.compile_path(base_path + route.path) if isinstance(route, routing.Route) and route.include_in_schema: if inspect.isfunction(route.endpoint) or inspect.ismethod(route.endpoint): for method in route.methods or ["GET"]: if method == "HEAD": continue endpoints_info[path].append( EndpointInfo( path=path, method=method.lower(), func=route.endpoint, query_fields=route.query_fields.get(method, {}), path_fields=route.path_fields.get(method, {}), body_field=route.body_field.get(method), output_field=route.output_field.get(method), ) ) else: for method in ["get", "post", "put", "patch", "delete", "options"]: if not hasattr(route.endpoint, method): continue func = getattr(route.endpoint, method) endpoints_info[path].append( EndpointInfo( path=path, method=method.lower(), func=func, query_fields=route.query_fields.get(method.upper(), {}), path_fields=route.path_fields.get(method.upper(), {}), body_field=route.body_field.get(method.upper()), output_field=route.output_field.get(method.upper()), ) ) elif isinstance(route, routing.Mount): endpoints_info.update(self.get_endpoints(route.routes, base_path=path)) return endpoints_info
def get_path_params_from_request(request: Request) -> str: path_params = {} for r in api_router.routes: path_regex, path_format, param_converters = compile_path(r.path) # remove the /api/v1 for matching path = f"/{request['path'].strip('/api/v1')}" match = path_regex.match(path) if match: path_params = match.groupdict() return path_params
def wrap(func): func_name = func.__name__ if not asyncio.iscoroutinefunction(func): raise ValueError(f'@on function {func_name} must be async') if isinstance(arg, str) and len(arg): if arg.startswith('#'): rx, _, conv = compile_path(arg[1:]) _path_handlers.append((rx, conv, func)) else: _arg_handlers[arg] = func else: _arg_handlers[func_name] = func logger.debug(f'Registered event handler {func_name}') return func
def wrap(func): func_name = func.__name__ if not asyncio.iscoroutinefunction(func): raise ValueError(f'@on function {func_name} must be async') if predicate: if not callable(predicate): raise ValueError('@on predicate must be callable') if isinstance(arg, str) and len(arg): if arg.startswith('#'): rx, _, conv = compile_path(arg[1:]) _path_handlers.append((rx, conv, func)) else: _add_handler(arg, func, predicate) else: _add_handler(func_name, func, predicate) logger.debug(f'Registered event handler {func_name}') return func
def __init__(self, path: str, endpoint: Callable, *, name: str = None, dependency_overrides_provider: Any = None) -> None: super().__init__(path, endpoint, name=name) self.path = path self.endpoint = endpoint self.name = get_name(endpoint) if name is None else name self.dependant = get_dependant(path=path, call=self.endpoint) self.app = websocket_session( get_websocket_app( dependant=self.dependant, dependency_overrides_provider=dependency_overrides_provider, )) self.path_regex, self.path_format, self.param_convertors = compile_path( path)
def __init__(self, path: str, func_to_run: typing.Callable, last=False, **kwargs): assert path.startswith("/"), "Routed paths must start with '/'" self.path = path self.func_to_run = func_to_run self.name = kwargs.get('name', None) self.path_regex, self.path_format, self.param_convertors = compile_path( path) self.id = Route.id Route.id += 1 if last: Route.instances.append(self) else: Route.instances.insert(0, self)
def __init__( self, path: str, endpoint: Callable, *, response_model: Type[Any] = None, status_code: int = 200, tags: List[str] = None, dependencies: Sequence[params.Depends] = None, summary: str = None, description: str = None, response_description: str = "Successful Response", responses: Dict[Union[int, str], Dict[str, Any]] = None, deprecated: bool = None, name: str = None, methods: Optional[Union[Set[str], List[str]]] = None, operation_id: str = None, response_model_include: Union[SetIntStr, DictIntStrAny] = None, response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(), response_model_by_alias: bool = True, response_model_exclude_unset: bool = False, include_in_schema: bool = True, response_class: Optional[Type[Response]] = None, dependency_overrides_provider: Any = None, callbacks: Optional[List["APIRoute"]] = None, ) -> None: self.path = path self.endpoint = endpoint self.name = get_name(endpoint) if name is None else name self.path_regex, self.path_format, self.param_convertors = compile_path(path) if methods is None: methods = ["GET"] self.methods = set([method.upper() for method in methods]) self.unique_id = generate_operation_id_for_path( name=self.name, path=self.path_format, method=list(methods)[0] ) self.response_model = response_model if self.response_model: assert ( status_code not in STATUS_CODES_WITH_NO_BODY ), f"Status code {status_code} must not have a response body" response_name = "Response_" + self.unique_id self.response_field = create_response_field( name=response_name, type_=self.response_model ) # Create a clone of the field, so that a Pydantic submodel is not returned # as is just because it's an instance of a subclass of a more limited class # e.g. UserInDB (containing hashed_password) could be a subclass of User # that doesn't have the hashed_password. But because it's a subclass, it # would pass the validation and be returned as is. # By being a new field, no inheritance will be passed as is. A new model # will be always created. self.secure_cloned_response_field: Optional[ ModelField ] = create_cloned_field(self.response_field) else: self.response_field = None # type: ignore self.secure_cloned_response_field = None self.status_code = status_code self.tags = tags or [] if dependencies: self.dependencies = list(dependencies) else: self.dependencies = [] self.summary = summary self.description = description or inspect.cleandoc(self.endpoint.__doc__ or "") # if a "form feed" character (page break) is found in the description text, # truncate description text to the content preceding the first "form feed" self.description = self.description.split("\f")[0] self.response_description = response_description self.responses = responses or {} response_fields = {} for additional_status_code, response in self.responses.items(): assert isinstance(response, dict), "An additional response must be a dict" model = response.get("model") if model: assert ( additional_status_code not in STATUS_CODES_WITH_NO_BODY ), f"Status code {additional_status_code} must not have a response body" response_name = f"Response_{additional_status_code}_{self.unique_id}" response_field = create_response_field(name=response_name, type_=model) response_fields[additional_status_code] = response_field if response_fields: self.response_fields: Dict[Union[int, str], ModelField] = response_fields else: self.response_fields = {} self.deprecated = deprecated self.operation_id = operation_id self.response_model_include = response_model_include self.response_model_exclude = response_model_exclude self.response_model_by_alias = response_model_by_alias self.response_model_exclude_unset = response_model_exclude_unset self.include_in_schema = include_in_schema self.response_class = response_class assert callable(endpoint), f"An endpoint must be a callable" self.dependant = get_dependant(path=self.path_format, call=self.endpoint) for depends in self.dependencies[::-1]: self.dependant.dependencies.insert( 0, get_parameterless_sub_dependant(depends=depends, path=self.path_format), ) self.body_field = get_body_field(dependant=self.dependant, name=self.unique_id) self.dependency_overrides_provider = dependency_overrides_provider self.callbacks = callbacks self.app = request_response(self.get_route_handler())
def __init__( self, path: str, endpoint: Callable, *, response_model: Type[BaseModel] = None, status_code: int = 200, tags: List[str] = None, summary: str = None, description: str = None, response_description: str = "Successful Response", responses: Dict[Union[int, str], Dict[str, Any]] = None, deprecated: bool = None, name: str = None, methods: List[str] = None, operation_id: str = None, include_in_schema: bool = True, response_class: Type[Response] = JSONResponse, ) -> None: assert path.startswith("/"), "Routed paths must always start with '/'" self.path = path self.endpoint = endpoint self.name = get_name(endpoint) if name is None else name self.response_model = response_model if self.response_model: assert lenient_issubclass( response_class, JSONResponse ), "To declare a type the response must be a JSON response" response_name = "Response_" + self.name self.response_field: Optional[Field] = Field( name=response_name, type_=self.response_model, class_validators={}, default=None, required=False, model_config=BaseConfig, schema=Schema(None), ) else: self.response_field = None self.status_code = status_code self.tags = tags or [] self.summary = summary self.description = description or inspect.cleandoc( self.endpoint.__doc__ or "") self.response_description = response_description self.responses = responses or {} response_fields = {} for additional_status_code, response in self.responses.items(): assert isinstance(response, dict), "An additional response must be a dict" model = response.get("model") if model: assert lenient_issubclass( model, BaseModel), "A response model must be a Pydantic model" response_name = f"Response_{additional_status_code}_{self.name}" response_field = Field( name=response_name, type_=model, class_validators=None, default=None, required=False, model_config=BaseConfig, schema=Schema(None), ) response_fields[additional_status_code] = response_field if response_fields: self.response_fields: Dict[Union[int, str], Field] = response_fields else: self.response_fields = {} self.deprecated = deprecated if methods is None: methods = ["GET"] self.methods = methods self.operation_id = operation_id self.include_in_schema = include_in_schema self.response_class = response_class self.path_regex, self.path_format, self.param_convertors = compile_path( path) assert inspect.isfunction(endpoint) or inspect.ismethod( endpoint), f"An endpoint must be a function or method" self.dependant = get_dependant(path=path, call=self.endpoint) self.body_field = get_body_field(dependant=self.dependant, name=self.name) self.app = request_response( get_app( dependant=self.dependant, body_field=self.body_field, status_code=self.status_code, response_class=self.response_class, response_field=self.response_field, ))
def __init__( self, entrypoint: 'Entrypoint', path: str, func: Union[FunctionType, CoroutineType], *, result_model: Type[Any] = None, name: str = None, errors: Sequence[Type[BaseError]] = None, dependencies: Sequence[Depends] = None, response_class: Type[Response] = JSONResponse, **kwargs, ): name = name or func.__name__ result_model = result_model or func.__annotations__.get('return') _, path_format, _ = compile_path(path) func_dependant = get_dependant(path=path_format, call=func) insert_dependencies(func_dependant, dependencies) insert_dependencies(func_dependant, entrypoint.common_dependencies) fix_query_dependencies(func_dependant) flat_dependant = get_flat_dependant(func_dependant, skip_repeats=True) _Request = make_request_model(name, func.__module__, flat_dependant.body_params) @component_name(f'_Response[{name}]', func.__module__) class _Response(BaseModel): jsonrpc: StrictStr = Field('2.0', const=True, example='2.0') id: Union[StrictStr, int] = Field(None, example=0) result: result_model class Config: extra = 'forbid' # Only needed to generate OpenAPI async def endpoint(__request__: _Request): del __request__ endpoint.__name__ = func.__name__ endpoint.__doc__ = func.__doc__ responses = errors_responses(errors) super().__init__( path, endpoint, methods=['POST'], name=name, response_class=response_class, response_model=_Response, response_model_exclude_unset=True, responses=responses, **kwargs, ) # Add dependencies and other parameters from func_dependant for correct OpenAPI generation self.dependant.path_params = func_dependant.path_params self.dependant.header_params = func_dependant.header_params self.dependant.cookie_params = func_dependant.cookie_params self.dependant.dependencies = func_dependant.dependencies self.dependant.security_requirements = func_dependant.security_requirements self.func = func self.func_dependant = func_dependant self.entrypoint = entrypoint self.app = request_response(self.handle_http_request)
def __init__( self, entrypoint: 'Entrypoint', path: str, *, name: str = None, errors: Sequence[Type[BaseError]] = None, common_dependencies: Sequence[Depends] = None, response_class: Type[Response] = JSONResponse, **kwargs, ): name = name or 'entrypoint' _, path_format, _ = compile_path(path) _Request = JsonRpcRequest common_dependant = Dependant(path=path_format) if common_dependencies: insert_dependencies(common_dependant, common_dependencies) fix_query_dependencies(common_dependant) common_dependant = get_flat_dependant(common_dependant, skip_repeats=True) if common_dependant.body_params: _Request = make_request_model(name, entrypoint.callee_module, common_dependant.body_params) # This is only necessary for generating OpenAPI def endpoint(__request__: _Request): del __request__ responses = errors_responses(errors) super().__init__( path, endpoint, methods=['POST'], name=name, response_class=response_class, response_model=JsonRpcResponse, responses=responses, **kwargs, ) flat_dependant = get_flat_dependant(self.dependant, skip_repeats=True) if len(flat_dependant.body_params) > 1: body_params = [p for p in flat_dependant.body_params if p.type_ is not _Request] raise RuntimeError( f"Entrypoint shared dependencies can't use 'Body' parameters: " f"params={body_params}" ) if flat_dependant.query_params: raise RuntimeError( f"Entrypoint shared dependencies can't use 'Query' parameters: " f"params={flat_dependant.query_params}" ) self.shared_dependant = clone_dependant(self.dependant) # No shared 'Body' params, because each JSON-RPC request in batch has own body self.shared_dependant.body_params = [] # Add dependencies and other parameters from common_dependant for correct OpenAPI generation self.dependant.path_params.extend(common_dependant.path_params) self.dependant.header_params.extend(common_dependant.header_params) self.dependant.cookie_params.extend(common_dependant.cookie_params) self.dependant.dependencies.extend(common_dependant.dependencies) self.dependant.security_requirements.extend(common_dependant.security_requirements) self.app = request_response(self.handle_http_request) self.entrypoint = entrypoint self.common_dependencies = common_dependencies
def __init__( self, path: str, endpoint: Callable, *, response_model: Type[Any] = None, status_code: int = 200, tags: List[str] = None, dependencies: Sequence[params.Depends] = None, summary: str = None, description: str = None, response_description: str = "Successful Response", responses: Dict[Union[int, str], Dict[str, Any]] = None, deprecated: bool = None, name: str = None, methods: Optional[Union[Set[str], List[str]]] = None, operation_id: str = None, response_model_include: Set[str] = None, response_model_exclude: Set[str] = set(), response_model_by_alias: bool = True, response_model_skip_defaults: bool = False, include_in_schema: bool = True, response_class: Type[Response] = JSONResponse, dependency_overrides_provider: Any = None, ) -> None: assert path.startswith("/"), "Routed paths must always start with '/'" self.path = path self.endpoint = endpoint self.name = get_name(endpoint) if name is None else name self.path_regex, self.path_format, self.param_convertors = compile_path( path) if methods is None: methods = ["GET"] self.methods = set([method.upper() for method in methods]) self.unique_id = generate_operation_id_for_path( name=self.name, path=self.path_format, method=list(methods)[0]) self.response_model = response_model if self.response_model: assert lenient_issubclass( response_class, JSONResponse ), "To declare a type the response must be a JSON response" response_name = "Response_" + self.unique_id self.response_field: Optional[Field] = Field( name=response_name, type_=self.response_model, class_validators={}, default=None, required=False, model_config=BaseConfig, schema=Schema(None), ) # Create a clone of the field, so that a Pydantic submodel is not returned # as is just because it's an instance of a subclass of a more limited class # e.g. UserInDB (containing hashed_password) could be a subclass of User # that doesn't have the hashed_password. But because it's a subclass, it # would pass the validation and be returned as is. # By being a new field, no inheritance will be passed as is. A new model # will be always created. self.secure_cloned_response_field: Optional[ Field] = create_cloned_field(self.response_field) else: self.response_field = None self.secure_cloned_response_field = None self.status_code = status_code self.tags = tags or [] if dependencies: self.dependencies = list(dependencies) else: self.dependencies = [] self.summary = summary self.description = description or inspect.cleandoc( self.endpoint.__doc__ or "") self.response_description = response_description self.responses = responses or {} response_fields = {} for additional_status_code, response in self.responses.items(): assert isinstance(response, dict), "An additional response must be a dict" model = response.get("model") if model: assert lenient_issubclass( model, BaseModel), "A response model must be a Pydantic model" response_name = f"Response_{additional_status_code}_{self.unique_id}" response_field = Field( name=response_name, type_=model, class_validators=None, default=None, required=False, model_config=BaseConfig, schema=Schema(None), ) response_fields[additional_status_code] = response_field if response_fields: self.response_fields: Dict[Union[int, str], Field] = response_fields else: self.response_fields = {} self.deprecated = deprecated self.operation_id = operation_id self.response_model_include = response_model_include self.response_model_exclude = response_model_exclude self.response_model_by_alias = response_model_by_alias self.response_model_skip_defaults = response_model_skip_defaults self.include_in_schema = include_in_schema self.response_class = response_class assert inspect.isfunction(endpoint) or inspect.ismethod( endpoint), f"An endpoint must be a function or method" self.dependant = get_dependant(path=self.path_format, call=self.endpoint) for depends in self.dependencies[::-1]: self.dependant.dependencies.insert( 0, get_parameterless_sub_dependant(depends=depends, path=self.path_format), ) self.body_field = get_body_field(dependant=self.dependant, name=self.unique_id) self.dependency_overrides_provider = dependency_overrides_provider self.app = request_response( get_app( dependant=self.dependant, body_field=self.body_field, status_code=self.status_code, response_class=self.response_class, response_field=self.secure_cloned_response_field, response_model_include=self.response_model_include, response_model_exclude=self.response_model_exclude, response_model_by_alias=self.response_model_by_alias, response_model_skip_defaults=self.response_model_skip_defaults, dependency_overrides_provider=self. dependency_overrides_provider, ))
def __init__( self, path: str, endpoint: Callable, *, response_model: Type[BaseModel] = None, status_code: int = 200, tags: List[str] = None, summary: str = None, description: str = None, response_description: str = "Successful Response", deprecated: bool = None, name: str = None, methods: List[str] = None, operation_id: str = None, include_in_schema: bool = True, content_type: Type[Response] = JSONResponse, ) -> None: assert path.startswith("/"), "Routed paths must always start with '/'" self.path = path self.endpoint = endpoint self.name = get_name(endpoint) if name is None else name self.response_model = response_model if self.response_model: assert lenient_issubclass( content_type, JSONResponse ), "To declare a type the response must be a JSON response" response_name = "Response_" + self.name self.response_field: Optional[Field] = Field( name=response_name, type_=self.response_model, class_validators=[], default=None, required=False, model_config=UnconstrainedConfig, schema=Schema(None), ) else: self.response_field = None self.status_code = status_code self.tags = tags or [] self.summary = summary self.description = description or self.endpoint.__doc__ self.response_description = response_description self.deprecated = deprecated if methods is None: methods = ["GET"] self.methods = methods self.operation_id = operation_id self.include_in_schema = include_in_schema self.content_type = content_type self.path_regex, self.path_format, self.param_convertors = compile_path(path) assert inspect.isfunction(endpoint) or inspect.ismethod( endpoint ), f"An endpoint must be a function or method" self.dependant = get_dependant(path=path, call=self.endpoint) self.body_field = get_body_field(dependant=self.dependant, name=self.name) self.app = request_response( get_app( dependant=self.dependant, body_field=self.body_field, status_code=self.status_code, content_type=self.content_type, response_field=self.response_field, ) )