hbmartin commited on
Commit
3d00b79
·
1 Parent(s): 17564d0

type hinting for query, streams, and extract

Browse files
Files changed (4) hide show
  1. README.md +3 -3
  2. pytube/extract.py +4 -3
  3. pytube/query.py +9 -6
  4. pytube/streams.py +15 -15
README.md CHANGED
@@ -2,7 +2,7 @@
2
  <div align="center">
3
  <p align="center">
4
  <img src="https://img.shields.io/pypi/v/pytube3.svg" alt="pypi">
5
- <a href="https://travis-ci.org/hbmartin/pytube3"><img src="https://travis-ci.org/hbmartin/pytube3.svg?branch=master" /></a>
6
  <a href='https://pytube3.readthedocs.io/en/latest/?badge=latest'><img src='https://readthedocs.org/projects/pytube3/badge/?version=latest' alt='Documentation Status' /></a>
7
  <a href="https://coveralls.io/github/hbmartin/pytube3?branch=master"><img src="https://coveralls.io/repos/github/hbmartin/pytube3/badge.svg?branch=master" /></a>
8
  <a href="https://pypi.python.org/pypi/pytube3/"><img src="https://img.shields.io/pypi/pyversions/pytube3.svg" /></a>
@@ -239,11 +239,11 @@ Virtual environment is setup with [pipenv](https://pipenv-fork.readthedocs.io/en
239
 
240
  #### Code Formatting
241
 
242
- This project is linted with [pyflakes](https://github.com/PyCQA/pyflakes) and makes strict use of [Black](https://github.com/ambv/black) for code formatting.
243
 
244
  #### Testing
245
 
246
- This project is tested with [pytest](https://docs.pytest.org/en/latest/) and coverage is evaluated with [coveralls](https://coveralls-python.readthedocs.io/en/latest/index.html)
247
 
248
  #### Code of Conduct
249
 
 
2
  <div align="center">
3
  <p align="center">
4
  <img src="https://img.shields.io/pypi/v/pytube3.svg" alt="pypi">
5
+ <a href="https://travis-ci.com/hbmartin/pytube3/"><img src="https://travis-ci.org/hbmartin/pytube3.svg?branch=master" /></a>
6
  <a href='https://pytube3.readthedocs.io/en/latest/?badge=latest'><img src='https://readthedocs.org/projects/pytube3/badge/?version=latest' alt='Documentation Status' /></a>
7
  <a href="https://coveralls.io/github/hbmartin/pytube3?branch=master"><img src="https://coveralls.io/repos/github/hbmartin/pytube3/badge.svg?branch=master" /></a>
8
  <a href="https://pypi.python.org/pypi/pytube3/"><img src="https://img.shields.io/pypi/pyversions/pytube3.svg" /></a>
 
239
 
240
  #### Code Formatting
241
 
242
+ This project is linted with [pyflakes](https://github.com/PyCQA/pyflakes), formatted with [Black](https://github.com/ambv/black), and typed with [mypy](https://mypy.readthedocs.io/en/latest/introduction.html)
243
 
244
  #### Testing
245
 
246
+ This project is tested with [pytest](https://docs.pytest.org/en/latest/) and coverage is evaluated with [coveralls](https://coveralls-python.readthedocs.io/en/latest/index.html).
247
 
248
  #### Code of Conduct
249
 
pytube/extract.py CHANGED
@@ -4,6 +4,7 @@ import json
4
  from collections import OrderedDict
5
 
6
  from html.parser import HTMLParser
 
7
  from urllib.parse import quote
8
  from urllib.parse import urlencode
9
  from pytube.exceptions import RegexMatchError
@@ -177,14 +178,14 @@ def mime_type_codec(mime_type_codec):
177
  return mime_type, [c.strip() for c in codecs.split(",")]
178
 
179
 
180
- def get_ytplayer_config(html, age_restricted=False):
181
  """Get the YouTube player configuration data from the watch html.
182
 
183
  Extract the ``ytplayer_config``, which is json data embedded within the
184
  watch html and serves as the primary source of obtaining the stream
185
  manifest data.
186
 
187
- :param str watch_html:
188
  The html contents of the watch page.
189
  :param bool age_restricted:
190
  Is video age restricted.
@@ -200,7 +201,7 @@ def get_ytplayer_config(html, age_restricted=False):
200
  return json.loads(yt_player_config)
201
 
202
 
203
- def get_vid_descr(html):
204
  html_parser = PytubeHTMLParser()
205
  html_parser.feed(html)
206
  return html_parser.vid_descr
 
4
  from collections import OrderedDict
5
 
6
  from html.parser import HTMLParser
7
+ from typing import Any
8
  from urllib.parse import quote
9
  from urllib.parse import urlencode
10
  from pytube.exceptions import RegexMatchError
 
178
  return mime_type, [c.strip() for c in codecs.split(",")]
179
 
180
 
181
+ def get_ytplayer_config(html: str, age_restricted: bool = False) -> Any:
182
  """Get the YouTube player configuration data from the watch html.
183
 
184
  Extract the ``ytplayer_config``, which is json data embedded within the
185
  watch html and serves as the primary source of obtaining the stream
186
  manifest data.
187
 
188
+ :param str html:
189
  The html contents of the watch page.
190
  :param bool age_restricted:
191
  Is video age restricted.
 
201
  return json.loads(yt_player_config)
202
 
203
 
204
+ def get_vid_descr(html: str) -> str:
205
  html_parser = PytubeHTMLParser()
206
  html_parser.feed(html)
207
  return html_parser.vid_descr
pytube/query.py CHANGED
@@ -1,5 +1,8 @@
1
  # -*- coding: utf-8 -*-
2
  """This module provides a query interface for media streams and captions."""
 
 
 
3
 
4
 
5
  class StreamQuery:
@@ -210,7 +213,7 @@ class StreamQuery:
210
  def get_by_itag(self, itag):
211
  """Get the corresponding :class:`Stream <Stream>` for a given itag.
212
 
213
- :param str int itag:
214
  YouTube format identifier code.
215
  :rtype: :class:`Stream <Stream>` or None
216
  :returns:
@@ -251,7 +254,7 @@ class StreamQuery:
251
  except IndexError:
252
  pass
253
 
254
- def count(self):
255
  """Get the count the query would return.
256
 
257
  :rtype: int
@@ -259,7 +262,7 @@ class StreamQuery:
259
  """
260
  return len(self.fmt_streams)
261
 
262
- def all(self):
263
  """Get all the results represented by this query as a list.
264
 
265
  :rtype: list
@@ -271,7 +274,7 @@ class StreamQuery:
271
  class CaptionQuery:
272
  """Interface for querying the available captions."""
273
 
274
- def __init__(self, captions):
275
  """Construct a :class:`Caption <Caption>`.
276
 
277
  param list captions:
@@ -281,7 +284,7 @@ class CaptionQuery:
281
  self.captions = captions
282
  self.lang_code_index = {c.code: c for c in captions}
283
 
284
- def get_by_language_code(self, lang_code):
285
  """Get the :class:`Caption <Caption>` for a given ``lang_code``.
286
 
287
  :param str lang_code:
@@ -293,7 +296,7 @@ class CaptionQuery:
293
  """
294
  return self.lang_code_index.get(lang_code)
295
 
296
- def all(self):
297
  """Get all the results represented by this query as a list.
298
 
299
  :rtype: list
 
1
  # -*- coding: utf-8 -*-
2
  """This module provides a query interface for media streams and captions."""
3
+ from typing import List, Optional
4
+
5
+ from pytube import Stream, Caption
6
 
7
 
8
  class StreamQuery:
 
213
  def get_by_itag(self, itag):
214
  """Get the corresponding :class:`Stream <Stream>` for a given itag.
215
 
216
+ :param int itag:
217
  YouTube format identifier code.
218
  :rtype: :class:`Stream <Stream>` or None
219
  :returns:
 
254
  except IndexError:
255
  pass
256
 
257
+ def count(self) -> int:
258
  """Get the count the query would return.
259
 
260
  :rtype: int
 
262
  """
263
  return len(self.fmt_streams)
264
 
265
+ def all(self) -> List[Stream]:
266
  """Get all the results represented by this query as a list.
267
 
268
  :rtype: list
 
274
  class CaptionQuery:
275
  """Interface for querying the available captions."""
276
 
277
+ def __init__(self, captions: List[Caption]):
278
  """Construct a :class:`Caption <Caption>`.
279
 
280
  param list captions:
 
284
  self.captions = captions
285
  self.lang_code_index = {c.code: c for c in captions}
286
 
287
+ def get_by_language_code(self, lang_code: str) -> Optional[Caption]:
288
  """Get the :class:`Caption <Caption>` for a given ``lang_code``.
289
 
290
  :param str lang_code:
 
296
  """
297
  return self.lang_code_index.get(lang_code)
298
 
299
+ def all(self) -> List[Caption]:
300
  """Get all the results represented by this query as a list.
301
 
302
  :rtype: list
pytube/streams.py CHANGED
@@ -12,6 +12,7 @@ import io
12
  import logging
13
  import os
14
  import pprint
 
15
 
16
  from pytube import extract
17
  from pytube import request
@@ -25,7 +26,7 @@ logger = logging.getLogger(__name__)
25
  class Stream(object):
26
  """Container for stream manifest data."""
27
 
28
- def __init__(self, stream, player_config_args, monostate):
29
  """Construct a :class:`Stream <Stream>`.
30
 
31
  :param dict stream:
@@ -47,7 +48,7 @@ class Stream(object):
47
  self.res = None # resolution (e.g.: 480p, 720p, 1080p)
48
  self.url = None # signed download url
49
 
50
- self._filesize = None # filesize in bytes
51
  self.mime_type = None # content identifier (e.g.: video/mp4)
52
  self.type = None # the part of the mime before the slash
53
  self.subtype = None # the part of the mime after the slash
@@ -82,7 +83,7 @@ class Stream(object):
82
  # streams return NoneType for audio/video depending.
83
  self.video_codec, self.audio_codec = self.parse_codecs()
84
 
85
- def set_attributes_from_dict(self, dct):
86
  """Set class attributes from dictionary items.
87
 
88
  :rtype: None
@@ -91,17 +92,17 @@ class Stream(object):
91
  setattr(self, key, val)
92
 
93
  @property
94
- def is_adaptive(self):
95
  """Whether the stream is DASH.
96
 
97
  :rtype: bool
98
  """
99
  # if codecs has two elements (e.g.: ['vp8', 'vorbis']): 2 % 2 = 0
100
  # if codecs has one element (e.g.: ['vp8']) 1 % 2 = 1
101
- return len(self.codecs) % 2
102
 
103
  @property
104
- def is_progressive(self):
105
  """Whether the stream is progressive.
106
 
107
  :rtype: bool
@@ -109,7 +110,7 @@ class Stream(object):
109
  return not self.is_adaptive
110
 
111
  @property
112
- def includes_audio_track(self):
113
  """Whether the stream only contains audio.
114
 
115
  :rtype: bool
@@ -119,7 +120,7 @@ class Stream(object):
119
  return self.type == "audio"
120
 
121
  @property
122
- def includes_video_track(self):
123
  """Whether the stream only contains video.
124
 
125
  :rtype: bool
@@ -128,7 +129,7 @@ class Stream(object):
128
  return True
129
  return self.type == "video"
130
 
131
- def parse_codecs(self):
132
  """Get the video/audio codecs from list of codecs.
133
 
134
  Parse a variable length sized list of codecs and returns a
@@ -152,7 +153,7 @@ class Stream(object):
152
  return video, audio
153
 
154
  @property
155
- def filesize(self):
156
  """File size of the media stream in bytes.
157
 
158
  :rtype: int
@@ -165,7 +166,7 @@ class Stream(object):
165
  return self._filesize
166
 
167
  @property
168
- def title(self):
169
  """Get title of video
170
 
171
  :rtype: str
@@ -187,7 +188,7 @@ class Stream(object):
187
  return "Unknown YouTube Video Title"
188
 
189
  @property
190
- def default_filename(self):
191
  """Generate filename based on the video title.
192
 
193
  :rtype: str
@@ -316,7 +317,7 @@ class Stream(object):
316
  logger.debug("calling on_complete callback %s", on_complete)
317
  on_complete(self, file_handle)
318
 
319
- def __repr__(self):
320
  """Printable object representation.
321
 
322
  :rtype: str
@@ -335,5 +336,4 @@ class Stream(object):
335
  parts.extend(['vcodec="{s.video_codec}"'])
336
  else:
337
  parts.extend(['abr="{s.abr}"', 'acodec="{s.audio_codec}"'])
338
- parts = " ".join(parts).format(s=self)
339
- return "<Stream: {parts}>".format(parts=parts)
 
12
  import logging
13
  import os
14
  import pprint
15
+ from typing import Dict, Tuple, Optional
16
 
17
  from pytube import extract
18
  from pytube import request
 
26
  class Stream(object):
27
  """Container for stream manifest data."""
28
 
29
+ def __init__(self, stream: Dict, player_config_args: Dict, monostate: Dict):
30
  """Construct a :class:`Stream <Stream>`.
31
 
32
  :param dict stream:
 
48
  self.res = None # resolution (e.g.: 480p, 720p, 1080p)
49
  self.url = None # signed download url
50
 
51
+ self._filesize: Optional[int] = None # filesize in bytes
52
  self.mime_type = None # content identifier (e.g.: video/mp4)
53
  self.type = None # the part of the mime before the slash
54
  self.subtype = None # the part of the mime after the slash
 
83
  # streams return NoneType for audio/video depending.
84
  self.video_codec, self.audio_codec = self.parse_codecs()
85
 
86
+ def set_attributes_from_dict(self, dct: Dict):
87
  """Set class attributes from dictionary items.
88
 
89
  :rtype: None
 
92
  setattr(self, key, val)
93
 
94
  @property
95
+ def is_adaptive(self) -> bool:
96
  """Whether the stream is DASH.
97
 
98
  :rtype: bool
99
  """
100
  # if codecs has two elements (e.g.: ['vp8', 'vorbis']): 2 % 2 = 0
101
  # if codecs has one element (e.g.: ['vp8']) 1 % 2 = 1
102
+ return bool(len(self.codecs) % 2)
103
 
104
  @property
105
+ def is_progressive(self) -> bool:
106
  """Whether the stream is progressive.
107
 
108
  :rtype: bool
 
110
  return not self.is_adaptive
111
 
112
  @property
113
+ def includes_audio_track(self) -> bool:
114
  """Whether the stream only contains audio.
115
 
116
  :rtype: bool
 
120
  return self.type == "audio"
121
 
122
  @property
123
+ def includes_video_track(self) -> bool:
124
  """Whether the stream only contains video.
125
 
126
  :rtype: bool
 
129
  return True
130
  return self.type == "video"
131
 
132
+ def parse_codecs(self) -> Tuple:
133
  """Get the video/audio codecs from list of codecs.
134
 
135
  Parse a variable length sized list of codecs and returns a
 
153
  return video, audio
154
 
155
  @property
156
+ def filesize(self) -> int:
157
  """File size of the media stream in bytes.
158
 
159
  :rtype: int
 
166
  return self._filesize
167
 
168
  @property
169
+ def title(self) -> str:
170
  """Get title of video
171
 
172
  :rtype: str
 
188
  return "Unknown YouTube Video Title"
189
 
190
  @property
191
+ def default_filename(self) -> str:
192
  """Generate filename based on the video title.
193
 
194
  :rtype: str
 
317
  logger.debug("calling on_complete callback %s", on_complete)
318
  on_complete(self, file_handle)
319
 
320
+ def __repr__(self) -> str:
321
  """Printable object representation.
322
 
323
  :rtype: str
 
336
  parts.extend(['vcodec="{s.video_codec}"'])
337
  else:
338
  parts.extend(['abr="{s.abr}"', 'acodec="{s.audio_codec}"'])
339
+ return "<Stream: {parts}>".format(parts=" ".join(parts).format(s=self))