Skip to content

Commit

Permalink
Merge pull request #216 from dbatten5/tmdb-source
Browse files Browse the repository at this point in the history
Add TMDB source
  • Loading branch information
dbatten5 committed Feb 3, 2023
2 parents 43d372f + e04a5c0 commit 9247cf3
Show file tree
Hide file tree
Showing 26 changed files with 1,898 additions and 483 deletions.
11 changes: 0 additions & 11 deletions .flake8

This file was deleted.

1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:

env:
NOXSESSION: ${{ matrix.session }}
TMDB_API_KEY: ${{secrets.TMDB_API_KEY}}

steps:
- name: Check out the repository
Expand Down
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ repos:
language: system
types: [text]
stages: [commit, push, manual]
- id: flake8
name: flake8
entry: flake8
- id: ruff
name: ruff
entry: ruff
language: system
types: [python]
require_serial: true
Expand Down
62 changes: 62 additions & 0 deletions docs/tmdb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Usage

To access TMDB data points, first ensure you have loaded the TMDB source
through:

```python
await phylm.load_source("tmdb")
```

Alternatively you can instantiate the TMDB source class directly through:

```python
from phylm.sources import Tmdb

tmdb = Tmdb(raw_title="The Matrix", raw_year=1999)  # raw_year is optional
await tmdb.load_source()
```

## Movie ID

If you know the TMDB movie ID you can instantiate the `Phylm` class with a `tmdb_id`
property:

```python
from phylm.sources import Tmdb

tmdb = Tmdb(raw_title="The Matrix", tmdb_id="609")
```

Then, when running `load_source` for `tmdb`, `phylm` will first perform a search based
on the ID. If the ID is valid the result will be selected, if not then it will fall back
to a title search.

Alternatively, you can pass it to `load_source` with `"tmdb"` as the first argument:

```python
await phylm.load_source("tmdb", tmdb_id="609")
```

Or instantiate the TMDB source class with the ID:

```python
from phylm.sources import Tmdb

tmdb = Tmdb(movie_id="0133093")
```

!!! warning ""
If instantiating the class directly you must supply at least one of `movie_id`
or `raw_title`, otherwise a `ValueError` will be raised.

Note that TMDB doesn't provide any fuzzy search for title, only exact matches are
returned.

# Reference

::: phylm.sources.tmdb.Tmdb
rendering:
show_signature_annotations: true
heading_level: 2
show_root_heading: false
show_root_toc_entry: false
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ nav:
IMDb: imdb.md
Metacritic: mtc.md
Rotten Tomatoes: rt.md
TMDB: tmdb.md
- Tools: tools.md
- Contributing: contributing.md

