From e87f448fe1818e9d50b2ad086472d996996ed840 Mon Sep 17 00:00:00 2001 From: mBaratta96 Date: Mon, 7 Aug 2023 18:50:27 +0200 Subject: [PATCH 1/3] types --- letterboxd_stats/cli.py | 39 ++++++++++----------------------- letterboxd_stats/data.py | 28 +++++++++++------------ letterboxd_stats/main.py | 10 ++++----- letterboxd_stats/tmdb.py | 14 +++++++----- letterboxd_stats/web_scraper.py | 10 ++++----- 5 files changed, 44 insertions(+), 57 deletions(-) diff --git a/letterboxd_stats/cli.py b/letterboxd_stats/cli.py index 2dcb0e3..00da1d5 100644 --- a/letterboxd_stats/cli.py +++ b/letterboxd_stats/cli.py @@ -11,7 +11,7 @@ IMAGE_URL = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2" -def select_value(values: list[str], message: str, default: str | None = None): +def select_value(values: list[str], message: str, default: str | None = None) -> str: value = inquirer.select( # type: ignore message=message, choices=values, @@ -20,23 +20,20 @@ def select_value(values: list[str], message: str, default: str | None = None): return value -def select_movie_id(movies_info: pd.DataFrame) -> int: - movie_id = inquirer.fuzzy( # type: ignore - message="Write movie id for more information", +def select_movie(movies: pd.Series, results: pd.Series) -> str: + result = inquirer.fuzzy( # type: ignore + message="Select movie for more information", mandatory=False, max_height="25%", - choices=[ - Choice(value=id, name=f"{id} - {title}") for id, title in zip(movies_info["Id"], movies_info["Title"]) - ], + choices=[Choice(value=result, name=title) for result, title in zip(results, movies)], keybindings={"skip": [{"key": "escape"}]}, - validate=lambda result: result in movies_info["Id"].values, - filter=lambda result: None if result is None else int(result), - invalid_message="Input must be in the resulting IDs", + invalid_message="Input not in list of movies.", + validate=lambda result: result in results.values, ).execute() - return movie_id + return result -def select_list(names: list[str]) -> int: +def select_list(names: list[str]) -> str: name = inquirer.fuzzy( # type: ignore message="Select your list:", mandatory=True, @@ -68,19 +65,7 @@ def select_range(options: list[str]) -> list[str]: return result -def select_movie(movie_df: pd.DataFrame) -> str: - result = inquirer.fuzzy( # type: ignore - message="Select movie for more information", - mandatory=False, - max_height="25%", - choices=[Choice(value=url, name=f"{title}") for url, title in zip(movie_df["Url"], movie_df["Title"])], - keybindings={"skip": [{"key": "escape"}]}, - invalid_message="Input must be in the resulting IDs", - ).execute() - return result - - -def print_film(film, expand=True): +def print_film(film: dict, expand=True): grid = Table.grid(expand=expand, padding=1) grid.add_column(style="bold yellow") grid.add_column() @@ -107,7 +92,7 @@ def download_poster(poster: str): art.to_terminal(columns=int(config["CLI"]["poster_columns"])) -def _validate_date(s: str): +def _validate_date(s: str) -> bool: try: datetime.strptime(s, "%Y-%m-%d") except ValueError: @@ -115,7 +100,7 @@ def _validate_date(s: str): return True -def add_film_questions(): +def add_film_questions() -> dict[str, str]: print("Set all the infos for the film:\n") specify_date = inquirer.confirm(message="Specify date?").execute() # type: ignore today = datetime.today().strftime("%Y-%m-%d") diff --git a/letterboxd_stats/data.py b/letterboxd_stats/data.py index a6e562f..d222c82 100644 --- a/letterboxd_stats/data.py +++ b/letterboxd_stats/data.py @@ -12,17 +12,17 @@ tqdm.pandas(desc="Fetching ids...") -def check_if_watched(df: pd.DataFrame, row: pd.Series): +def check_if_watched(df: pd.DataFrame, row: pd.Series) -> bool: if row["Title"] in df["Name"].values: watched_films_same_name = df[df["Name"] == row["Title"]] for _, film in watched_films_same_name.iterrows(): film_id = get_tmdb_id(film["Letterboxd URI"]) - if film_id == row["Id"]: + if film_id == row.name: return True return False -def read_watched_films(df: pd.DataFrame, path: str, name: str): +def read_watched_films(df: pd.DataFrame, path: str, name: str) -> pd.DataFrame: df_profile = pd.read_csv(path) df.insert(0, "watched", np.where([check_if_watched(df_profile, row) for _, row in df.iterrows()], "[X]", "[ ]")) df["Release Date"] = pd.to_datetime(df["Release Date"]) @@ -31,20 +31,20 @@ def read_watched_films(df: pd.DataFrame, path: str, name: str): return df -def select_film_of_person(df): - movie_id = cli.select_movie_id(df[["Id", "Title"]]) +def select_film_of_person(df: pd.DataFrame) -> pd.Series | None: + movie_id = cli.select_movie(df["Title"], df.index.to_series().parallel_map(str)) if movie_id is None: return None - movie_row = df.loc[df["Id"] == movie_id].iloc[0, :] + movie_row = df.loc[int(movie_id)] return movie_row -def get_list_name(path: str): +def get_list_name(path: str) -> str: df = pd.read_csv(path, header=1) return df["Name"].iloc[0] -def open_list(path: str, limit, acending): +def open_list(path: str, limit: int, acending: bool) -> str: list_names = { get_list_name(os.path.join(path, letterboxd_list)): letterboxd_list for letterboxd_list in os.listdir(path) } @@ -52,7 +52,7 @@ def open_list(path: str, limit, acending): return open_file("Lists", os.path.join(path, list_names[name]), limit, acending, header=3) -def open_file(filetype: str, path: str, limit, ascending, header=0): +def open_file(filetype: str, path: str, limit, ascending, header=0) -> str: df = pd.read_csv(path, header=header) df.rename(columns={"Name": "Title", "Letterboxd URI": "Url"}, inplace=True) df["Year"] = df["Year"].fillna(0).astype(int) @@ -60,10 +60,10 @@ def open_file(filetype: str, path: str, limit, ascending, header=0): if limit is not None: df = df.iloc[:limit, :] cli.render_table(df, filetype) - return cli.select_movie(df[["Title", "Url"]]) + return cli.select_movie(df["Title"], df["Url"]) -def _show_lists(df: pd.DataFrame, ascending: bool): +def _show_lists(df: pd.DataFrame, ascending: bool) -> pd.DataFrame: ratings_path = os.path.expanduser(os.path.join(config["root_folder"], "static", "ratings.csv")) df_ratings = pd.read_csv(ratings_path) df_ratings.rename(columns={"Letterboxd URI": "URL"}, inplace=True) @@ -84,7 +84,7 @@ def _show_lists(df: pd.DataFrame, ascending: bool): return df -def _show_watchlist(df: pd.DataFrame, ascending: bool): +def _show_watchlist(df: pd.DataFrame, ascending: bool) -> pd.DataFrame: sort_column = cli.select_value( df.columns.values.tolist() + ["Shuffle"], "Select the order of your watchlist entries:" ) @@ -95,7 +95,7 @@ def _show_watchlist(df: pd.DataFrame, ascending: bool): return df -def _show_diary(df: pd.DataFrame, ascending: bool): +def _show_diary(df: pd.DataFrame, ascending: bool) -> pd.DataFrame: df["Watched Date"] = pd.to_datetime(df["Watched Date"]) sort_column = cli.select_value(df.columns.values.tolist(), "Select the order of your diary entries:") df.sort_values(by=sort_column, ascending=ascending, inplace=True) @@ -103,7 +103,7 @@ def _show_diary(df: pd.DataFrame, ascending: bool): return df -def _show_ratings(df: pd.DataFrame, ascending: bool): +def _show_ratings(df: pd.DataFrame, ascending: bool) -> pd.DataFrame: df["Date"] = pd.to_datetime(df["Date"]) sort_column = cli.select_value(df.columns.values.tolist(), "Select the order of your ratings:") df.sort_values(by=sort_column, ascending=ascending, inplace=True) diff --git a/letterboxd_stats/main.py b/letterboxd_stats/main.py index dbbf196..70e63a9 100644 --- a/letterboxd_stats/main.py +++ b/letterboxd_stats/main.py @@ -11,7 +11,7 @@ def try_command(command, args): try: command(*args) except Exception as e: - print(e) + raise e def check_path(path: str): @@ -27,7 +27,7 @@ def download_data(): downloader.download_stats() -def get_movie_detail_from_url(letterboxd_url, is_diary=False): +def get_movie_detail_from_url(letterboxd_url: str, is_diary=False): if letterboxd_url is not None: id = ws.get_tmdb_id(letterboxd_url, is_diary) if id is not None: @@ -41,9 +41,9 @@ def search_person(args_search: str): df = data.read_watched_films(df, path, name) movie = data.select_film_of_person(df) while movie is not None: - search_film_query = f"{movie['Title']} {movie['Release Date'].year}" + search_film_query = f"{movie['Title']} {movie['Release Date'].year}" # type: ignore title_url = ws.search_film(search_film_query) - tmdb.get_movie_detail(movie["Id"], ws.create_movie_url(title_url, "film_page")) + tmdb.get_movie_detail(int(movie.name), ws.create_movie_url(title_url, "film_page")) # type: ignore movie = data.select_film_of_person(df) @@ -58,7 +58,7 @@ def search_film(args_search_film: str): downloader.perform_operation(answer, title_url) -def get_data(args_limit, args_ascending, data_type): +def get_data(args_limit: int, args_ascending: bool, data_type: str): path = os.path.expanduser(os.path.join(config["root_folder"], "static", DATA_FILES[data_type])) check_path(path) letterboxd_url = ( diff --git a/letterboxd_stats/tmdb.py b/letterboxd_stats/tmdb.py index 2994e3a..e2843de 100644 --- a/letterboxd_stats/tmdb.py +++ b/letterboxd_stats/tmdb.py @@ -1,6 +1,8 @@ +from typing import Any, Tuple from tmdbv3api import TMDb, Person, Movie, Search from tmdbv3api.exceptions import TMDbException import pandas as pd +from tmdbv3api.objs.account import AsObj from letterboxd_stats import cli from letterboxd_stats import config from pandarallel import pandarallel @@ -13,7 +15,7 @@ pandarallel.initialize(verbose=0) -def get_person(name: str): +def get_person(name: str) -> Tuple[pd.DataFrame, str]: print(f"Searching for '{name}'") search_results = search.people({"query": name}) names = [result.name for result in search_results] # type: ignore @@ -26,27 +28,27 @@ def get_person(name: str): movie_credits = person.movie_credits(search_result["id"]) list_of_films = [ { + "Id": m.id, "Title": m.title, "Release Date": m.release_date, "Department": m.department, - "Id": m.id, } for m in movie_credits["crew"] ] if len(list_of_films) == 0: raise ValueError("The selected person doesn't have any film.") - df = pd.DataFrame(list_of_films) + df = pd.DataFrame(list_of_films).set_index("Id") department = cli.select_value( df["Department"].unique(), f"Select a department for {p['name']}", known_for_department ) df = df[df["Department"] == department] df = df.drop("Department", axis=1) if config["TMDB"]["get_list_runtimes"] is True: - df["Duration"] = df["Id"].parallel_map(get_film_duration) # type: ignore + df["Duration"] = df.index.to_series().parallel_map(get_film_duration) # type: ignore return df, p["name"] -def get_movie(movie_query: str): +def get_movie(movie_query: str) -> Any | AsObj: print(f"Searching for movie '{movie_query}'") search_results = search.movies({"query": movie_query}) titles = [f"{result.title} ({result.release_date})" for result in search_results] # type: ignore @@ -75,7 +77,7 @@ def get_movie_detail(movie_id: int, letterboxd_url=None): cli.print_film(selected_details) -def get_film_duration(tmdb_id: str): +def get_film_duration(tmdb_id: str) -> int: try: runtime = movie.details(int(tmdb_id)).runtime # type: ignore except TMDbException: diff --git a/letterboxd_stats/web_scraper.py b/letterboxd_stats/web_scraper.py index bdddcda..d40e589 100644 --- a/letterboxd_stats/web_scraper.py +++ b/letterboxd_stats/web_scraper.py @@ -92,11 +92,11 @@ def perform_operation(self, answer: str, link: str): getattr(self, MOVIE_OPERATIONS[answer])(link) -def create_movie_url(title: str, operation: str): +def create_movie_url(title: str, operation: str) -> str: return URL + OPERATIONS_URLS[operation](title) -def _get_tmdb_id_from_web(link: str, is_diary: bool): +def _get_tmdb_id_from_web(link: str, is_diary: bool) -> int: res = requests.get(link) movie_page = html.fromstring(res.text) if is_diary: @@ -113,7 +113,7 @@ def _get_tmdb_id_from_web(link: str, is_diary: bool): return int(id) -def get_tmdb_id(link: str, is_diary=False): +def get_tmdb_id(link: str, is_diary=False) -> int | None: tmdb_id_cache = shelve.open(cache_path, writeback=False, protocol=5) prefix, key = link.rsplit("/", 1) if prefix in tmdb_id_cache and key in tmdb_id_cache[prefix]: @@ -131,11 +131,11 @@ def get_tmdb_id(link: str, is_diary=False): return id -def select_optional_operation(): +def select_optional_operation() -> str: return cli.select_value(["Exit"] + list(MOVIE_OPERATIONS.keys()), "Select operation:") -def search_film(title: str, allow_selection=False): +def search_film(title: str, allow_selection=False) -> str: search_url = create_movie_url(title, "search") res = requests.get(search_url) if res.status_code != 200: From 46e2b771a37b5afe4407975629913fa2c483615c Mon Sep 17 00:00:00 2001 From: mBaratta96 Date: Mon, 7 Aug 2023 18:51:20 +0200 Subject: [PATCH 2/3] release --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ef42a62..ac34224 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "letterboxd_stats" -version = "0.2.8" +version = "0.2.9" authors = [{ name = "mBaratta96" }] description = "Get information about your Letterboxd activity." readme = "README.md" From 9e97618125dd08f5c1bed195f3bf53f08977b89d Mon Sep 17 00:00:00 2001 From: mBaratta96 Date: Mon, 7 Aug 2023 19:04:43 +0200 Subject: [PATCH 3/3] print exceptions --- letterboxd_stats/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letterboxd_stats/main.py b/letterboxd_stats/main.py index 70e63a9..dcd103a 100644 --- a/letterboxd_stats/main.py +++ b/letterboxd_stats/main.py @@ -11,7 +11,7 @@ def try_command(command, args): try: command(*args) except Exception as e: - raise e + print(e) def check_path(path: str):