Added docker support

This commit is contained in:
Jett Jackson
2018-10-30 14:15:49 +08:00
parent 97e7e792f8
commit 84b0c2e861
108 changed files with 240 additions and 16 deletions

0
app/external/__init__.py vendored Normal file
View File

19
app/external/pytaw/.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
*.bak
*.egg
*.egg-info/
*.eggs/
*.pyproj
*.sln
*.vs/
*~
.DS_Store
.cache/
.coverage
.idea/
.tox/
_build/
build/
dist/
__pycache__/
*.ini

3
app/external/pytaw/.pytaw.conf vendored Normal file
View File

@ -0,0 +1,3 @@
; by default pytaw will look for this file (".pytaw.conf") in the user's home directory
[youtube]
developer_key = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

29
app/external/pytaw/README.md vendored Normal file
View File

@ -0,0 +1,29 @@
# PYTAW: Python YouTube API Wrapper
###Note
This library is copied from [https://github.com/chibicitiberiu/pytaw/tree/improvements](https://github.com/chibicitiberiu/pytaw/tree/improvements).
```python
>>> from pytaw import YouTube
>>> youtube = YouTube(key='your_api_key')
>>> video = youtube.video('4vuW6tQ0218')
>>> video.title
'Monty Python - Dead Parrot'
>>> video.published_at
datetime.datetime(2007, 2, 14, 13, 55, 51, tzinfo=tzutc())
>>> channel = video.channel
>>> channel.title
'Chadner'
>>> search = youtube.search(q='monty python')
>>> search[0]
<Channel UCGm3CO6LPcN-Y7HIuyE0Rew "Monty Python">
>>> for r in search[:5]:
... print(r)
...
Monty Python
Chemist Sketch - Monty Python's Flying Circus
A Selection of Sketches from "Monty Python's Flying Circus" - #4
Monty Python - Dead Parrot
Monty Python And the holy grail
```

0
app/external/pytaw/__init__.py vendored Normal file
View File

20
app/external/pytaw/docs/Makefile vendored Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = pytaw
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

169
app/external/pytaw/docs/conf.py vendored Normal file
View File

@ -0,0 +1,169 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# pytaw documentation build configuration file, created by
# sphinx-quickstart on Mon Nov 27 19:26:35 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.autodoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'pytaw'
copyright = '2017, 6000hulls'
author = '6000hulls'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.1'
# The full version, including alpha/beta/rc tags.
release = '0.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
'**': [
'relations.html', # needs 'show_related': True theme option to display
'searchbox.html',
]
}
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'pytawdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'pytaw.tex', 'pytaw Documentation',
'6000hulls', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'pytaw', 'pytaw Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'pytaw', 'pytaw Documentation',
author, 'pytaw', 'One line description of project.',
'Miscellaneous'),
]

18
app/external/pytaw/docs/index.rst vendored Normal file
View File

@ -0,0 +1,18 @@
PYTAW: Python YouTube API Wrapper
=================================
It's a wrapper for the YouTube python API. Written in python.
.. automodule:: pytaw.youtube
:members:
.. toctree::
:maxdepth: 2
:caption: Contents:
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

36
app/external/pytaw/docs/make.bat vendored Normal file
View File

@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=pytaw
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

13
app/external/pytaw/main_test.py vendored Normal file
View File

@ -0,0 +1,13 @@
import pytaw
yt = pytaw.YouTube(key='AIzaSyBabzE4Bup77WexdLMa9rN9z-wJidEfNX8')
c = yt.channel('UCmmPgObSUPw1HL2lq6H4ffA')
uploads_playlist = c.uploads_playlist
print(repr(uploads_playlist))
uploads_list = list(uploads_playlist.items)
for item in uploads_list:
print(item.position, '...', repr(item), ' .... ', repr(item.video))
print(item.thumbnails)
break

1
app/external/pytaw/pytaw/__init__.py vendored Normal file
View File

@ -0,0 +1 @@
from .youtube import YouTube

92
app/external/pytaw/pytaw/utils.py vendored Normal file
View File

@ -0,0 +1,92 @@
import re
import urllib.parse
import typing
from datetime import datetime, timezone
import dateutil.parser
import itertools
def string_to_datetime(string):
if string is None:
return None
else:
return dateutil.parser.parse(string)
def datetime_to_string(dt):
if dt is None:
return None
if dt.tzinfo is None:
dt = dt.astimezone(timezone.utc)
return dt.isoformat()
def youtube_url_to_id(url):
"""Extract video id from a youtube url.
If parsing fails, try regex. If that fails, return None.
The regex is from somewhere in this thread, I think:
https://stackoverflow.com/questions/3452546/how-do-i-get-the-youtube-video-id-from-a-url
"""
url = urllib.parse.unquote(url)
url_data = urllib.parse.urlparse(url)
query = urllib.parse.parse_qs(url_data.query)
try:
# parse the url for a video query
return query["v"][0]
except KeyError:
# use regex to try and extract id
match = re.search(
r"((?<=(v|V)/)|(?<=be/)|(?<=(\?|\&)v=)|(?<=embed/))([\w-]+)",
url,
)
if match:
return match.group()
else:
return None
def youtube_duration_to_seconds(value):
"""Convert youtube (ISO 8601) duration to seconds.
https://en.wikipedia.org/wiki/ISO_8601#Durations
https://regex101.com/r/ALmmSS/1
"""
iso8601 = r"P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?T?(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
match = re.match(iso8601, value)
if match is None:
return None
group_names = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds']
d = dict()
for name, group in zip(group_names, match.groups(default=0)):
d[name] = int(group)
return int(
d['years']*365*24*60*60 +
d['months']*30*24*60*60 +
d['weeks']*7*24*60*60 +
d['days']*24*60*60 +
d['hours']*60*60 +
d['minutes']*60 +
d['seconds']
)
def iterate_chunks(iterable: typing.Iterable, chunk_size: int):
"""
Iterates an iterable in chunks of chunk_size elements.
:param iterable: An iterable containing items to iterate.
:param chunk_size: Chunk size
:return: Returns a generator which will yield chunks of size chunk_size
"""
it = iter(iterable)
while True:
chunk = tuple(itertools.islice(it, chunk_size))
if not chunk:
return
yield chunk

