Skip to content

Commit ae378e2

Browse files
feat(typing): add type hints (#52)
* Add type hints. * chore: fix typo * Remove old type declaration from dostrs. * Fix generic types. * Move the module to a package and add `py.typed` file. * Make mypy happy. * PEP 563.
1 parent 3dc9383 commit ae378e2

File tree

3 files changed

+23
-28
lines changed

3 files changed

+23
-28
lines changed

mimeparse.py renamed to mimeparse/__init__.py

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1+
from __future__ import annotations
2+
13
__version__ = '2.0.0'
24
__author__ = 'Joe Gregorio'
35
__email__ = '[email protected]'
46
__license__ = 'MIT License'
57
__credits__ = ''
68

9+
from typing import Dict, Generator, Iterable, Tuple, Union
10+
711

812
class MimeTypeParseException(ValueError):
913
pass
1014

1115

1216
# Vendored version of cgi._parseparam from Python 3.11 (deprecated and slated
1317
# for removal in 3.13)
14-
def _parseparam(s):
18+
def _parseparam(s: str) -> Generator[str, None, None]:
1519
while s[:1] == ';':
1620
s = s[1:]
1721
end = s.find(';')
@@ -26,7 +30,7 @@ def _parseparam(s):
2630

2731
# Vendored version of cgi.parse_header from Python 3.11 (deprecated and slated
2832
# for removal in 3.13)
29-
def _parse_header(line):
33+
def _parse_header(line: str) -> Tuple[str, Dict[str, str]]:
3034
"""Parse a Content-type like header.
3135
3236
Return the main content-type and a dictionary of options.
@@ -47,7 +51,7 @@ def _parse_header(line):
4751
return key, pdict
4852

4953

50-
def parse_mime_type(mime_type):
54+
def parse_mime_type(mime_type: str) -> Tuple[str, str, Dict[str, str]]:
5155
"""Parses a mime-type into its component parts.
5256
5357
Carves up a mime-type and returns a tuple of the (type, subtype, params)
@@ -56,8 +60,6 @@ def parse_mime_type(mime_type):
5660
into:
5761
5862
('application', 'xhtml', {'q', '0.5'})
59-
60-
:rtype: (str,str,dict)
6163
"""
6264
full_type, params = _parse_header(mime_type)
6365
# Java URLConnection class sends an Accept header that includes a
@@ -75,7 +77,7 @@ def parse_mime_type(mime_type):
7577
return (type.strip(), subtype.strip(), params)
7678

7779

78-
def parse_media_range(range):
80+
def parse_media_range(range: str) -> Tuple[str, str, Dict[str, str]]:
7981
"""Parse a media-range into its component parts.
8082
8183
Carves up a media range and returns a tuple of the (type, subtype,
@@ -88,11 +90,9 @@ def parse_media_range(range):
8890
In addition this function also guarantees that there is a value for 'q'
8991
in the params dictionary, filling it in with a proper default if
9092
necessary.
91-
92-
:rtype: (str,str,dict)
9393
"""
9494
(type, subtype, params) = parse_mime_type(range)
95-
params.setdefault('q', params.pop('Q', None)) # q is case insensitive
95+
params.setdefault('q', params.pop('Q', '1')) # q is case insensitive
9696
try:
9797
if not params['q'] or not 0 <= float(params['q']) <= 1:
9898
params['q'] = '1'
@@ -102,19 +102,20 @@ def parse_media_range(range):
102102
return (type, subtype, params)
103103

104104

105-
def quality_and_fitness_parsed(mime_type, parsed_ranges):
105+
def quality_and_fitness_parsed(
106+
mime_type: str,
107+
parsed_ranges: Iterable[Tuple[str, str, Dict[str, str]]],
108+
) -> Tuple[float, float]:
106109
"""Find the best match for a mime-type amongst parsed media-ranges.
107110
108111
Find the best match for a given mime-type against a list of media_ranges
109112
that have already been parsed by parse_media_range(). Returns a tuple of
110113
the fitness value and the value of the 'q' quality parameter of the best
111114
match, or (-1, 0) if no match was found. Just as for quality_parsed(),
112115
'parsed_ranges' must be a list of parsed media ranges.
113-
114-
:rtype: (float,int)
115116
"""
116-
best_fitness = -1
117-
best_fit_q = 0
117+
best_fitness = -1.
118+
best_fit_q: Union[float, str] = 0.
118119
(target_type, target_subtype, target_params) = \
119120
parse_media_range(mime_type)
120121

@@ -129,10 +130,10 @@ def quality_and_fitness_parsed(mime_type, parsed_ranges):
129130
if type_match and subtype_match:
130131

131132
# 100 points if the type matches w/o a wildcard
132-
fitness = type == target_type and 100 or 0
133+
fitness = type == target_type and 100. or 0.
133134

134135
# 10 points if the subtype matches w/o a wildcard
135-
fitness += subtype == target_subtype and 10 or 0
136+
fitness += subtype == target_subtype and 10. or 0.
136137

137138
# 1 bonus point for each matching param besides "q"
138139
param_matches = sum([
@@ -151,39 +152,35 @@ def quality_and_fitness_parsed(mime_type, parsed_ranges):
151152
return float(best_fit_q), best_fitness
152153

153154

154-
def quality_parsed(mime_type, parsed_ranges):
155+
def quality_parsed(mime_type: str, parsed_ranges: Iterable[Tuple[str, str, Dict[str, str]]]) -> float:
155156
"""Find the best match for a mime-type amongst parsed media-ranges.
156157
157158
Find the best match for a given mime-type against a list of media_ranges
158159
that have already been parsed by parse_media_range(). Returns the 'q'
159160
quality parameter of the best match, 0 if no match was found. This function
160161
behaves the same as quality() except that 'parsed_ranges' must be a list of
161162
parsed media ranges.
162-
163-
:rtype: float
164163
"""
165164

166165
return quality_and_fitness_parsed(mime_type, parsed_ranges)[0]
167166

168167

169-
def quality(mime_type, ranges):
168+
def quality(mime_type: str, ranges: str) -> float:
170169
"""Return the quality ('q') of a mime-type against a list of media-ranges.
171170
172171
Returns the quality 'q' of a mime-type when compared against the
173172
media-ranges in ranges. For example:
174173
175-
>>> quality('text/html','text/*;q=0.3, text/html;q=0.7,
174+
>>> quality('text/html','text/*;q=0.3, text/html;q=0.7',
176175
text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5')
177176
0.7
178-
179-
:rtype: float
180177
"""
181178
parsed_ranges = [parse_media_range(r) for r in ranges.split(',')]
182179

183180
return quality_parsed(mime_type, parsed_ranges)
184181

185182

186-
def best_match(supported, header):
183+
def best_match(supported: Iterable[str], header: str) -> str:
187184
"""Return mime-type with the highest quality ('q') from list of candidates.
188185
189186
Takes a list of supported mime-types and finds the best match for all the
@@ -196,8 +193,6 @@ def best_match(supported, header):
196193
>>> best_match(['application/xbel+xml', 'text/xml'],
197194
'text/*;q=0.5,*/*; q=0.1')
198195
'text/xml'
199-
200-
:rtype: str
201196
"""
202197
split_header = _filter_blank(header.split(','))
203198
parsed_header = [parse_media_range(r) for r in split_header]
@@ -215,7 +210,7 @@ def best_match(supported, header):
215210
return weighted_matches[-1][0][0] and weighted_matches[-1][2] or ''
216211

217212

218-
def _filter_blank(i):
213+
def _filter_blank(i: Iterable[str]) -> Generator[str, None, None]:
219214
"""Return all non-empty items in the list."""
220215
for s in i:
221216
if s.strip():

mimeparse/py.typed

Whitespace-only changes.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Chat = "https://gitter.im/falconry/user"
5151

5252
[tool.setuptools]
5353
license-files = ["LICENSE"]
54-
py-modules = ["mimeparse"]
54+
packages = ["mimeparse"]
5555

5656
[tool.setuptools.dynamic]
5757
version = {attr = "mimeparse.__version__"}

0 commit comments

Comments
 (0)