Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions docs/abc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ experimental APIs without issue.
:meth:`getitem`.


.. coroutine:: post(url, url_vars={}, *, data, accept=sansio.accept_format(), jwt=None, oauth_token=None)
.. coroutine:: post(url, url_vars={}, *, data, accept=sansio.accept_format(), jwt=None, oauth_token=None, content_type="application/json")

Send a ``POST`` request to GitHub.

Expand All @@ -185,8 +185,19 @@ experimental APIs without issue.
raised if both are passed. If neither was passed, it defaults to the
value of the *oauth_token* attribute.

*content_type* is the value of the desired request header's content type.
If supplied, the data will be passed as the body in its raw format.
If not supplied, it will assume the default "application/json" content type,
and the data will be parsed as JSON.

A few GitHub POST endpoints do not take any *data* argument, for example
the endpoint to `create an installation access token <https://developer.github.com/v3/apps/#create-a-github-app-from-a-manifest>`_. For this situation, you can pass ``data=b""``.
the endpoint to `create an installation access token <https://developer.github.com/v3/apps/#create-a-github-app-from-a-manifest>`_.
For this situation, you can pass ``data=b""``.


.. versionchanged:: 4.12
Added *content_type*.


.. versionchanged:: 3.0

Expand Down
10 changes: 10 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
Changelog
=========

4.2.0
-----

- :meth:`gidgethub.abc.GitHubAPI.post` now accepts ``content_type`` parameter.
If supplied, the ``content_type`` value will be used in the request headers,
and the raw form of the data will be passed to the request. If not supplied,
by default the data will be parsed as JSON, and the "application/json" content
type will be used. (`Issue #115 <https://github.com/brettcannon/gidgethub/issues/115>`_).


4.1.1
-----

