Skip to content

Commit

Permalink
- tests
Browse files Browse the repository at this point in the history
  • Loading branch information
xNykram committed Mar 15, 2024
1 parent 775e870 commit be1d01c
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 72 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/dist
__pycache__
youtube_search2.egg-info
youtube_search2.egg-info
.vscode
.pytest_cache
.coverage
.coverage-report
17 changes: 16 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,19 @@ Issues = "https://github.com/xNykram/youtube_search2/issues"

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
build-backend = "setuptools.build_meta"

[tool.pytest.ini_options]
python_files = 'test*.py'
addopts = "--cov=. --cov-report=html:coverage-report -p no:warnings"
testpaths = "tests"
minversion = "6.0"

[tool.coverage.run]
branch = true
parallel = true
omit = ["tests/*", "__init__.py"]

[tool.coverage.report]
show_missing = true
fail_under = 75
80 changes: 42 additions & 38 deletions src/ytsearch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,51 @@
from bs4 import BeautifulSoup
import re


class YTSearch:
def __init__(self):
self.url: str
self.videos: list

def _parse_html(self, soup_obj: BeautifulSoup):
video_id = re.search(r'(?<=\?v=)[\w-]+', self.url).group(0)
title = soup_obj.find("meta", {"name": "title"})['content']
video_id = re.search(r"(?<=\?v=)[\w-]+", self.url).group(0)
title = soup_obj.find("meta", {"name": "title"})["content"]
thumbnail = f"https://i.ytimg.com/vi/{video_id}/maxresdefault.jpg"
js_script = str(soup_obj.find_all("script")[20])
duration_mil = re.search(r'"approxDurationMs":"(\d+)"',js_script).group(1)
return {"id" : video_id, "title" : title, "thumbnail": thumbnail, "duration": duration_mil}

duration_mil = re.search(r'"approxDurationMs":"(\d+)"', js_script).group(1)
return {
"id": video_id,
"title": title,
"thumbnail": thumbnail,
"duration": duration_mil,
}

def _fetch_yt_data(self) -> str:
try:
response = requests.get(url=self.url)
response.raise_for_status()
except requests.RequestException:
raise requests.RequestException("Failed to fetch data from YouTube.")
return response.text

def search_by_url(self, url: str):
if "https://" in url:
self.url = url
response = requests.get(url).text
response = self._fetch_yt_data()
soup_obj = BeautifulSoup(response, features="lxml")
return self._parse_html(soup_obj)

def search_by_term(self, term: str, max_results: int = 10):
else:
raise ValueError("Please provide valid URL.")

def search_by_term(self, term: str, max_results: int = None):
encoded_search = urllib.parse.quote_plus(term)
BASE_URL = "https://youtube.com"
url = f"{BASE_URL}/results?search_query={encoded_search}"
response = requests.get(url).text
while "ytInitialData" not in response:
response = requests.get(url).text

results = []
start = response.index("ytInitialData") + len("ytInitialData") + 3
end = response.index("};", start) + 1
json_str = response[start:end]
data = json.loads(json_str)
self.url = f"{BASE_URL}/results?search_query={encoded_search}"
response = self._fetch_yt_data()

for contents in data["contents"]["twoColumnSearchResultsRenderer"][
"primaryContents"
]["sectionListRenderer"]["contents"]:
results = []
searched_obj = self._prepare_data(response)
for contents in searched_obj:
for video in contents["itemSectionRenderer"]["contents"]:
res = {}
if "videoRenderer" in video.keys():
Expand Down Expand Up @@ -86,22 +94,18 @@ def search_by_term(self, term: str, max_results: int = 10):

if results:
if max_results is not None and len(results) > max_results:
return results[: max_results]
return results[:max_results]
self.videos = results
return results

def to_dict(self, clear_cache=True):
result = self.videos
if clear_cache:
self.videos = ""
return result
break
return results

def to_json(self, clear_cache=True):
result = json.dumps({"videos": self.videos})
if clear_cache:
self.videos = ""
return result

search_engine = YTSearch()
video_info = search_engine.search_by_term("Me at the zoo", max_results=1)
print(video_info)
def _prepare_data(self, response):
start = response.index("ytInitialData") + len("ytInitialData") + 3
end = response.index("};", start) + 1
json_str = response[start:end]
data = json.loads(json_str)
searched_obj = data["contents"]["twoColumnSearchResultsRenderer"][
"primaryContents"
]["sectionListRenderer"]["contents"]

return searched_obj
81 changes: 49 additions & 32 deletions tests/test_search.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
from src.ytsearch import YoutubeSearch


class TestSearch:

def test_init_defaults(self):
search = YoutubeSearch("test")
assert search.max_results is None
assert 1 <= len(search.videos)

def test_init_max_results(self):
search = YoutubeSearch("test", max_results=10)
assert 10 == search.max_results
assert 10 == len(search.videos)

def test_dict(self):
search = YoutubeSearch("test", max_results=10)
assert isinstance(search.to_dict(), list)

def test_json(self):
search = YoutubeSearch("test", max_results=10)
assert isinstance(search.to_json(), str)

def test_clear_cache(self):
search = YoutubeSearch("test", max_results=10)
json_output = search.to_json(clear_cache=False)
assert "" != search.videos

dict_output = search.to_dict()
assert "" == search.videos


import pytest
from src.ytsearch import YTSearch
import src
from requests import RequestException
@pytest.fixture
def yt_search():
return YTSearch()

def test_search_by_url_valid(yt_search):
url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
result = yt_search.search_by_url(url)
assert result["id"] == "dQw4w9WgXcQ"

def test_search_by_url_invalid(yt_search):
url = "invalid_url"
with pytest.raises(ValueError):
yt_search.search_by_url(url)

def test_search_by_term(yt_search):
term = "openai"
result = yt_search.search_by_term(term)
assert len(result) > 0

def test_search_by_term_not_finished_loop(yt_search, mocker):
mocker.patch('src.ytsearch.YTSearch._prepare_data', return_value=[])
term = "openai"
result = yt_search.search_by_term(term)

assert result == []

def test_search_by_term_no_results(yt_search):
term = "#########################"
result = yt_search.search_by_term(term)
print(result)
assert len(result) == 0


def test_search_by_term_with_max_results(yt_search):
term = "openai"
max_results = 5
result = yt_search.search_by_term(term, max_results)
assert len(result) == max_results


def test_search_by_url_exception(yt_search):
with pytest.raises(RequestException) as exc_info:
url = "https://wrong_url"
yt_search.search_by_url(url)
assert str(exc_info.value) == "Failed to fetch data from YouTube."

0 comments on commit be1d01c

Please sign in to comment.