import http # 打印“OK”对应的状态码 print('"OK" match to "{}"'.format(http.HTTPStatus.OK)) # 打印“NOT FOUND”对应的状态码 print('"NOT_FOUND" match to "{}"'.format(http.HTTPStatus.NOT_FOUND)) # 打印状态码对应的状态短语 while True: st_code = input("请输入要查询的状态码:(输入 q 退出)\n") try: print("状态为:{}\n".format(http.HTTPStatus(int(st_code)).phrase)) except ValueError: if st_code == 'q': break print("对应的状态码 {} 不存在\n".format(st_code))
async def graphql( self, query: str, *, endpoint: str = "https://api.github.com/graphql", **variables: Any, ) -> Any: """Query the GraphQL v4 API. The *endpoint* argument specifies the endpoint URL to use. The *variables* kwargs-style argument collects all variables for the query. """ payload: Dict[str, Any] = {"query": query} if variables: payload["variables"] = variables request_data = json.dumps(payload).encode("utf-8") request_headers = sansio.create_headers(self.requester, accept=JSON_UTF_8_CHARSET, oauth_token=self.oauth_token) request_headers.update({ "content-type": JSON_UTF_8_CHARSET, "content-length": str(len(request_data)), }) status_code, response_headers, response_data = await self._request( "POST", endpoint, request_headers, request_data) if not response_data: raise GraphQLException("Response contained no data", response_data) # Decode content. resp_content_type = response_headers.get("content-type") type_, encoding = sansio._parse_content_type(resp_content_type) response_str = response_data.decode(encoding) if type_ == "application/json": response: Dict[str, Any] = json.loads(response_str) else: raise GraphQLResponseTypeError(resp_content_type, response_str) if status_code >= 500: raise GitHubBroken(http.HTTPStatus(status_code)) elif status_code == 401: raise GraphQLAuthorizationFailure(response) elif status_code >= 400: # 400 corresponds to malformed JSON, but that should never receive # that as a response as json.dumps() should have raised its own # exception before we made the request. raise BadGraphQLRequest(http.HTTPStatus(status_code), response) elif status_code == 200: self.rate_limit = sansio.RateLimit.from_http(response_headers) if "errors" in response: raise QueryError(response) if "data" in response: return response["data"] else: raise GraphQLException( f"Response did not contain 'errors' or 'data': {response}", response) else: raise GraphQLException( f"Unexpected HTTP response to GraphQL request: {status_code}", response)
def alt_response( self, status_code, response=None, *, schema=None, content_type=None, description=None, example=None, examples=None, headers=None, success=False, ): """Decorator documenting an alternative response :param int|str|HTTPStatus status_code: HTTP status code. :param str response: Reponse reference. :param schema schema|str|dict: :class:`Schema <marshmallow.Schema>` class or instance or reference or dict. :param str description: Description of the response (default: None). :param dict example: Example of response message. :param dict examples: Examples of response message. :param dict headers: Headers returned by the response. :param bool success: ``True`` if this response is part of the normal flow of the function. Default: ``False``. This decorator allows the user to document an alternative response. This can be an error managed with :func:`abort <abort>` or any response that is not the primary flow of the function documented by :meth:`Blueprint.reponse <Blueprint.response>`. When a response reference is passed as ``response``, it is used as description and the keyword arguments are ignored. Otherwise, a description is built from the keyword arguments. See :ref:`document-alternative-responses`. """ # Response ref is passed if response is not None: resp_doc = response # Otherwise, build response description else: if isinstance(schema, type): schema = schema() # Document response (schema, description,...) in the API doc doc_schema = self._make_doc_response_schema(schema) if description is None: description = http.HTTPStatus(int(status_code)).phrase resp_doc = remove_none({ "schema": doc_schema, "description": description, "example": example, "examples": examples, "headers": headers, }) resp_doc["content_type"] = content_type def decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) # Store doc in wrapper function # The deepcopy avoids modifying the wrapped function doc wrapper._apidoc = deepcopy(getattr(wrapper, "_apidoc", {})) wrapper._apidoc.setdefault("response", {}).setdefault( "responses", {})[status_code] = resp_doc if success: # Indicate this code is a success status code # Helps other decorators documenting success responses wrapper._apidoc.setdefault("success_status_codes", []).append(status_code) return wrapper return decorator
def _get_status_line(status_code): try: phrase = http.HTTPStatus(status_code).phrase.encode() except ValueError: phrase = b"" return b"".join([b"HTTP/1.1 ", str(status_code).encode(), b" ", phrase, b"\r\n"])
def decipher_response( status_code: int, headers: Mapping[str, str], body: bytes) -> Tuple[Any, Optional[RateLimit], Optional[str]]: """Decipher an HTTP response for a GitHub API request. The mapping providing the headers is expected to support lowercase keys. The parameters of this function correspond to the three main parts of an HTTP response: the status code, headers, and body. Assuming no errors which lead to an exception being raised, a 3-item tuple is returned. The first item is the decoded body (typically a JSON object, but possibly None or a string depending on the content type of the body). The second item is an instance of RateLimit based on what the response specified. The last item of the tuple is the URL where to request the next part of results. If there are no more results then None is returned. Do be aware that the URL can be a URI template and so may need to be expanded. If the status code is anything other than 200, 201, or 204, then an HTTPException is raised. """ data = _decode_body(headers.get("content-type"), body) if status_code in {200, 201, 204}: return data, RateLimit.from_http(headers), _next_link( headers.get("link")) else: try: message = data["message"] except (TypeError, KeyError): message = None exc_type: Type[HTTPException] if status_code >= 500: exc_type = GitHubBroken elif status_code >= 400: exc_type = BadRequest if status_code == 403: rate_limit = RateLimit.from_http(headers) if rate_limit and not rate_limit.remaining: raise RateLimitExceeded(rate_limit, message) elif status_code == 422: try: errors = data.get("errors", None) except AttributeError: # Not JSON so don't know why the request failed. raise BadRequestUnknownError(data) exc_type = InvalidField if errors: if any(e["code"] in [ "missing", "missing_field", "invalid", "already_exists" ] for e in errors): error_context = ", ".join( repr(e.get("field")) for e in errors) message = f"{message} for {error_context}" else: exc_type = ValidationError error_context = ", ".join( repr(e.get("message")) for e in errors) message = f"{message}: {error_context}" else: message = data["message"] raise exc_type(errors, message) elif status_code >= 300: exc_type = RedirectionException else: exc_type = HTTPException status_code_enum = http.HTTPStatus(status_code) args: Union[Tuple[http.HTTPStatus, str], Tuple[http.HTTPStatus]] if message: args = status_code_enum, message else: args = (status_code_enum, ) raise exc_type(*args)
def test_5XX(self): status_code = 502 with pytest.raises(GitHubBroken) as exc_info: sansio.decipher_response(status_code, {}, b"") assert exc_info.value.status_code == http.HTTPStatus(status_code)
def test_3XX(self): status_code = 301 with pytest.raises(RedirectionException) as exc_info: sansio.decipher_response(status_code, {}, b"") assert exc_info.value.status_code == http.HTTPStatus(status_code)
def _prepare_auth_doc(doc, doc_info, **kwargs): if doc_info.get("auth", False): doc.setdefault("responses", {})["401"] = http.HTTPStatus(401).name doc["security"] = [{"TokenAuthentication": []}] return doc
def _prepare_404_doc(doc, doc_info, **kwargs): if doc_info.get("validate", False): doc.setdefault("responses", {})["404"] = http.HTTPStatus(404).name return doc
def run(args): # SNIPPET_START: parsing with open(args.scenario) as f: scenario = json.load(f) hosts = scenario.get("hosts", ["localhost", "localhost"]) if args.consensus == "pbft": hosts = ["localhost"] * 3 args.package = scenario["package"] # SNIPPET_END: parsing scenario_dir = os.path.dirname(args.scenario) # SNIPPET_START: create_network with infra.network.network(hosts, args.binary_dir, args.debug_nodes, args.perf_nodes) as network: network.start_and_join(args) # SNIPPET_END: create_network primary, backups = network.find_nodes() with primary.client() as mc: check = infra.checker.Checker() check_commit = infra.checker.Checker(mc) for connection in scenario["connections"]: with (primary.client("user0") if not connection.get("on_backup") else random.choice(backups).client("user0")) as client: txs = connection.get("transactions", []) for include_file in connection.get("include", []): with open(os.path.join(scenario_dir, include_file)) as f: txs += json.load(f) for tx in txs: r = client.call( tx["method"], body=tx["body"], http_verb=tx.get("verb", "POST"), ) if tx.get("expected_error") is not None: check( r, error=lambda status, msg, transaction=tx: status # pylint: disable=no-member == http.HTTPStatus( transaction.get("expected_error")).value, ) elif tx.get("expected_result") is not None: check_commit(r, result=tx.get("expected_result")) else: check_commit(r, result=lambda res: res is not None) network.wait_for_node_commit_sync(args.consensus) if args.network_only: LOG.info("Keeping network alive with the following nodes:") LOG.info(" Primary = {}:{}".format(primary.pubhost, primary.rpc_port)) for i, f in enumerate(backups): LOG.info(" Backup[{}] = {}:{}".format(i, f.pubhost, f.rpc_port)) input("Press Enter to shutdown...")
with pytest.raises(ValueError): falcon.get_http_status('-404.3') assert falcon.get_http_status(123, 'Go Away') == '123 Go Away' @pytest.mark.parametrize('v_in,v_out', [ (703, falcon.HTTP_703), (404, falcon.HTTP_404), (404.9, falcon.HTTP_404), (falcon.HTTP_200, falcon.HTTP_200), (falcon.HTTP_307, falcon.HTTP_307), (falcon.HTTP_404, falcon.HTTP_404), (123, '123 Unknown'), ('123 Wow Such Status', '123 Wow Such Status'), (b'123 Wow Such Status', '123 Wow Such Status'), (b'200 OK', falcon.HTTP_OK), (http.HTTPStatus(200), falcon.HTTP_200), (http.HTTPStatus(307), falcon.HTTP_307), (http.HTTPStatus(401), falcon.HTTP_401), (http.HTTPStatus(410), falcon.HTTP_410), (http.HTTPStatus(429), falcon.HTTP_429), (http.HTTPStatus(500), falcon.HTTP_500), ]) def test_code_to_http_status(self, v_in, v_out): assert falcon.code_to_http_status(v_in) == v_out @pytest.mark.parametrize( 'v', [0, 13, 99, 1000, 1337.01, -99, -404.3, -404, -404.3]) def test_code_to_http_status_value_error(self, v): with pytest.raises(ValueError): falcon.code_to_http_status(v)
def test_code_to_http_status(self, v_in, v_out): assert falcon.code_to_http_status(v_in) == v_out @pytest.mark.parametrize( 'v', ['not_a_number', 0, '0', 99, '99', '404.3', -404.3, '-404', '-404.3']) def test_code_to_http_status_neg(self, v): with pytest.raises(ValueError): falcon.code_to_http_status(v) @pytest.mark.parametrize( 'v_in,v_out', [ # NOTE(kgriffs): Include some codes not used elsewhere so that # we get past the LRU. (http.HTTPStatus(505), 505), (712, 712), ('712', 712), (b'404 Not Found', 404), (b'712 NoSQL', 712), ('404 Not Found', 404), ('123 Wow Such Status', 123), # NOTE(kgriffs): Test LRU (http.HTTPStatus(505), 505), ('123 Wow Such Status', 123), ]) def test_http_status_to_code(self, v_in, v_out): assert falcon.http_status_to_code(v_in) == v_out @pytest.mark.parametrize('v',
def decipher_response(status_code: int, headers: Mapping, body: bytes) -> Tuple[Any, RateLimit, Optional[str]]: """Decipher an HTTP response for a GitHub API request. The mapping providing the headers is expected to support lowercase keys. The parameters of this function correspond to the three main parts of an HTTP response: the status code, headers, and body. Assuming no errors which lead to an exception being raised, a 3-item tuple is returned. The first item is the decoded body (typically a JSON object, but possibly None or a string depending on the content type of the body). The second item is an instance of RateLimit based on what the response specified. The last item of the tuple is the URL where to request the next part of results. If there are no more results then None is returned. Do be aware that the URL can be a URI template and so may need to be expanded. If the status code is anything other than 200, 201, or 204, then an HTTPException is raised. """ data = _decode_body(headers.get("content-type"), body) if status_code in {200, 201, 204}: return data, RateLimit.from_http(headers), _next_link( headers.get("link")) else: try: message = data["message"] print("Message: ", message) except (TypeError, KeyError): message = None exc_type: Type[HTTPException] if status_code >= 500: exc_type = GitHubBroken elif status_code >= 400: exc_type = BadRequest if status_code == 403: rate_limit = RateLimit.from_http(headers) if not rate_limit.remaining: raise RateLimitExceeded(rate_limit, message) elif status_code == 404: print( "File not found or you do not have permission to access this repo" ) elif status_code == 422: errors = data.get("errors", None) if errors: #fields = ", ".join(repr(e["field"]) for e in errors) message = f"{message} for {repr(errors)} " else: message = data["message"] raise InvalidField(errors, message) elif status_code >= 300: exc_type = RedirectionException else: exc_type = HTTPException status_code_enum = http.HTTPStatus(status_code) args: Tuple if message: args = status_code_enum, message else: args = status_code_enum, raise exc_type(*args)
async def test_bad_credentials(self): gh, response_data = self.gh_and_response("bad-credentials-401.json") with pytest.raises(GraphQLAuthorizationFailure) as exc: await gh.graphql(_SAMPLE_QUERY) assert exc.value.response == response_data assert exc.value.status_code == http.HTTPStatus(401)
async def test_new_comment(): # Comment not from PR author. data = { "action": "created", "issue": { "user": { "login": "******" } }, "comment": { "user": { "login": "******" }, "body": awaiting.BORING_TRIGGER_PHRASE, }, } event = sansio.Event(data, event="issue_comment", delivery_id="12345") gh = FakeGH() await awaiting.router.dispatch(event, gh) assert not len(gh.post_) # Comment from PR author but missing trigger phrase. data = { "action": "created", "issue": { "user": { "login": "******" } }, "comment": { "user": { "login": "******" }, "body": "I DID expect the Spanish Inquisition", }, } event = sansio.Event(data, event="issue_comment", delivery_id="12345") gh = FakeGH() await awaiting.router.dispatch(event, gh) assert not len(gh.post_) # Everything is right with the world. data = { "action": "created", "issue": { "user": { "login": "******" }, "labels": [], "labels_url": "https://api.github.com/labels/42", "url": "https://api.github.com/issue/42", "pull_request": { "url": "https://api.github.com/pr/42" }, "comments_url": "https://api.github.com/comments/42", }, "comment": { "user": { "login": "******" }, "body": awaiting.BORING_TRIGGER_PHRASE, }, } event = sansio.Event(data, event="issue_comment", delivery_id="12345") items = { "https://api.github.com/teams/6/memberships/brettcannon": True, "https://api.github.com/teams/6/memberships/gvanrossum": True, "https://api.github.com/teams/6/memberships/not-core-dev": gidgethub.BadRequest(status_code=http.HTTPStatus(404)), } iterators = { "https://api.github.com/orgs/python/teams": [{ "name": "python core", "id": 6 }], "https://api.github.com/pr/42/reviews": [ { "user": { "login": "******" }, "state": "approved" }, { "user": { "login": "******" }, "state": "changes_requested" }, { "user": { "login": "******" }, "state": "approved" }, ], } gh = FakeGH(getitem=items, getiter=iterators) await awaiting.router.dispatch(event, gh) assert len(gh.post_) == 3 labeling, comment, review_request = gh.post_ assert labeling[0] == "https://api.github.com/labels/42" assert labeling[1] == [awaiting.Blocker.change_review.value] assert comment[0] == "https://api.github.com/comments/42" comment_body = comment[1]["body"] assert "@brettcannon" in comment_body assert "@gvanrossum" in comment_body assert "not-core-dev" not in comment_body assert review_request[ 0] == "https://api.github.com/pr/42/requested_reviewers" requested_reviewers = review_request[1]["reviewers"] assert "brettcannon" in requested_reviewers assert "gvanrossum" in requested_reviewers assert "not-core-dev" not in requested_reviewers # All is right with the Monty Python world. data = { "action": "created", "issue": { "user": { "login": "******" }, "labels": [], "labels_url": "https://api.github.com/labels/42", "url": "https://api.github.com/issue/42", "pull_request": { "url": "https://api.github.com/pr/42" }, "comments_url": "https://api.github.com/comments/42", }, "comment": { "user": { "login": "******" }, "body": awaiting.FUN_TRIGGER_PHRASE, }, } event = sansio.Event(data, event="issue_comment", delivery_id="12345") gh = FakeGH(getitem=items, getiter=iterators) await awaiting.router.dispatch(event, gh) assert len(gh.post_) == 3 labeling, comment, review_request = gh.post_ assert labeling[0] == "https://api.github.com/labels/42" assert labeling[1] == [awaiting.Blocker.change_review.value] assert comment[0] == "https://api.github.com/comments/42" comment_body = comment[1]["body"] assert "@brettcannon" in comment_body assert "@gvanrossum" in comment_body assert "not-core-dev" not in comment_body assert review_request[ 0] == "https://api.github.com/pr/42/requested_reviewers" requested_reviewers = review_request[1]["reviewers"] assert "brettcannon" in requested_reviewers assert "gvanrossum" in requested_reviewers assert "not-core-dev" not in requested_reviewers
def run(self, command, **data): """Convert specified command and data into HTTP request, send it to webservice, and return response. Handle error responses. The data kwargs are passed to the HTTP request. 'pocket' and 'table_name' data fields are substituted, if None. :return: dict. See Server class for possible keys :raise: ValueError if invalid command given :raise: CommunicationError on e.g. timeouts or server-side errors, InvalidRequest on invalid requests """ pocket = data.pop("pocket", None) or DEFAULT_POCKET_NAME host = self.http_config.get("host", DEFAULT_HOST) base_url = "{}{}".format(host, POCKETS_TAIL) pocket_url = "{}/{}".format(base_url, pocket) copy_url = "{}{}".format(host, COPY_TAIL) eid_url = "{}/{}/{}".format(pocket_url, data.get("table_name") or DEFAULT_TABLE, data.get("eid")) username = self.http_config.get("username") password = self.http_config.get("password") auth = None if username and password: auth = (username, password) kwargs = dict(auth=auth, timeout=self.http_config.get("timeout")) if command == "list": # Correctly send filters; allowing for server-side deserialization kwargs["json"] = json.dumps(data) else: kwargs["json"] = data or None if command == "list": url = pocket_url function = requests.get elif command == "remove": url = eid_url function = requests.delete elif command == "add": url = pocket_url function = requests.post elif command == "pockets": url = base_url function = requests.post elif command == "copy": url = copy_url function = requests.post elif command == "get": url = eid_url function = requests.get elif command == "update": url = eid_url function = requests.patch else: raise ValueError("Unknown command: {}".format(command)) try: response = function(url, **kwargs) except requests.RequestException as e: raise exceptions.CommunicationError( "Error sending request: {}".format(e)) if response.ok: return response.json() else: try: # Get further information about error (see Server.run) error = response.json()["error"] except (json.JSONDecodeError, KeyError): error = "-" status_code = response.status_code if 400 <= status_code < 500: error_class = exceptions.InvalidRequest else: error_class = exceptions.CommunicationError message = "Error handling request. " +\ "Server returned '{} ({}): {}'".format( http.HTTPStatus(status_code).phrase, status_code, error) raise error_class(message)
def __init__(self, status, headers, data): self.headers = headers self.data = data self.status = http.HTTPStatus(status)
def response(self, schema=None, *, code=200, description=None, example=None, examples=None, headers=None): """Decorator generating an endpoint response :param schema: :class:`Schema <marshmallow.Schema>` class or instance. If not None, will be used to serialize response data. :param int|str|HTTPStatus code: HTTP status code (default: 200). Used if none is returned from the view function. :param str description: Description of the response (default: None). :param dict example: Example of response message. :param list examples: Examples of response message. :param dict headers: Headers returned by the response. The decorated function is expected to return the same types of value than a typical flask view function, except the body part may be an object or a list of objects to serialize with the schema, rather than a ``string``. If the decorated function returns a ``Response`` object, the ``schema`` and ``code`` parameters are only used to document the resource. The `example` and `examples` parameters are mutually exclusive. The latter should only be used with OpenAPI 3. The `example`, `examples` and `headers` parameters are only used to document the resource. See :doc:`Response <response>`. """ if isinstance(schema, type): schema = schema() # Document response (schema, description,...) in the API doc resp_doc = {} doc_schema = self._make_doc_response_schema(schema) if doc_schema is not None: resp_doc['schema'] = doc_schema if description is not None: resp_doc['description'] = description else: resp_doc['description'] = http.HTTPStatus(int(code)).phrase if example is not None: resp_doc['example'] = example if examples is not None: resp_doc['examples'] = examples if headers is not None: resp_doc['headers'] = headers doc = {'responses': {code: resp_doc}} def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # Execute decorated function result_raw, status, headers = unpack_tuple_response( func(*args, **kwargs)) # If return value is a werkzeug BaseResponse, return it if isinstance(result_raw, BaseResponse): set_status_and_headers_in_response(result_raw, status, headers) return result_raw # Dump result with schema if specified if schema is None: result_dump = result_raw else: result_dump = schema.dump(result_raw) if MARSHMALLOW_VERSION_MAJOR < 3: result_dump = result_dump.data # Store result in appcontext (may be used for ETag computation) appcontext = get_appcontext() appcontext['result_raw'] = result_raw appcontext['result_dump'] = result_dump # Build response resp = jsonify(self._prepare_response_content(result_dump)) set_status_and_headers_in_response(resp, status, headers) if status is None: resp.status_code = code return resp # Document pagination header if needed if getattr(func, '_paginated', False) is True: doc['responses'][code]['headers'] = { self.PAGINATION_HEADER_FIELD_NAME: (self.PAGINATION_HEADER_DOC) } # Document default error response doc['responses']['default'] = 'DEFAULT_ERROR' # Store doc in wrapper function # The deepcopy avoids modifying the wrapped function doc wrapper._apidoc = deepcopy(getattr(wrapper, '_apidoc', {})) wrapper._apidoc['response'] = doc return wrapper return decorator
def test_4XX_no_message(self): status_code = 400 with pytest.raises(BadRequest) as exc_info: sansio.decipher_response(status_code, {}, b"") assert exc_info.value.status_code == http.HTTPStatus(status_code)
def _get_status_phrase(status_code): try: return http.HTTPStatus(status_code).phrase.encode() except ValueError: return b""
def test_2XX_error(self): status_code = 205 with pytest.raises(HTTPException) as exc_info: sansio.decipher_response(status_code, {}, b"") assert exc_info.value.status_code == http.HTTPStatus(status_code)
def __init__(self, response: Any) -> None: super().__init__(http.HTTPStatus(401), response)
def __init__(self, status_code: int, detail: str = None) -> None: if detail is None: detail = http.HTTPStatus(status_code).phrase self.status_code = status_code self.detail = detail
def on_get(self, req, resp): resp.content_type = falcon.MEDIA_TEXT resp.data = b'Hello, World!' resp.status = status app = create_app(asgi=asgi) app.add_route('/status', Resource()) return testing.TestClient(app) return client @pytest.mark.parametrize( 'status,expected_code', [ (http.HTTPStatus(200), 200), (http.HTTPStatus(202), 202), (http.HTTPStatus(403), 403), (http.HTTPStatus(500), 500), (http.HTTPStatus.OK, 200), (http.HTTPStatus.USE_PROXY, 305), (http.HTTPStatus.NOT_FOUND, 404), (http.HTTPStatus.NOT_IMPLEMENTED, 501), (200, 200), (307, 307), (500, 500), (702, 702), (b'200 OK', 200), (b'702 Emacs', 702), ], )
def _get_reason_phrase(status_code: int) -> str: try: return http.HTTPStatus(status_code).phrase except ValueError: return ""
async def test_new_review(): # First non-comment review from a non-core dev. username = "******" data = { "action": "submitted", "review": { "state": "approved", "user": { "login": username, }, }, "pull_request": { "url": "https://api.github.com/pr/42", "issue_url": "https://api.github.com/issue/42", "state": "open", }, } event = sansio.Event(data, event="pull_request_review", delivery_id="12345") teams = [{"name": "python core", "id": 6}] items = { f"https://api.github.com/teams/6/memberships/{username}": gidgethub.BadRequest(status_code=http.HTTPStatus(404)), "https://api.github.com/teams/6/memberships/brettcannon": True, "https://api.github.com/issue/42": { "labels": [], "labels_url": "https://api.github.com/labels/42", }, } iterators = { "https://api.github.com/orgs/python/teams": teams, "https://api.github.com/pr/42/reviews": [{ "user": { "login": "******" }, "state": "commented" }], } gh = FakeGH(getiter=iterators, getitem=items) await awaiting.router.dispatch(event, gh) assert len(gh.post_) == 1 post_ = gh.post_[0] assert post_[0] == "https://api.github.com/labels/42" assert post_[1] == [awaiting.Blocker.core_review.value] # First and second review from a non-core dev. items = { f"https://api.github.com/teams/6/memberships/{username}": gidgethub.BadRequest(status_code=http.HTTPStatus(404)), "https://api.github.com/teams/6/memberships/brettcannon": True, "https://api.github.com/issue/42": { "labels": [], "labels_url": "https://api.github.com/labels/42", }, } iterators = { "https://api.github.com/orgs/python/teams": teams, "https://api.github.com/pr/42/reviews": [{ "user": { "login": "******" }, "state": "approved" }], } gh = FakeGH(getiter=iterators, getitem=items) await awaiting.router.dispatch(event, gh) assert not gh.post_ # First comment review from a non-core dev. data = { "action": "submitted", "review": { "state": "comment", "user": { "login": username, }, }, "pull_request": { "url": "https://api.github.com/pr/42", "issue_url": "https://api.github.com/issue/42", "state": "open", }, } event = sansio.Event(data, event="pull_request_review", delivery_id="12345") items = { f"https://api.github.com/teams/6/memberships/{username}": gidgethub.BadRequest(status_code=http.HTTPStatus(404)), "https://api.github.com/teams/6/memberships/brettcannon": True, "https://api.github.com/issue/42": { "labels": [], "labels_url": "https://api.github.com/labels/42", }, } iterators = { "https://api.github.com/orgs/python/teams": teams, "https://api.github.com/pr/42/reviews": [{ "user": { "login": "******" }, "state": "approved" }], } gh = FakeGH(getiter=iterators, getitem=items) await awaiting.router.dispatch(event, gh) assert not gh.post_ # Core dev submits an approving review. username = "******" data = { "action": "submitted", "review": { "user": { "login": username, }, "state": "APPROVED", }, "pull_request": { "url": "https://api.github.com/pr/42", "issue_url": "https://api.github.com/issue/42", "state": "open", }, } event = sansio.Event(data, event="pull_request_review", delivery_id="12345") teams = [{"name": "python core", "id": 6}] items = { f"https://api.github.com/teams/6/memberships/{username}": True, "https://api.github.com/issue/42": { "labels": [{ "name": awaiting.Blocker.changes.value }], "labels_url": "https://api.github.com/labels/42", }, } iterators = { "https://api.github.com/orgs/python/teams": teams, "https://api.github.com/pr/42/reviews": [], } gh = FakeGH(getiter=iterators, getitem=items) await awaiting.router.dispatch(event, gh) assert len(gh.post_) == 1 post_ = gh.post_[0] assert post_[0] == "https://api.github.com/labels/42" assert post_[1] == [awaiting.Blocker.merge.value] # Core dev submits an approving review on an already closed pull request. username = "******" data = { "action": "submitted", "review": { "user": { "login": username, }, "state": "APPROVED", }, "pull_request": { "url": "https://api.github.com/pr/42", "issue_url": "https://api.github.com/issue/42", "state": "closed", }, } event = sansio.Event(data, event="pull_request_review", delivery_id="12345") teams = [{"name": "python core", "id": 6}] items = { f"https://api.github.com/teams/6/memberships/{username}": True, "https://api.github.com/issue/42": { "labels": [{ "name": awaiting.Blocker.changes.value }], "labels_url": "https://api.github.com/labels/42", }, } iterators = { "https://api.github.com/orgs/python/teams": teams, "https://api.github.com/pr/42/reviews": [], } gh = FakeGH(getiter=iterators, getitem=items) await awaiting.router.dispatch(event, gh) assert not gh.post_ # Core dev requests changes. data = { "action": "submitted", "review": { "user": { "login": username, }, "state": "changes_requested".upper(), }, "pull_request": { "url": "https://api.github.com/pr/42", "issue_url": "https://api.github.com/issue/42", "comments_url": "https://api.github.com/comment/42", "user": { "login": "******" }, "state": "open", }, } event = sansio.Event(data, event="pull_request_review", delivery_id="12345") items = { f"https://api.github.com/teams/6/memberships/{username}": True, f"https://api.github.com/teams/6/memberships/miss-islington": gidgethub.BadRequest(status_code=http.HTTPStatus(404)), "https://api.github.com/issue/42": { "labels": [], "labels_url": "https://api.github.com/labels/42", }, } gh = FakeGH(getiter=iterators, getitem=items) await awaiting.router.dispatch(event, gh) assert len(gh.post_) == 2 labeling = gh.post_[0] assert labeling[0] == "https://api.github.com/labels/42" assert labeling[1] == [awaiting.Blocker.changes.value] message = gh.post_[1] assert message[0] == "https://api.github.com/comment/42" assert awaiting.BORING_TRIGGER_PHRASE in message[1]["body"] # Comment reviews do nothing. data = { "action": "submitted", "review": { "user": { "login": username, }, "state": "commented".upper(), }, "pull_request": { "url": "https://api.github.com/pr/42", "issue_url": "https://api.github.com/issue/42", "comments_url": "https://api.github.com/comment/42", "state": "open", }, } event = sansio.Event(data, event="pull_request_review", delivery_id="12345") gh = FakeGH(getiter=iterators, getitem=items) await awaiting.router.dispatch(event, gh) assert not len(gh.post_) # Skip commenting if "awaiting changes" is already set. data = { "action": "submitted", "review": { "user": { "login": username, }, "state": "changes_requested".upper(), }, "pull_request": { "url": "https://api.github.com/pr/42", "issue_url": "https://api.github.com/issue/42", "comments_url": "https://api.github.com/comment/42", "state": "open", }, } event = sansio.Event(data, event="pull_request_review", delivery_id="12345") items = { f"https://api.github.com/teams/6/memberships/{username}": True, "https://api.github.com/issue/42": { "labels": [{ "name": awaiting.Blocker.changes.value }], "labels_url": "https://api.github.com/labels/42", }, } gh = FakeGH(getiter=iterators, getitem=items) await awaiting.router.dispatch(event, gh) assert not len(gh.post_)
def update(self, data): """Decrypt cipher text Cipher text must be passed to this function in the order in which it was output from the encryption.update function. data: (A portion of) the cipher text to be decrypted. data value has to be contained in a bytes, bytearray or memoryview object. returns: any plain text produced by the call """ if not isinstance(data, (bytes, bytearray, memoryview)): raise RuntimeError( "Data must be bytes, bytearray, or memoryview objects") # # each encryption has a header on it that identifies the algorithm # used and an encryption of the data key that was used to encrypt # the original plain text. there is no guarantee how much of that # data will be passed to this function or how many times this # function will be called to process all of the data. to that end, # this function buffers data internally, when it is unable to # process it. # # the function buffers data internally until the entire header is # received. once the header has been received, the encrypted data # key is sent to the server for decryption. after the header has # been successfully handled, this function always decrypts all of # the data in its internal buffer *except* for however many bytes # are specified by the algorithm's tag size. see the end() function # for details. # self._buf += data pt = b'' # if there is no key or 'dec' member of key, then the code # is still trying to build a complete header if not hasattr(self, '_key') or not 'dec' in self._key: fmt = '!BBBBH' fmtlen = struct.calcsize(fmt) # does the buffer contain enough of the header to # determine the lengths of the initialization vector # and the key? if len(self._buf) >= fmtlen: ver, flags, alg, veclen, keylen = struct.unpack( fmt, self._buf[:fmtlen]) # For VER 0, lsb of indicates AAD or not if (ver != 0) or (flags & ~algorithm.UBIQ_HEADER_V0_FLAG_AAD): raise RuntimeError('invalid encryption header') # does the buffer contain the entire header? if len(self._buf) >= fmtlen + veclen + keylen: # Get the Header for AAD purposes. Only needed if # version != 0, but get it now anyways aad = self._buf[:fmtlen + veclen + keylen] # extract the initialization vector and the key vec = self._buf[fmtlen:fmtlen + veclen] key = self._buf[fmtlen + veclen:fmtlen + veclen + keylen] # remove the header from the buffer self._buf = self._buf[fmtlen + veclen + keylen:] # generate a local identifier for the key sha = crypto.hashes.Hash(crypto.hashes.SHA256(), backend=crypto_backend()) sha.update(key) client_id = sha.finalize() # if the object already has a key (from a previous # decryption), is the key in this header the same as # that previous one? # # if not, clear out the existing key if hasattr(self, '_key'): if self._key['client_id'] != client_id: self.reset() # if the object (still) has a key, then it can be # reused--see below. if not, then request a decryption # of the key in the current header from the server if not hasattr(self, '_key'): url = self._endpoint_base() + '/decryption/key' response = requests.post( url, data=json.dumps({ 'encrypted_data_key': base64.b64encode(key).decode('utf-8') }).encode('utf-8'), auth=http_auth(self._papi, self._sapi)) if response.status_code != http.HTTPStatus.OK: raise urllib.error.HTTPError( url, response.status_code, http.HTTPStatus(response.status_code).phrase, response.headers, response.content) content = json.loads(response.content.decode('utf-8')) self._key = {} self._key['algo'] = algorithm(alg) # the client's id for recognizing key reuse self._key['client_id'] = client_id # the server's id for sending updates self._key['finger_print'] = content['key_fingerprint'] self._key['session'] = content['encryption_session'] # decrypt the client's private key (sent # by the server) prvkey = crypto.serialization.load_pem_private_key( content['encrypted_private_key'].encode('utf-8'), self._srsa.encode('utf-8'), crypto_backend()) # use the private key to decrypt the data key self._key['raw'] = prvkey.decrypt( base64.b64decode(content['wrapped_data_key']), crypto.asymmetric.padding.OAEP( mgf=crypto.asymmetric.padding.MGF1( algorithm=crypto.hashes.SHA1()), algorithm=crypto.hashes.SHA1(), label=None)) # this key hasn't been used (yet) self._key['uses'] = 0 # if the key object exists, create a new decryptor # with the initialization vector from the header and # the decrypted key (which is either new from the # server or cached from the previous decryption). in # either case, increment the key usage if hasattr(self, '_key'): self._key['dec'] = self._key['algo'].decryptor( self._key['raw'], vec) self._key['uses'] += 1 if (flags & algorithm.UBIQ_HEADER_V0_FLAG_AAD): self._key['dec'].authenticate_additional_data(aad) # if the object has a key and a decryptor, then decrypt whatever # data is in the buffer, less any data that needs to be saved to # serve as the tag. if hasattr(self, '_key') and 'dec' in self._key: sz = len(self._buf) - self._key['algo'].len['tag'] if sz > 0: pt = self._key['dec'].update(self._buf[:sz]) self._buf = self._buf[sz:] return pt
async def test_non_core_dev_does_not_downgrade(): core_dev = "brettcannon" non_core_dev = "andreamcinnes" teams = [{"name": "python core", "id": 6}] items = { f"https://api.github.com/teams/6/memberships/{non_core_dev}": gidgethub.BadRequest(status_code=http.HTTPStatus(404)), f"https://api.github.com/teams/6/memberships/{core_dev}": True, "https://api.github.com/issue/42": { "labels": [], "labels_url": "https://api.github.com/labels/42", }, } # Approval from a core dev changes the state to "Awaiting merge". data = { "action": "submitted", "review": { "state": "approved", "user": { "login": core_dev, }, }, "pull_request": { "url": "https://api.github.com/pr/42", "issue_url": "https://api.github.com/issue/42", "state": "open", }, } event = sansio.Event(data, event="pull_request_review", delivery_id="12345") iterators = { "https://api.github.com/orgs/python/teams": teams, "https://api.github.com/pr/42/reviews": [{ "user": { "login": core_dev }, "state": "approved" }], } gh = FakeGH(getiter=iterators, getitem=items) await awaiting.router.dispatch(event, gh) assert len(gh.post_) == 1 post_ = gh.post_[0] assert post_[0] == "https://api.github.com/labels/42" assert post_[1] == [awaiting.Blocker.merge.value] # Non-comment review from a non-core dev doesn't "downgrade" the PR's state. data = { "action": "submitted", "review": { "state": "approved", "user": { "login": non_core_dev, }, }, "pull_request": { "url": "https://api.github.com/pr/42", "issue_url": "https://api.github.com/issue/42", "state": "open", }, } event = sansio.Event(data, event="pull_request_review", delivery_id="12345") iterators = { "https://api.github.com/orgs/python/teams": teams, "https://api.github.com/pr/42/reviews": [ { "user": { "login": core_dev }, "state": "approved" }, { "user": { "login": non_core_dev }, "state": "approved" }, ], } gh = FakeGH(getiter=iterators, getitem=items) await awaiting.router.dispatch(event, gh) assert not gh.post_
def response( self, status_code, schema=None, *, content_type=None, description=None, example=None, examples=None, headers=None, ): """Decorator generating an endpoint response :param int|str|HTTPStatus status_code: HTTP status code. Used if none is returned from the view function. :param schema schema|str|dict: :class:`Schema <marshmallow.Schema>` class or instance or reference or dict. If not None, will be used to serialize response data. :param str content_type: Content type of the response. :param str description: Description of the response (default: None). :param dict example: Example of response message. :param dict examples: Examples of response message. :param dict headers: Headers returned by the response. The decorated function is expected to return the same types of value than a typical flask view function, except the body part may be an object or a list of objects to serialize with the schema, rather than a ``string``. If the decorated function returns a ``Response`` object, the ``schema`` and ``status_code`` parameters are only used to document the resource. Only in this case, ``schema`` may be a reference as string or a schema definition as dict. The `example` and `examples` parameters are mutually exclusive. The latter should only be used with OpenAPI 3. The `example`, `examples` and `headers` parameters are only used to document the resource. See :doc:`Response <response>`. """ if isinstance(schema, type): schema = schema() # Document response (schema, description,...) in the API doc doc_schema = self._make_doc_response_schema(schema) if description is None: description = http.HTTPStatus(int(status_code)).phrase resp_doc = remove_none({ "schema": doc_schema, "description": description, "example": example, "examples": examples, "headers": headers, }) resp_doc["content_type"] = content_type def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # Execute decorated function result_raw, r_status_code, r_headers = unpack_tuple_response( func(*args, **kwargs)) # If return value is a werkzeug Response, return it if isinstance(result_raw, Response): set_status_and_headers_in_response(result_raw, r_status_code, r_headers) return result_raw # Dump result with schema if specified if schema is None: result_dump = result_raw else: result_dump = schema.dump(result_raw) # Store result in appcontext (may be used for ETag computation) appcontext = get_appcontext() appcontext["result_raw"] = result_raw appcontext["result_dump"] = result_dump # Build response resp = jsonify(self._prepare_response_content(result_dump)) set_status_and_headers_in_response(resp, r_status_code, r_headers) if r_status_code is None: resp.status_code = status_code return resp # Store doc in wrapper function # The deepcopy avoids modifying the wrapped function doc wrapper._apidoc = deepcopy(getattr(wrapper, "_apidoc", {})) wrapper._apidoc.setdefault("response", {}).setdefault( "responses", {})[status_code] = resp_doc # Indicate this code is a success status code # Helps other decorators documenting success responses wrapper._apidoc.setdefault("success_status_codes", []).append(status_code) return wrapper return decorator
def run(self, command, **data): """Run the specified command. If no http_config given, it is read from the user config. The data kwargs are passed to the HTTP request. 'period' and 'table_name' are substituted, if None. :return: dict. See Server class for possible keys :raise: ValueError if invalid command given :raise: CommunicationError on e.g. timeouts or server-side errors, InvalidRequest on invalid requests """ period = data.pop("period", None) or default_period_name() host = self.http_config.get("host", DEFAULT_HOST) base_url = "{}{}".format(host, PERIODS_TAIL) period_url = "{}/{}".format(base_url, period) copy_url = "{}{}".format(host, COPY_TAIL) eid_url = "{}/{}/{}".format(period_url, data.get("table_name") or DEFAULT_TABLE, data.get("eid")) version_url = "{}/version".format(host) username = self.http_config.get("username") password = self.http_config.get("password") auth = None if username and password: auth = (username, password) kwargs = dict(auth=auth, timeout=DEFAULT_TIMEOUT) if command == "list": # Correctly send filters; allowing for server-side deserialization kwargs["json"] = json.dumps(data) else: kwargs["json"] = data or None if command == "list": url = period_url function = requests.get elif command == "rm": url = eid_url function = requests.delete elif command == "add": url = period_url function = requests.post elif command == "periods": url = base_url function = requests.post elif command == "copy": url = copy_url function = requests.post elif command == "get": url = eid_url function = requests.get elif command == "update": url = eid_url function = requests.patch elif command == "service": url = version_url function = requests.get else: raise ValueError("Unknown command: {}".format(command)) try: response = function(url, **kwargs) except requests.RequestException as e: raise CommunicationError("Error sending request: {}".format(e)) if response.ok: return response.json() else: try: # Get further information about error (see Server.run) error = response.json()["error"] except (json.JSONDecodeError, KeyError): error = "-" status_code = response.status_code if 400 <= status_code < 500: error_class = InvalidRequest else: error_class = CommunicationError message = "Error handling request. " +\ "Server returned '{} ({}): {}'".format( http.HTTPStatus(status_code).phrase, status_code, error) raise error_class(message)