Expand Down
2 changes: 1 addition & 1 deletion gidgethub/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""An async GitHub API library"""
__version__ = "4.1.1"
__version__ = "4.2.0"

import http
from typing import Any, Optional
Expand Down
30 changes: 23 additions & 7 deletions gidgethub/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
# Value represents etag, last-modified, data, and next page.
CACHE_TYPE = MutableMapping[str, Tuple[Opt[str], Opt[str], Any, Opt[str]]]

_json_content_type = "application/json; charset=utf-8"
JSON_CONTENT_TYPE = "application/json"
UTF_8_CHARSET = "utf-8"
JSON_UTF_8_CHARSET = f"{JSON_CONTENT_TYPE}; charset={UTF_8_CHARSET}"


class GitHubAPI(abc.ABC):
Expand Down Expand Up @@ -59,6 +61,7 @@ async def _make_request(
accept: str,
jwt: Opt[str] = None,
oauth_token: Opt[str] = None,
content_type: str = JSON_CONTENT_TYPE,
) -> Tuple[bytes, Opt[str]]:
"""Construct and make an HTTP request."""
if oauth_token is not None and jwt is not None:
Expand Down Expand Up @@ -95,9 +98,14 @@ async def _make_request(
if last_modified is not None:
request_headers["if-modified-since"] = last_modified
else:
charset = "utf-8"
body = json.dumps(data).encode(charset)
request_headers["content-type"] = f"application/json; charset={charset}"
if content_type != JSON_CONTENT_TYPE:
# We don't know how to handle other content types, so just pass things along.
request_headers["content-type"] = content_type
body = data
else:
# Since JSON is so common, add some niceties.
body = json.dumps(data).encode(UTF_8_CHARSET)
request_headers["content-type"] = JSON_UTF_8_CHARSET
request_headers["content-length"] = str(len(body))
if self.rate_limit is not None:
self.rate_limit.remaining -= 1
Expand Down Expand Up @@ -162,9 +170,17 @@ async def post(
accept: str = sansio.accept_format(),
jwt: Opt[str] = None,
oauth_token: Opt[str] = None,
content_type: str = JSON_CONTENT_TYPE,
) -> Any:
data, _ = await self._make_request(
"POST", url, url_vars, data, accept, jwt=jwt, oauth_token=oauth_token
"POST",
url,
url_vars,
data,
accept,
jwt=jwt,
oauth_token=oauth_token,
content_type=content_type,
)
return data

Expand Down Expand Up @@ -229,11 +245,11 @@ async def graphql(
payload["variables"] = variables
request_data = json.dumps(payload).encode("utf-8")
request_headers = sansio.create_headers(
self.requester, accept=_json_content_type, oauth_token=self.oauth_token
self.requester, accept=JSON_UTF_8_CHARSET, oauth_token=self.oauth_token
)
request_headers.update(
{
"content-type": _json_content_type,
"content-type": JSON_UTF_8_CHARSET,
"content-length": str(len(request_data)),
}
)
Expand Down
24 changes: 18 additions & 6 deletions tests/test_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@

from .samples import GraphQL as graphql_samples

from gidgethub.abc import JSON_UTF_8_CHARSET


class MockGitHubAPI(gh_abc.GitHubAPI):

DEFAULT_HEADERS = {
"x-ratelimit-limit": "2",
"x-ratelimit-remaining": "1",
"x-ratelimit-reset": "0",
"content-type": "application/json; charset=utf-8",
"content-type": JSON_UTF_8_CHARSET,
}

def __init__(
Expand Down Expand Up @@ -343,42 +345,41 @@ async def test_post(self):
send_json = json.dumps(send).encode("utf-8")
receive = {"hello": "world"}
headers = MockGitHubAPI.DEFAULT_HEADERS.copy()
headers["content-type"] = "application/json; charset=utf-8"
gh = MockGitHubAPI(headers=headers, body=json.dumps(receive).encode("utf-8"))
await gh.post("/fake", data=send)
assert gh.method == "POST"
assert gh.headers["content-type"] == "application/json; charset=utf-8"
assert gh.body == send_json
assert gh.headers["content-length"] == str(len(send_json))
assert gh.headers["content-type"] == JSON_UTF_8_CHARSET

@pytest.mark.asyncio
async def test_with_passed_jwt(self):
send = [1, 2, 3]
receive = {"hello": "world"}
headers = MockGitHubAPI.DEFAULT_HEADERS.copy()
headers["content-type"] = "application/json; charset=utf-8"
gh = MockGitHubAPI(headers=headers, body=json.dumps(receive).encode("utf-8"))
await gh.post("/fake", data=send, jwt="json web token")
assert gh.method == "POST"
assert gh.headers["authorization"] == "bearer json web token"
assert gh.headers["content-type"] == JSON_UTF_8_CHARSET

@pytest.mark.asyncio
async def test_with_passed_oauth_token(self):
send = [1, 2, 3]
receive = {"hello": "world"}
headers = MockGitHubAPI.DEFAULT_HEADERS.copy()
headers["content-type"] = "application/json; charset=utf-8"
gh = MockGitHubAPI(headers=headers, body=json.dumps(receive).encode("utf-8"))
await gh.post("/fake", data=send, oauth_token="my oauth token")
assert gh.method == "POST"
assert gh.headers["authorization"] == "token my oauth token"
assert gh.headers["content-type"] == JSON_UTF_8_CHARSET

@pytest.mark.asyncio
async def test_cannot_pass_both_oauth_and_jwt(self):
send = [1, 2, 3]
receive = {"hello": "world"}
headers = MockGitHubAPI.DEFAULT_HEADERS.copy()
headers["content-type"] = "application/json; charset=utf-8"
gh = MockGitHubAPI(headers=headers, body=json.dumps(receive).encode("utf-8"))
with pytest.raises(ValueError) as exc_info:
await gh.post(
Expand All @@ -387,6 +388,17 @@ async def test_cannot_pass_both_oauth_and_jwt(self):

assert str(exc_info.value) == "Cannot pass both oauth_token and jwt."

@pytest.mark.asyncio
async def test_with_passed_content_type(self):
"""Assert that the body is not parsed to JSON and the content-type header is set."""
gh = MockGitHubAPI()
data = "blabla"
await gh.post("/fake", data=data, content_type="application/zip")
assert gh.method == "POST"
assert gh.headers["content-type"] == "application/zip"
assert gh.body == data
assert gh.headers["content-length"] == str(len(data))


class TestGitHubAPIPatch:
@pytest.mark.asyncio
Expand Down Expand Up @@ -842,6 +854,6 @@ async def test_no_response_content_type_gh121(self):
@pytest.mark.asyncio
async def test_no_response_data(self):
# An empty response should raise an exception.
gh = MockGitHubAPI(200, body=b"", oauth_token="oauth-token",)
gh = MockGitHubAPI(200, body=b"", oauth_token="oauth-token")
with pytest.raises(GraphQLException):
await gh.graphql("does not matter")