diff --git a/.gitignore b/.gitignore index c780f7d..1db40a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ __pycache__/ *.pyc -.env \ No newline at end of file +.env +.python-version +dist/ +build/ +movietimes.egg-info/ +.pytest_cache/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..37e64b8 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# MovieTimes + +MovieTimes is a Python-based scraper that fetches movie showtimes for a given ZIP code using Google search results. + +## Installation + +```bash +git clone https://github.com/yourusername/movietimes.git +cd movietimes +pip install -r requirements.txt +``` + +## Usage + +```bash +python src/showtimes_scraper.py 90210 +``` + +## Testing + +```bash +pytest +``` \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..cf6e157 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,3 @@ +pytest +setuptools +build \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b94ef2e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +requests +beautifulsoup4 +rich \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3d0a7f3 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +from setuptools import setup, find_packages + +setup( + name="movietimes", + version="1.0", + packages=find_packages(where="src"), + package_dir={"": "src"}, + install_requires=[ + "requests", + "rich" + ], + entry_points={ + "console_scripts": [ + "movietimes=movietimes.cli:main", + ], + }, +) diff --git a/src/movietimes/__init__.py b/src/movietimes/__init__.py new file mode 100644 index 0000000..0bf814f --- /dev/null +++ b/src/movietimes/__init__.py @@ -0,0 +1 @@ +# src/__init__.py diff --git a/src/movietimes/cli.py b/src/movietimes/cli.py new file mode 100644 index 0000000..780c8c0 --- /dev/null +++ b/src/movietimes/cli.py @@ -0,0 +1,4 @@ +from .movie_showtimes import main + +if __name__ == "__main__": + main() diff --git a/src/movietimes/movie_showtimes.py b/src/movietimes/movie_showtimes.py new file mode 100644 index 0000000..964dd87 --- /dev/null +++ b/src/movietimes/movie_showtimes.py @@ -0,0 +1,55 @@ +import requests +import time +import os +from rich.console import Console +from rich.table import Table + +SERPAPI_SHOWTIMES_URL = "https://serpapi.com/search" +SERPAPI_API_KEY = os.getenv("SERPAPI_API_KEY", "your_serpapi_api_key") + +default_theater_query = "cinemark century boulder" +default_location = "boulder, colorado, united states" + +console = Console() + +def get_movie_showtimes(): + params = { + 'q': default_theater_query, + 'hl': 'en', + 'gl': 'us', + 'location': default_location, + 'api_key': SERPAPI_API_KEY + } + response = requests.get(SERPAPI_SHOWTIMES_URL, params=params) + if response.status_code == 200: + data = response.json() + return data.get('showtimes', []) + else: + raise ValueError("Could not fetch movie showtimes from SerpApi.") + +def display_showtimes(showtimes): + if not showtimes: + console.print("[bold red]No showtimes available.[/bold red]") + return + + for day_data in showtimes: + day = day_data.get("day", "Unknown Day") + console.print(f"\n[bold cyan]{day}[/bold cyan]") + + for movie in day_data.get("movies", []): + table = Table(title=f"{movie['name']}", show_header=True, header_style="bold magenta") + table.add_column("Type", style="dim") + table.add_column("Times", justify="right") + + for showing in movie.get("showing", []): + table.add_row(showing["type"], ", ".join(showing["time"])) + + console.print(table) + +def main(): + showtimes = get_movie_showtimes() + display_showtimes(showtimes) + time.sleep(1) + +if __name__ == '__main__': + main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..7a8947f --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Make tests a package diff --git a/tests/test_movie_showtimes.py b/tests/test_movie_showtimes.py new file mode 100644 index 0000000..f7ac2b3 --- /dev/null +++ b/tests/test_movie_showtimes.py @@ -0,0 +1,69 @@ +import pytest +import requests +from unittest.mock import patch +from src.movie_showtimes import get_movie_showtimes, display_showtimes +from io import StringIO +from rich.console import Console + +@pytest.fixture +def mock_successful_response(): + """Mock a successful API response.""" + return { + "showtimes": [ + { + "day": "Today", + "movies": [ + { + "name": "Dune", + "showing": [ + {"time": ["12:30pm", "4:00pm"], "type": "Standard"}, + {"time": ["1:30pm", "5:30pm"], "type": "IMAX"} + ] + } + ] + } + ] + } + +@pytest.fixture +def mock_failed_response(): + """Mock a failed API response.""" + return {} + +@patch("requests.get") +def test_get_movie_showtimes_success(mock_get, mock_successful_response): + """Test fetching movie showtimes successfully from SerpApi.""" + mock_get.return_value.status_code = 200 + mock_get.return_value.json.return_value = mock_successful_response + + showtimes = get_movie_showtimes() + + assert len(showtimes) == 1 + assert showtimes[0]["day"] == "Today" + assert showtimes[0]["movies"][0]["name"] == "Dune" + assert showtimes[0]["movies"][0]["showing"][0]["type"] == "Standard" + +@patch("requests.get") +def test_get_movie_showtimes_failure(mock_get, mock_failed_response): + """Test handling of API failure (non-200 response).""" + mock_get.return_value.status_code = 500 + mock_get.return_value.json.return_value = mock_failed_response + + with pytest.raises(ValueError, match="Could not fetch movie showtimes from SerpApi."): + get_movie_showtimes() + +@patch("sys.stdout", new_callable=StringIO) +def test_display_showtimes_output(mock_stdout, mock_successful_response): + """Test console output formatting of display_showtimes.""" + console = Console(file=mock_stdout) + + display_showtimes(mock_successful_response["showtimes"]) + output = mock_stdout.getvalue() + + assert "Today" in output + assert "Dune" in output + assert "Standard" in output + assert "12:30pm, 4:00pm" in output + assert "IMAX" in output + assert "1:30pm, 5:30pm" in output +