def matches(self, scope: Scope) -> typing.Tuple[Match, Scope]: if scope["type"] == "websocket": match = self.path_regex.match(scope["path"]) if match: matched_params = match.groupdict() for key, value in matched_params.items(): matched_params[key] = self.param_convertors[key].convert( value) path_params = dict(scope.get("path_params", {})) path_params.update(matched_params) child_scope = { "endpoint": self.endpoint, "path_params": path_params } return Match.FULL, child_scope return Match.NONE, {}
def matches(self, scope: Scope) -> typing.Tuple[Match, Scope]: if scope["type"] in ("http", "websocket"): headers = Headers(scope=scope) host = headers.get("host", "").split(":")[0] match = self.host_regex.match(host) if match: matched_params = match.groupdict() for key, value in matched_params.items(): matched_params[key] = self.param_convertors[key].convert( value) path_params = dict(scope.get("path_params", {})) path_params.update(matched_params) child_scope = { "path_params": path_params, "endpoint": self.app } return Match.FULL, child_scope return Match.NONE, {}
def _get_router_path(scope: Scope) -> Optional[str]: """Returns the original router path (with url param names) for given request.""" if not (scope.get("endpoint", None) and scope.get("router", None)): return None base_scope = { "type": scope.get("type"), "path": scope.get("root_path", "") + scope.get("path"), "path_params": scope.get("path_params", {}), "method": scope.get("method"), } try: path = get_matching_route_path(base_scope, scope.get("router").routes) return path except: # unhandled path pass return None
async def _app(scope: Scope, receive: Receive, send: Send) -> None: app = scope["app"] route, route_scope = app.router.get_route_from_scope(scope) state = { "scope": scope, "receive": receive, "send": send, "exc": None, "app": app, "path_params": route_scope["path_params"], "route": route, "websocket": websockets.WebSocket(scope, receive, send), } injected_func = await app.injector.inject(endpoint, state) kwargs = scope.get("kwargs", {}) await injected_func(**kwargs)
async def lifespan(self, scope: Scope, receive: Receive, send: Send) -> None: """ Handle ASGI lifespan messages, which allows us to manage application startup and shutdown events. """ started = False app = scope.get("app") await receive() try: async with self.lifespan_context(app): await send({"type": "lifespan.startup.complete"}) started = True await receive() except BaseException: exc_text = traceback.format_exc() if started: await send({"type": "lifespan.shutdown.failed", "message": exc_text}) else: await send({"type": "lifespan.startup.failed", "message": exc_text}) raise else: await send({"type": "lifespan.shutdown.complete"})
async def app(self, scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] in ("http", "websocket"): path = scope["path"] root_path = scope.get("root_path", "") # Call into a submounted app, if one exists. for path_prefix, app in self.mount_apps.items(): if not path.startswith(path_prefix + "/"): continue if isinstance(app, WSGIMiddleware): if scope["type"] != "http": continue subscope = copy.deepcopy(scope) subscope["path"] = path[len(path_prefix):] subscope["root_path"] = root_path + path_prefix await app(subscope, receive, send) return await self.indexfile(scope, receive, send) elif scope["type"] == "lifespan": await self.lifespan(scope, receive, send)
def __init__(self, url: str = "", scope: Scope = None) -> None: if scope is not None: assert not url, 'Cannot set both "url" and "scope".' scheme = scope["scheme"] host, port = scope["server"] path = scope.get("root_path", "") + scope["path"] query_string = scope["query_string"] default_port = { "http": 80, "https": 443, "ws": 80, "wss": 443 }[scheme] if port == default_port: url = "%s://%s%s" % (scheme, host, path) else: url = "%s://%s:%s%s" % (scheme, host, port, path) if query_string: url += "?" + unquote(query_string.decode()) self._url = url
async def __call__(self, scope: Scope, receive: Receive, send: Send): self._set_cookies_to_headers() if self.streaming: self._starlette_resp = StreamingResponse self._body = self.streaming elif self._json is not None: self._starlette_resp = JSONResponse self._body = self.json self.headers["content-type"] = "application/json" elif self._redirect_to is not None: url, qs = self._redirect_to if qs is None: qs = scope.get("query_string", b"").decode("ascii") if qs: qs = f"?{qs}" self._body = url + qs app = self._starlette_resp(self._body, status_code=self.status_code, headers=self.headers) return await app(scope, receive, send)
def test_request_client(scope: Scope, expected_client: Optional[Address]): scope.update({"type": "http"}) # required by Request's constructor client = Request(scope).client assert client == expected_client
def prepare_fhir_scopes( scope: Scope, headers: Headers, params: QueryParams, errors: typing.List[typing.Any], ): # Generate Request ID scope["FHIR_REQUEST_ID"] = uuid.uuid4() # 1. Prepare Accept & FHIR Version # -------------------------------- accept = headers.get("accept", None) if accept: parts = accept.split(";") accept = parts[0].strip() if accept in ("application/json", "text/json"): accept = "application/fhir+json" if accept in ALLOWED_ACCEPTS: scope["FHIR_RESPONSE_ACCEPT"] = accept else: errors.append({ "loc": "Header.Accept", "msg": f"Accept mime '{accept}' is not supported.", "original": headers.get("accept"), }) if len(parts) > 1: version_str = None try: name, version_str = parts[1].strip().split("=") if name == "fhirVersion": version = MIME_FHIR_VERSION_MAP[version_str] scope["FHIR_VERSION"] = version scope["FHIR_VERSION_ORIGINAL"] = version_str else: errors.append({ "loc": "Header.Accept", "msg": "Invalid format of FHIR Version is provided in mime", "original": headers.get("accept"), }) except KeyError: errors.append({ "loc": "Header.Accept", "msg": f"Unsupported FHIR Version '{version_str}' is provided in mime", "original": headers.get("accept"), }) except ValueError: errors.append({ "loc": "Header.Accept", "msg": "Invalid format of FHIR Version is provided in mime", "original": headers.get("accept"), }) else: scope["FHIR_RESPONSE_ACCEPT"] = "application/fhir+json" if (scope.get("FHIR_VERSION_ORIGINAL", None) is None and scope.get("FHIR_VERSION", None) is None): scope["FHIR_VERSION"] = MIME_FHIR_VERSION_MAP[DEFAULT_FHIR_VERSION] # 2. Check Query String # --------------------- format_mime = params.get("_format", None) if format_mime is not None: if format_mime in ALLOWED_ACCEPTS: scope["FHIR_RESPONSE_FORMAT"] = format_mime else: errors.append({ "loc": "QueryString._format", "msg": f"Format mime '{format_mime}' is not supported.", "original": format_mime, }) pretty_response = params.get("_pretty", None) if pretty_response is not None: if pretty_response in ("true", "false"): scope["FHIR_RESPONSE_PRETTY"] = pretty_response == "true" else: errors.append({ "loc": "QueryString._pretty", "msg": f"Invalid ``_pretty`` value '{pretty_response}' is provided.", "original": pretty_response, }) # 3. Prepare Conditional Headers # ------------------------------ if headers.get("If-None-Exist"): scope["FHIR_CONDITION_NONE_EXIST"] = [ tuple( map(lambda x: x.strip(), headers.get("If-None-Exist").split("="))) ] if headers.get("If-Modified-Since"): try: scope["FHIR_CONDITION_MODIFIED_SINCE"] = parsedate_to_datetime( headers.get("If-Modified-Since")) except ValueError: errors.append({ "loc": "Header.If-Modified-Since", "msg": "Invalid formatted datetime value is provided.", "original": headers.get("If-Modified-Since"), }) if headers.get("If-None-Match"): try: scope["FHIR_CONDITION_NONE_MATCH"] = literal_eval( headers.get("If-None-Match").replace("W/", "")) except (SyntaxError, ValueError): errors.append({ "loc": "Header.If-None-Match", "msg": "Invalid formatted ETag value is provided.", "original": headers.get("If-None-Match"), }) if headers.get("If-Match"): try: scope["FHIR_CONDITION_MATCH"] = literal_eval( headers.get("If-Match").replace("W/", "")) except (SyntaxError, ValueError): errors.append({ "loc": "Header.If-Match", "msg": "Invalid formatted ETag value is provided.", "original": headers.get("If-Match"), })
async def _dispatch(self, scope: Scope, receive: Receive, send: Send) -> None: # check request type if scope["type"] == "lifespan": while True: message = await receive() if message["type"] == "lifespan.startup": try: await self._startup() except Exception as e: await send({ "type": "lifespan.startup.failed", "message": str(e) }) else: await send({"type": "lifespan.startup.complete"}) elif message["type"] == "lifespan.shutdown": try: await self._shutdown() except Exception as e: await send({ "type": "lifespan.shutdown.failed", "message": str(e) }) else: await send({"type": "lifespan.shutdown.complete"}) return # check `/favicon.ico`. if self.favicon and scope["path"] == "/favicon.ico": scope["path"] = self.favicon # check mounted app. normalized = normalize_path(scope["path"]) for prefix, app in self.mounted_app.items(): if normalized.startswith(prefix): scope["path"] = scope["path"].replace(prefix[:-1], "", 1) root = scope.get("root_path", "") + prefix scope["root_path"] = root.replace("//", "/") if scope["type"] == "http": model = { "req": models.http.Request(scope, receive, send), "resp": models.http.Response(), } elif scope["type"] == "websocket": model = { "conn": models.websocket.Connection(scope, receive, send) } else: raise TypeError(f"`{scope['type']}` is not supported.") scope["extensions"] = scope.get("extensions", {}) scope["extensions"].update({"spangle": dict(**model)}) await app(scope, receive, send) return # check spangle views. if scope["type"] == "http": app = await dispatch_http(scope, receive, send) t = asyncio.create_task(app(scope, receive, send)) elif scope["type"] == "websocket": app = await dispatch_websocket(scope, receive, send) t = asyncio.create_task(app(scope, receive, send)) else: raise ValueError("Invalid scheme.") await t