Expand Down
6 changes: 1 addition & 5 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,7 @@ def precommit(session: Session) -> None:
session.install(
"black",
"darglint",
"flake8",
"flake8-bandit",
"flake8-bugbear",
"flake8-docstrings",
"flake8-rst-docstrings",
"ruff",
"pep8-naming",
"pre-commit",
"pre-commit-hooks",
Expand Down
640 changes: 256 additions & 384 deletions poetry.lock

Large diffs are not rendered by default.

52 changes: 47 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,13 @@ typeguard = "^2.12.1"
xdoctest = {extras = ["colors"], version = "^0.15.5"}
pre-commit = "^2.13.0"
black = "^22.12.0"
flake8 = "^6.0.0"
flake8-bandit = "^4.1.1"
flake8-bugbear = "^21.11.29"
flake8-docstrings = "^1.6.0"
flake8-rst-docstrings = "^0.2.3"
pep8-naming = "^0.12.0"
darglint = "^1.8.0"
reorder-python-imports = "^2.6.0"
pre-commit-hooks = "^4.0.1"
Pygments = "^2.9.0"
types-requests = "^2.25.11"
ruff = "^0.0.240"

[tool.poetry.group.docs.dependencies]
markdown-include = "*"
Expand Down Expand Up @@ -78,6 +74,52 @@ source = ["phylm"]
show_missing = true
fail_under = 90

[tool.ruff]
ignore = [
'B019',
'D203',
'D204',
'D213',
'D215',
'D400',
'D404',
'D406',
'D407',
'D408',
'D409',
'D413',
'E501',
'S113'
]
line-length = 80
select = [
'B',
'B9',
'C',
'D',
'E',
'F',
'N',
'S',
'W',
]

[tool.ruff.mccabe]
max-complexity = 10

[tool.ruff.pydocstyle]
convention = 'google'

[tool.ruff.per-file-ignores]
"__init__.py" = ['F401']
"tests/*" = [
'S101',
'D212',
'D415',
'D205',
'D104',
]

[tool.mypy]
strict = true
pretty = true
Expand Down
98 changes: 93 additions & 5 deletions src/phylm/clients/tmdb.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
"""Client to interact with The Movie DB (TMDB)."""
import os
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Union

from aiohttp import ClientSession
from requests import Session

from phylm.errors import NoTMDbApiKeyError


class TmdbClient:
"""Class to abstract to the Tmdb API."""

def __init__(self, api_key: str) -> None:
def __init__(
self, api_key: str, async_session: Optional[ClientSession] = None
) -> None:
"""Initialize the client.
Args:
api_key: an api_key for authentication
async_session: an optional instance of `aiohttp.ClientSession`
"""
super().__init__()
self.session = Session()
self.async_session = async_session or ClientSession()
self.api_key = api_key
self._base = "https://api.themoviedb.org/3"
self._base_url = "https://api.themoviedb.org/3"

def search_movies(self, query: str, region: str = "us") -> List[Dict[str, Any]]:
"""Search for movies.
Expand All @@ -37,13 +46,67 @@ def search_movies(self, query: str, region: str = "us") -> List[Dict[str, Any]]:
"include_adult": "false",
"region": region.upper(),
}
res = self.session.get(f"{self._base}/search/movie", params=payload)
res = self.session.get(f"{self._base_url}/search/movie", params=payload)

res.raise_for_status()

results: List[Dict[str, Any]] = res.json()["results"]
return results

async def search_movies_async(
self, query: str, region: str = "us", year: Optional[int] = None
) -> List[Dict[str, Any]]:
"""Search for movies async.
Args:
query: the search query
region: the region for the query, affects the release date value
year: the year of the movie
Returns:
List[Dict[str, Any]]: the search results
"""
params: Dict[str, Union[str, int]] = {
"api_key": self.api_key,
"language": "en-US",
"query": query,
"include_adult": "false",
"region": region.upper(),
"page": 1,
}

if year:
params["year"] = year

async with self.async_session.get(
f"{self._base_url}/search/movie", params=params
) as resp:
results = await resp.json()

movies: List[Dict[str, Any]] = results["results"]
return movies

async def get_movie(self, movie_id: str) -> Dict[str, Any]:
"""Return a movie by id.
Args:
movie_id: the tmdb id of the movie
Returns:
Dict[str, Any]: a dictionary of the movie data
"""
params = {
"api_key": self.api_key,
"language": "en-US",
}

async with self.async_session.get(
f"{self._base_url}/movie/{movie_id}", params=params
) as resp:
movie: Dict[str, Any] = await resp.json()

return movie

def get_streaming_providers(
self, movie_id: str, regions: List[str]
) -> Dict[str, Any]:
Expand All @@ -57,12 +120,37 @@ def get_streaming_providers(
Dict[str, Any]: a dictionary of streaming providers, keyed by region name
"""
payload = {"api_key": self.api_key}

res = self.session.get(
f"{self._base}/movie/{movie_id}/watch/providers", params=payload
f"{self._base_url}/movie/{movie_id}/watch/providers", params=payload
)

res.raise_for_status()

results: Dict[str, Any] = res.json()["results"]

return {key: results.get(key.upper(), {}) for key in regions}


def initialize_tmdb_client(
api_key: Optional[str] = None,
async_session: Optional[ClientSession] = None,
) -> TmdbClient:
"""Initialize and return a TmdbClient.
Args:
api_key: an optional api_key to take precedence over an env var key
async_session: an optional aiohttp ClienSession
Raises:
NoTMDbApiKeyError: when no api_key has been provided
Returns:
TmdbClient: an authorized Tmdb client
"""
tmdb_api_key = api_key or os.environ.get("TMDB_API_KEY")

if not tmdb_api_key:
raise NoTMDbApiKeyError("An `api_key` must be provided to use this service")

return TmdbClient(api_key=tmdb_api_key, async_session=async_session)
Loading

0 comments on commit 9247cf3

Please sign in to comment.