def guess_target_format(request): # type: (Request) -> Tuple[Str, bool] """ Guess the best applicable response ``Content-Type`` header according to request ``Accept`` header and ``format`` query, or defaulting to :py:data:`CONTENT_TYPE_JSON`. :returns: tuple of matched MIME-type and where it was found (``True``: header, ``False``: query) """ content_type = FORMAT_TYPE_MAPPING.get(request.params.get("format")) is_header = False if not content_type: is_header = True content_type = get_header("accept", request.headers, default=CONTENT_TYPE_JSON, split=";,") if content_type != CONTENT_TYPE_JSON: # because most browsers enforce some 'visual' list of accept header, revert to JSON if detected # explicit request set by other client (e.g.: using 'requests') will have full control over desired content user_agent = get_header("user-agent", request.headers) if user_agent and any( browser in user_agent for browser in ["Mozilla", "Chrome", "Safari"]): content_type = CONTENT_TYPE_JSON if not content_type or content_type == CONTENT_TYPE_ANY: is_header = True content_type = CONTENT_TYPE_JSON return content_type, is_header
def unauthorized_or_forbidden(request): # type: (Request) -> HTTPException """ Overrides the default ``HTTPForbidden`` [403] by appropriate ``HTTPUnauthorized`` [401] when applicable. Unauthorized response is for restricted user access according to credentials and/or authorization headers. Forbidden response is for operation refused by the underlying process operations. Without this fix, both situations return [403] regardless. .. seealso:: http://www.restapitutorial.com/httpstatuscodes.html """ authn_policy = request.registry.queryUtility(IAuthenticationPolicy) http_err = HTTPForbidden http_msg = s.HTTPForbiddenResponseSchema.description if authn_policy: principals = authn_policy.effective_principals(request) if Authenticated not in principals: http_err = HTTPUnauthorized http_msg = s.UnauthorizedResponseSchema.description content = get_request_info(request, default_message=http_msg) return raise_http(nothrow=True, httpError=http_err, detail=content[u"detail"], content=content, contentType=get_header("Accept", request.headers, default=CONTENT_TYPE_JSON, split=";,"))
def not_found_or_method_not_allowed(request): # type: (Request) -> HTTPException """ Overrides the default ``HTTPNotFound`` [404] by appropriate ``HTTPMethodNotAllowed`` [405] when applicable. Not found response can correspond to underlying process operation not finding a required item, or a completely unknown route (path did not match any existing API definition). Method not allowed is more specific to the case where the path matches an existing API route, but the specific request method (GET, POST, etc.) is not allowed on this path. Without this fix, both situations return [404] regardless. """ # noinspection PyProtectedMember if isinstance(request.exception, PredicateMismatch ) and request.method not in request.exception._safe_methods: http_err = HTTPMethodNotAllowed http_msg = "" # auto-generated by HTTPMethodNotAllowed else: http_err = HTTPNotFound http_msg = s.NotFoundResponseSchema.description content = get_request_info(request, default_message=http_msg) return raise_http(nothrow=True, httpError=http_err, detail=content[u"detail"], content=content, contentType=get_header("Accept", request.headers, default=CONTENT_TYPE_JSON, split=";,"))
def ows_parser_factory(request): # type: (Request) -> OWSParser """ Retrieve the appropriate :class:`OWSParser` parser using the ``Content-Type`` header. If the ``Content-Type`` header is missing or ``text/plain``, and the request has a body, try to parse the body as JSON and set the content-type to ``application/json`` if successful. Handle XML-like ``Content-Type`` headers such as ``application/x-www-form-urlencoded`` whenever applicable. Otherwise, use the basic :class:`OWSGetParser` or :class:`OWSPostParser` according to the presence of a body. These provide minimal parsing to handle most typical `OGC Web Services` (:term:`OWS`) request parameters. """ content_type = get_header("Content-Type", request.headers, default=None, split=";,") if content_type is None or content_type == CONTENT_TYPE_PLAIN: if is_json_body(request.body): # default to json when can be parsed as json request.headers[ "Content-Type"] = request.content_type = content_type = CONTENT_TYPE_JSON if content_type in (CONTENT_TYPE_JSON, CONTENT_TYPE_FORM): return MultiFormatParser(request) if request.body: return OWSPostParser(request) return OWSGetParser(request)
def request_api( request, # type: Request path, # type: Str method="GET", # type: Str data=None, # type: Optional[JSON] headers=None, # type: Optional[HeadersType] cookies=None, # type: Optional[CookiesType] ): # type: (...) -> Response """ Use a pyramid sub-request to request Magpie API routes via the UI. This avoids max retries and closed connections when using 1 worker (eg: during tests). Some information is retrieved from ``request`` to pass down to the sub-request (eg: cookies). If they are passed as argument, corresponding values will override the ones found in ``request``. All sub-requests to the API are assumed to be of ``magpie.common.CONTENT_TYPE_JSON`` unless explicitly overridden with ``headers``. """ method = method.upper() extra_kwargs = {"method": method} if headers: headers = dict(headers) else: headers = { "Accept": CONTENT_TYPE_JSON, "Content-Type": CONTENT_TYPE_JSON } # although no body is required per-say for HEAD/GET requests, add it if missing # this avoid downstream errors when 'request.POST' is accessed # we use a plain empty byte str because empty dict `{}` or `None` cause errors on each case # of local/remote testing with corresponding `webtest.TestApp`/`requests.Request` if not data: data = u"" if isinstance(data, dict) and get_header( "Content-Type", headers, split=[",", ";"]) == CONTENT_TYPE_JSON: data = json.dumps(data) if isinstance(cookies, dict): cookies = list(cookies.items()) if cookies and isinstance(headers, dict): headers = list(cookies.items()) for c, v in cookies: headers.append(("Set-Cookie", "{}={}".format(c, v))) if not cookies: cookies = request.cookies # cookies must be added to kw only if populated, iterable error otherwise if cookies: extra_kwargs["cookies"] = cookies subreq = Request.blank(path, base_url=request.application_url, headers=headers, POST=data, **extra_kwargs) return request.invoke_subrequest(subreq, use_tweens=True)
def check_ui_response_basic_info(response, expected_code=200, expected_type=CONTENT_TYPE_HTML): msg = None \ if get_header("Content-Type", response.headers) != CONTENT_TYPE_JSON \ else "Response body: {}".format(get_json_body(response)) check_val_equal(response.status_code, expected_code, msg=msg) check_val_is_in("Content-Type", dict(response.headers)) check_val_is_in(expected_type, get_response_content_types_list(response)) check_val_is_in("Magpie Administration", response.text, msg=null) # don't output big html if failing
def test_get_header_split(self): headers = { "Content-Type": "{}; charset=UTF-8".format(CONTENT_TYPE_JSON) } for name in [ "content_type", "content-type", "Content_Type", "Content-Type", "CONTENT_TYPE", "CONTENT-TYPE" ]: for split in [";,", ",;", ";", (",", ";"), [";", ","]]: utils.check_val_equal(get_header(name, headers, split=split), CONTENT_TYPE_JSON)
def internal_server_error(request): # type: (Request) -> HTTPException """ Overrides default HTTP. """ content = get_request_info( request, exception_details=True, default_message=s.InternalServerErrorResponseSchema.description) return raise_http(nothrow=True, httpError=HTTPInternalServerError, detail=content[u"detail"], content=content, contentType=get_header("Accept", request.headers, default=CONTENT_TYPE_JSON, split=";,"))
def validate_accept_header(request): # ignore types defined under UI or static routes to allow rendering path = request.path if not request.path.startswith( "/magpie") else request.path.replace("/magpie", "", 1) if not any(path.startswith(p) for p in ("/ui", "/static")): any_supported_header = SUPPORTED_CONTENT_TYPES + [CONTENT_TYPE_ANY] accept = get_header("accept", request.headers, default=CONTENT_TYPE_JSON, split=";,") verify_param(accept, isIn=True, paramCompare=any_supported_header, paramName="Accept Header", httpError=HTTPNotAcceptable, msgOnFail=s.NotAcceptableResponseSchema.description) return handler(request)
def ows_parser_factory(request): # type: (Request) -> OWSParser """ Retrieve the appropriate ``OWSRequest`` parser using the ``Content-Type`` header. Default to JSON if no ``Content-Type`` is specified or if it is 'text/plain' but can be parsed as JSON. Otherwise, use the GET/POST WPS parsers. """ content_type = get_header("Content-Type", request.headers, default=None, split=";,") if content_type is None or content_type == CONTENT_TYPE_PLAIN: try: # noinspection PyUnresolvedReferences if request.body: # raises if parsing fails # noinspection PyUnresolvedReferences json.loads(request.body) content_type = CONTENT_TYPE_JSON except ValueError: pass if content_type == CONTENT_TYPE_JSON: return JSONParser(request) elif content_type == CONTENT_TYPE_FORM: return FormParser(request) else: if request.method == "GET": return WPSGet(request) elif request.method == "POST": return WPSPost(request) # method not supported, raise using the specified content type header raise_http(httpError=HTTPMethodNotAllowed, contentType=content_type, detail="Method not implemented by Magpie OWSParser.")
def test_request(test_item, method, path, timeout=5, allow_redirects=True, **kwargs): """ Calls the request using either a :class:`webtest.TestApp` instance or :class:`requests.Request` from a string URL. :param test_item: one of `Base_Magpie_TestCase`, `webtest.TestApp` or remote server URL to call with `requests` :param method: request method (GET, POST, PUT, DELETE) :param path: test path starting at base path :param timeout: `timeout` to pass down to `request` :param allow_redirects: `allow_redirects` to pass down to `request` :return: response of the request """ method = method.upper() status = kwargs.pop("status", None) # obtain json body from any json/data/body/params kw and empty {} if not specified # reapply with the expected webtest/requests method kw afterward json_body = None for kw in ["json", "data", "body", "params"]: json_body = kwargs.get(kw, json_body) if kw in kwargs: kwargs.pop(kw) json_body = json_body or {} app_or_url = get_app_or_url(test_item) if isinstance(app_or_url, TestApp): # remove any 'cookies' keyword handled by the 'TestApp' instance if "cookies" in kwargs: cookies = kwargs.pop("cookies") if cookies and not app_or_url.cookies: app_or_url.cookies.update(cookies) # obtain Content-Type header if specified to ensure it is properly applied kwargs["content_type"] = get_header("Content-Type", kwargs.get("headers")) # convert JSON body as required kwargs["params"] = json_body if json_body is not None: kwargs.update( {"params": json.dumps(json_body, cls=json.JSONEncoder)}) if status and status >= 300: kwargs.update({"expect_errors": True}) # noinspection PyProtectedMember resp = app_or_url._gen_request(method, path, **kwargs) # automatically follow the redirect if any and evaluate its response max_redirect = kwargs.get("max_redirects", 5) while 300 <= resp.status_code < 400 and max_redirect > 0: resp = resp.follow() max_redirect -= 1 assert max_redirect >= 0, "Maximum follow redirects reached." # test status accordingly if specified assert resp.status_code == status or status is None, "Response not matching the expected status code." return resp else: # remove keywords specific to TestApp kwargs.pop("expect_errors", None) kwargs["json"] = json_body url = "{url}{path}".format(url=app_or_url, path=path) return requests.request(method, url, timeout=timeout, allow_redirects=allow_redirects, **kwargs)
def request_api( request, # type: Request path, # type: Str method="GET", # type: Str data=None, # type: Optional[Union[JSON, Str]] headers=None, # type: Optional[HeadersType] cookies=None, # type: Optional[CookiesType] ): # type: (...) -> AnyResponseType """ Use a pyramid sub-request to request Magpie API routes via the UI. This avoids max retries and closed connections when using 1 worker (eg: during tests). Some information is retrieved from :paramref:`request` to pass down to the sub-request (eg: cookies). If they are passed as argument, corresponding values will override the ones found in :paramref:`request`. All sub-requests to the API are assumed to be :py:data:`magpie.common.CONTENT_TYPE_JSON` unless explicitly overridden with :paramref:`headers`. Headers are also looked for for additional ``Set-Cookie`` header in case they need to be passed down to :paramref:`cookies`. :param request: incoming Magpie UI request that requires sub-request to Magpie API, to retrieve required details. :param path: local Magpie API path (relative to root without URL). :param method: HTTP method to send the API sub-request. :param data: JSON dictionary or literal string content of the request body. :param headers: override headers to employ for the API sub-request. Defaults to JSON Accept & Content-Type headers. :param cookies: override cookies to employ for the API sub-request. Defaults to current logged user. """ method = method.upper() extra_kwargs = {"method": method} if headers: headers = dict(headers) else: headers = { "Accept": CONTENT_TYPE_JSON, "Content-Type": CONTENT_TYPE_JSON } # although no body is required per-say for HEAD/GET requests, add it if missing # this avoid downstream errors when 'request.POST' is accessed # we use a plain empty byte str because empty dict `{}` or `None` cause errors on each case # of local/remote testing with corresponding `webtest.TestApp`/`requests.Request` if not data: data = "" if isinstance(data, dict) and get_header( "Content-Type", headers, split=[",", ";"]) == CONTENT_TYPE_JSON: data = json.dumps(data) if isinstance(cookies, dict): cookies = list(cookies.items()) if cookies and isinstance(headers, dict): headers = list(headers.items()) for cookie_name, cookie_value in cookies: headers.append( ("Set-Cookie", "{}={}".format(cookie_name, cookie_value))) if not cookies: cookies = request.cookies # cookies must be added to kw only if populated, iterable error otherwise if cookies: extra_kwargs["cookies"] = cookies subreq = Request.blank(path, base_url=request.application_url, headers=headers, POST=data, **extra_kwargs) return request.invoke_subrequest(subreq, use_tweens=True)