Skip to content

Commit 9c9338b

Browse files
committed
Add accept parameters & constants
1 parent 0331b51 commit 9c9338b

File tree

5 files changed

+107
-14
lines changed

5 files changed

+107
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ v1.0.0 (in development)
3131
- Renamed `DistributionPackage.from_pep691_details()` to `from_json_data()`
3232
- `PyPISimple.stream_project_names()` now accepts JSON responses
3333
- Use pydantic internally to parse JSON responses
34+
- Added constants for passing to `PyPISimple` and its methods in order to
35+
specify the `Accept` header to send
3436

3537
v0.10.0 (2022-06-30)
3638
--------------------

docs/api.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ Constants
3838
.. autodata:: PYPI_SIMPLE_ENDPOINT
3939
.. autodata:: SUPPORTED_REPOSITORY_VERSION
4040

41+
:mailheader:`Accept` Header Values
42+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43+
44+
The following constants can be passed as the ``accept`` parameter of
45+
`PyPISimple` and some of its methods in order to indicate to the server which
46+
serialization format of the Simple API it should return:
47+
48+
.. autodata:: ACCEPT_ANY
49+
.. autodata:: ACCEPT_JSON_ONLY
50+
.. autodata:: ACCEPT_HTML_ONLY
51+
.. autodata:: ACCEPT_JSON_PREFERRED
52+
.. autodata:: ACCEPT_HTML_PREFERRED
53+
4154
Exceptions
4255
----------
4356
.. autoexception:: DigestMismatchError()

docs/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ v1.0.0 (in development)
4343
`~DistributionPackage.from_json_data()`
4444
- `PyPISimple.stream_project_names()` now accepts JSON responses
4545
- Use pydantic internally to parse JSON responses
46+
- Added constants for passing to `PyPISimple` and its methods in order to
47+
specify the :mailheader:`Accept` header to send
4648

4749
v0.10.0 (2022-06-30)
4850
--------------------

src/pypi_simple/__init__.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,47 @@
2626
#: The maximum supported simple repository version (See :pep:`629`)
2727
SUPPORTED_REPOSITORY_VERSION: str = "1.0"
2828

29+
#: :mailheader:`Accept` header value for accepting either the HTML or JSON
30+
#: serialization without a preference
31+
ACCEPT_ANY: str = ", ".join(
32+
[
33+
"application/vnd.pypi.simple.v1+json",
34+
"application/vnd.pypi.simple.v1+html",
35+
"text/html;q=0.01",
36+
]
37+
)
38+
39+
#: :mailheader:`Accept` header value for accepting only the JSON serialization
40+
ACCEPT_JSON_ONLY = "application/vnd.pypi.simple.v1+json"
41+
42+
#: :mailheader:`Accept` header value for accepting only the HTML serialization
43+
ACCEPT_HTML_ONLY = ", ".join(
44+
[
45+
"application/vnd.pypi.simple.v1+html",
46+
"text/html;q=0.01",
47+
]
48+
)
49+
50+
#: :mailheader:`Accept` header value for accepting either the HTML or JSON
51+
#: serialization with a preference for JSON
52+
ACCEPT_JSON_PREFERRED = ", ".join(
53+
[
54+
"application/vnd.pypi.simple.v1+json",
55+
"application/vnd.pypi.simple.v1+html;q=0.5",
56+
"text/html;q=0.01",
57+
]
58+
)
59+
60+
#: :mailheader:`Accept` header value for accepting either the HTML or JSON
61+
#: serialization with a preference for HTML
62+
ACCEPT_HTML_PREFERRED = ", ".join(
63+
[
64+
"application/vnd.pypi.simple.v1+html",
65+
"text/html;q=0.5",
66+
"application/vnd.pypi.simple.v1+json;q=0.1",
67+
]
68+
)
69+
2970
from .classes import DistributionPackage, IndexPage, ProjectPage
3071
from .client import NoSuchProjectError, PyPISimple
3172
from .errors import (

src/pypi_simple/client.py

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from mailbits import ContentType
99
from packaging.utils import canonicalize_name as normalize
1010
import requests
11-
from . import PYPI_SIMPLE_ENDPOINT, __url__, __version__
11+
from . import ACCEPT_ANY, PYPI_SIMPLE_ENDPOINT, __url__, __version__
1212
from .classes import DistributionPackage, IndexPage, ProjectPage
1313
from .errors import UnsupportedContentTypeError
1414
from .html_stream import parse_links_stream_response
@@ -25,14 +25,6 @@
2525
platform.python_version(),
2626
)
2727

28-
ACCEPT = ", ".join(
29-
[
30-
"application/vnd.pypi.simple.v1+json",
31-
"application/vnd.pypi.simple.v1+html",
32-
"text/html;q=0.01",
33-
]
34-
)
35-
3628

3729
class PyPISimple:
3830
"""
@@ -53,6 +45,10 @@ class PyPISimple:
5345
automatically close its session on exit, regardless of where the session
5446
object came from.
5547
48+
.. versionchanged:: 1.0.0
49+
50+
``accept`` parameter added
51+
5652
:param str endpoint: The base URL of the simple API instance to query;
5753
defaults to the base URL for PyPI's simple API
5854
@@ -63,13 +59,19 @@ class PyPISimple:
6359
6460
:param session: Optional `requests.Session` object to use instead of
6561
creating a fresh one
62+
63+
:param str accept:
64+
The :mailheader:`Accept` header to send in requests in order to specify
65+
what serialization format the server should return; defaults to
66+
`ACCEPT_ANY`
6667
"""
6768