1055
app/external/pytaw/pytaw/youtube.py vendored Normal file

File diff suppressed because it is too large Load Diff

12
app/external/pytaw/setup.py vendored Normal file
View File

@ -0,0 +1,12 @@
from setuptools import setup
setup(
name='pytaw',
version='0.0.1',
packages=['pytaw'],
url='https://github.com/6000hulls/pytaw',
license='',
author='6000hulls',
author_email='6000hulls@gmail.com',
description='PYTAW: Python YouTube API Wrapper'
)

0
app/external/pytaw/tests/__init__.py vendored Normal file
View File

165
app/external/pytaw/tests/test_pytaw.py vendored Normal file
View File

@ -0,0 +1,165 @@
import pytest
import logging
import sys
import collections
from datetime import datetime, timedelta
from googleapiclient.errors import HttpError
from pytaw import YouTube
from pytaw.youtube import Resource, Video, AttributeDef
logging.basicConfig(stream=sys.stdout) # show log output when run with pytest -s
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)
@pytest.fixture
def youtube():
"""A YouTube instance initialised with a developer key loaded from config.ini"""
return YouTube()
@pytest.fixture
def video(youtube):
"""A Video instance for the classic video 'Me at the zoo'"""
return youtube.video('jNQXAC9IVRw')
@pytest.fixture
def channel(youtube):
"""A Channel instance for the 'YouTube Help' channel"""
return youtube.channel('UCMDQxm7cUx3yXkfeHa5zJIQ')
@pytest.fixture
def search(youtube):
"""A ListResponse instance corresponding to a search for the query 'python'"""
return youtube.search()
@pytest.fixture
def video_search(youtube):
"""A ListResponse instance corresponding to a video search for the query 'python'"""
return youtube.search(q='python', type='video')
@pytest.fixture
def video_search_array(youtube):
"""An array of video searches with a wide range of results (zero to millions)."""
one_minute_ago = datetime.utcnow() - timedelta(minutes=1)
five_minutes_ago = datetime.utcnow() - timedelta(minutes=5)
return [
#
# no results
youtube.search(q='minecraft', type='video', publishedBefore=datetime(2000, 1, 1)),
#
# less than 100 results
youtube.search(q='minecraft', type='video', publishedBefore=datetime(2005, 7, 1)),
#
# over 100 results
youtube.search(q='minecraft', type='video', publishedBefore=datetime(2006, 1, 1)),
#
# variable number of results (hundreds or thousands...?)
youtube.search(q='minecraft', type='video', publishedAfter=one_minute_ago),
youtube.search(q='minecraft', type='video', publishedAfter=five_minutes_ago),
#
# over a million results
youtube.search(q='minecraft', type='video'),
youtube.search(q='minecraft'),
]
class TestResource:
def test_equality(self, search):
a = search[0]
b = search[0]
c = search[1]
assert a == b
assert a != c
def test_unknown_attribute(self, video):
with pytest.raises(AttributeError):
_ = video.attribute_name_which_definitely_will_never_exist
def test_unknown_part_in_attributedef(self, video):
video.ATTRIBUTE_DEFS['x'] = AttributeDef('nonexistant_part', 'x')
with pytest.raises(HttpError):
_ = video.x
def test_unknown_attribute_name_in_attributedef(self, video):
video.ATTRIBUTE_DEFS['x'] = AttributeDef('snippet', 'nonexistant_attribute')
assert video.x is None
class TestVideo:
def test_bad_video_id(self, youtube):
video = youtube.video('not_a_valid_youtube_video_id')
assert video is None
def test_title(self, video):
assert video.title == "Me at the zoo"
def test_published_at(self, video):
assert video.published_at.isoformat() == '2005-04-24T03:31:52+00:00'
def test_n_views(self, video):
assert video.n_views > int(40e6)
def test_tags(self, video):
assert video.tags == ['jawed', 'karim', 'elephant', 'zoo', 'youtube', 'first', 'video']
def test_duration(self, video):
assert video.duration.total_seconds() == 19
class TestChannel:
def test_title(self, channel):
assert channel.title == "YouTube Help"
class TestSearch:
def test_video_search_returns_a_video(self, video_search):
assert isinstance(video_search[0], Video)
def test_video_search_has_many_results(self, video_search):
# make video_search unlazy (populate pageInfo attributes)
_ = video_search[0]
assert video_search.total_results > 10000
def test_search_iteration(self, search):
"""Simply iterate over a search, creating all resources, to check for exceptions."""
for resource in search:
log.debug(resource)
class TestListResponse:
def test_if_iterable(self, search):
assert isinstance(search, collections.Iterator)
def test_integer_indexing(self, search):
assert isinstance(search[0], Resource)
def test_slice_indexing(self, search):
assert isinstance(search[1:3], list)
def test_full_listing_iteration(self, video_search_array):
"""Iterate over all search results to check no exceptions are raised when paging etc.
Even if millions of results are found, the API will never return more than 500 (by
design), so we're okay to just bang right through the search results generator for the
whole array of video searches.
"""
for i, search in enumerate(video_search_array):
c = 0
for _ in search:
c += 1
log.debug(f"checked first {c} results (search #{i})")