6869
def __init__(
6970
self,
7071
endpoint: str = PYPI_SIMPLE_ENDPOINT,
7172
auth: Any = None,
7273
session: Optional[requests.Session] = None,
74+
accept: str = ACCEPT_ANY,
7375
) -> None:
7476
self.endpoint: str = endpoint.rstrip("/") + "/"
7577
self.s: requests.Session
@@ -80,6 +82,7 @@ def __init__(
8082
self.s.headers["User-Agent"] = USER_AGENT
8183
if auth is not None:
8284
self.s.auth = auth
85+
self.accept = accept
8386

8487
def __enter__(self) -> PyPISimple:
8588
return self
@@ -95,6 +98,7 @@ def __exit__(
9598
def get_index_page(
9699
self,
97100
timeout: float | tuple[float, float] | None = None,
101+
accept: Optional[str] = None,
98102
) -> IndexPage:
99103
"""
100104
Fetches the index/root page from the simple repository and returns an
@@ -105,8 +109,16 @@ def get_index_page(
105109
PyPI's project index file is very large and takes several seconds
106110
to parse. Use this method sparingly.
107111
112+
.. versionchanged:: 1.0.0
113+
114+
``accept`` parameter added
115+
108116
:param timeout: optional timeout to pass to the ``requests`` call
109117
:type timeout: float | tuple[float,float] | None
118+
:param Optional[str] accept:
119+
The :mailheader:`Accept` header to send in order to
120+
specify what serialization format the server should return;
121+
defaults to the value supplied on client instantiation
110122
:rtype: IndexPage
111123
:raises requests.HTTPError: if the repository responds with an HTTP
112124
error code
@@ -115,14 +127,17 @@ def get_index_page(
115127
:raises UnsupportedRepoVersionError: if the repository version has a
116128
greater major component than the supported repository version
117129
"""
118-
r = self.s.get(self.endpoint, timeout=timeout, headers={"Accept": ACCEPT})
130+
r = self.s.get(
131+
self.endpoint, timeout=timeout, headers={"Accept": accept or self.accept}
132+
)
119133
r.raise_for_status()
120134
return IndexPage.from_response(r)
121135

122136
def stream_project_names(
123137
self,
124138
chunk_size: int = 65535,
125139
timeout: float | tuple[float, float] | None = None,
140+
accept: Optional[str] = None,
126141
) -> Iterator[str]:
127142
"""
128143
Returns a generator of names of projects available in the repository.
@@ -145,10 +160,18 @@ def stream_project_names(
145160
rather than an HTML representation, the response body will be
146161
loaded & parsed in its entirety before yielding anything.
147162
163+
.. versionchanged:: 1.0.0
164+
165+
``accept`` parameter added
166+
148167
:param int chunk_size: how many bytes to read from the response at a
149168
time
150169
:param timeout: optional timeout to pass to the ``requests`` call
151170
:type timeout: float | tuple[float,float] | None
171+
:param Optional[str] accept:
172+
The :mailheader:`Accept` header to send in order to
173+
specify what serialization format the server should return;
174+
defaults to the value supplied on client instantiation
152175
:rtype: Iterator[str]
153176
:raises requests.HTTPError: if the repository responds with an HTTP
154177
error code
@@ -157,7 +180,12 @@ def stream_project_names(
157180
:raises UnsupportedRepoVersionError: if the repository version has a
158181
greater major component than the supported repository version
159182
"""
160-
with self.s.get(self.endpoint, stream=True, timeout=timeout) as r:
183+
with self.s.get(
184+
self.endpoint,
185+
stream=True,
186+
timeout=timeout,
187+
headers={"Accept": accept or self.accept},
188+
) as r:
161189
r.raise_for_status()
162190
ct = ContentType.parse(r.headers.get("content-type", "text/html"))
163191
if ct.content_type == "application/vnd.pypi.simple.v1+json":
@@ -176,6 +204,7 @@ def get_project_page(
176204
self,
177205
project: str,
178206
timeout: float | tuple[float, float] | None = None,
207+
accept: Optional[str] = None,
179208
) -> ProjectPage:
180209
"""
181210
Fetches the page for the given project from the simple repository and
@@ -185,13 +214,19 @@ def get_project_page(
185214
186215
.. versionchanged:: 1.0.0
187216
188-
A 404 now causes `NoSuchProjectError` to be raised instead of
189-
returning `None`
217+
- A 404 now causes `NoSuchProjectError` to be raised instead of
218+
returning `None`
219+
220+
- ``accept`` parameter added
190221
191222
:param str project: The name of the project to fetch information on.
192223
The name does not need to be normalized.
193224
:param timeout: optional timeout to pass to the ``requests`` call
194225
:type timeout: float | tuple[float,float] | None
226+
:param Optional[str] accept:
227+
The :mailheader:`Accept` header to send in order to
228+
specify what serialization format the server should return;
229+
defaults to the value supplied on client instantiation
195230
:rtype: ProjectPage
196231
:raises NoSuchProjectError: if the repository responds with a 404 error
197232
code
@@ -203,7 +238,7 @@ def get_project_page(
203238
greater major component than the supported repository version
204239
"""
205240
url = self.get_project_url(project)
206-
r = self.s.get(url, timeout=timeout, headers={"Accept": ACCEPT})
241+
r = self.s.get(url, timeout=timeout, headers={"Accept": accept or None})
207242
if r.status_code == 404:
208243
raise NoSuchProjectError(project, url)
209244
r.raise_for_status()

0 commit comments

Comments
 (0)