riplex 0.9.1__tar.gz → 0.9.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {riplex-0.9.1/src/riplex.egg-info → riplex-0.9.2}/PKG-INFO +1 -1
- {riplex-0.9.1 → riplex-0.9.2}/docs/changelog.md +6 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/getting-started/installation.md +3 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/troubleshooting.md +2 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/metadata/sources/tmdb.py +35 -6
- {riplex-0.9.1 → riplex-0.9.2/src/riplex.egg-info}/PKG-INFO +1 -1
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex.egg-info/SOURCES.txt +1 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex.egg-info/scm_file_list.json +1 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex.egg-info/scm_version.json +2 -2
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/welcome.py +3 -2
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_cli/commands/setup.py +1 -1
- riplex-0.9.2/tests/test_tmdb_provider.py +57 -0
- {riplex-0.9.1 → riplex-0.9.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/.github/ISSUE_TEMPLATE/crash_report.yml +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/.github/agents/riplex.agent.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/.github/copilot-instructions.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/.github/workflows/publish.yml +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/.github/workflows/release.yml +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/.gitignore +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/.vscode/settings.json +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/CONTRIBUTORS.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/LICENSE +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/README.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/architecture.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/cli-guide/lookup.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/cli-guide/orchestrate.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/cli-guide/organize.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/cli-guide/workflow.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/getting-started/configuration.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/gui-guide/gui-walkthrough.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/index.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/naming-rules.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/docs/reference/cli.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/issues/debug-artifacts-consolidation.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/issues/manual-rip-and-organize-escape-hatch.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/issues/orchestrate-dvdcompare-fallback.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/issues/planned-features.md +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/mkdocs.yml +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/pyproject.toml +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/screenshots/0_Rip_Flow_BTTF.gif +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/screenshots/1_Welcome_Screen.png +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/screenshots/2_Disc_Detection_BTTF.png +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/screenshots/3_Metadata_Lookup_BTTF.png +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/screenshots/4_Disc_Release_BTTF.png +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/screenshots/5_Multi_Disc_Overview_BTTF.png +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/screenshots/5_Select_Title_to_RIP_BTTF.png +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/setup.cfg +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/__init__.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/cache.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/config.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/dedup.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/detect.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/disc/__init__.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/disc/analysis.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/disc/makemkv.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/disc/provider.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/formatter.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/lookup.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/manifest.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/matcher.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/metadata/__init__.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/metadata/planner.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/metadata/provider.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/metadata/sources/__init__.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/models.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/normalize.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/organizer.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/scanner.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/snapshot.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/splitter.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/tagger.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/title.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/ui.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex/updater.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex.egg-info/dependency_links.txt +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex.egg-info/entry_points.txt +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex.egg-info/requires.txt +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex.egg-info/top_level.txt +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/__init__.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/bug_report.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/crash_dump.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/keep_awake.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/main.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/__init__.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/disc_detection.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/disc_overview.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/disc_swap.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/done.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/folder_picker.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/metadata.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/orchestrate_done.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/organize_done.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/organize_preview.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/progress.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/release.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/selection.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_app/screens/update.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_cli/__init__.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_cli/commands/__init__.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_cli/commands/lookup.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_cli/commands/orchestrate.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_cli/commands/organize.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_cli/commands/rip.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_cli/formatting.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/src/riplex_cli/main.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/__init__.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/fixtures/chernobyl_disc1.json +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/fixtures/makemkvcon_frozen_planet_ii_d2.txt +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/fixtures/makemkvcon_list.txt +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/snapshots/Batman Begins.snapshot.json +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/snapshots/Blade Runner (Blu-ray 4k).snapshot.json +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/snapshots/Blade Runner The Final Cut.snapshot.json +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/snapshots/Seven Worlds One Planet.snapshot.json +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/snapshots/The Dark Knight Rises.snapshot.json +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/snapshots/The Dark Knight.snapshot.json +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/snapshots/Waterworld.snapshot.json +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_app_main.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_cache.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_cli_utils.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_config.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_dedup.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_detect.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_disc_analysis.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_disc_detection_screen.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_disc_fixtures.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_disc_provider.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_formatter.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_lookup.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_makemkv.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_matcher.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_normalize.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_organizer.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_planner.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_release_screen.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_rip_guide.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_scanner.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_snapshot.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_splitter.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_tagger.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_ui.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_updater.py +0 -0
- {riplex-0.9.1 → riplex-0.9.2}/tests/test_welcome_screen.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: riplex
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Summary: Automates the tedious manual work around MakeMKV: figuring out what to rip, which MKV files are actually what, and organizing everything into Plex-compatible folder structures.
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -4,6 +4,12 @@ All notable changes to the riplex documentation are recorded here.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
6
6
|
|
|
7
|
+
## v0.9.2 — 2026-06-24
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **TMDb credentials: accept both the API Key and the Read Access Token.** TMDb's settings page offers a v3 API Key (query parameter auth) and a v4 API Read Access Token (bearer header auth). riplex now auto-detects which credential was provided and uses the matching auth scheme, so pasting the Read Access Token no longer fails with `401 Unauthorized`. The setup wizard, GUI welcome screen, and docs now note that either credential works.
|
|
12
|
+
|
|
7
13
|
## v0.9.0 — 2026-06-13
|
|
8
14
|
|
|
9
15
|
Summary: Plex-aligned movie version and edition support for combo-disc releases, with better organize preview matching for 4K, Blu-ray, and 3D movie rips.
|
|
@@ -243,6 +243,9 @@ The setup wizard will:
|
|
|
243
243
|
> enter "riplex" as the app name and `https://github.com/AnyCredit5518/riplex`
|
|
244
244
|
> as the URL. The rest of the form can be filled with basic info -- it
|
|
245
245
|
> doesn't need to be a real business. The key is approved instantly.
|
|
246
|
+
>
|
|
247
|
+
> The settings page offers both an **API Key** (v3) and an **API Read
|
|
248
|
+
> Access Token** (v4). riplex accepts either one.
|
|
246
249
|
|
|
247
250
|
2. Ask where your Plex library and MakeMKV rip folders are
|
|
248
251
|
3. Check for required tools (MakeMKV, ffprobe, mkvmerge, mkvpropedit)
|
|
@@ -74,6 +74,8 @@ riplex setup --force
|
|
|
74
74
|
|
|
75
75
|
**Solution:** TMDb asks for an app name and URL when you request a key. You can enter "riplex" as the app name and `https://github.com/AnyCredit5518/riplex` as the URL. The rest of the form can be filled with basic info - it doesn't need to be a real business. The key is approved instantly.
|
|
76
76
|
|
|
77
|
+
The settings page shows two credentials: an **API Key** (v3) and an **API Read Access Token** (v4). riplex accepts either one - paste whichever you copied. If you get a `401 Unauthorized` error, double-check that the value was copied in full.
|
|
78
|
+
|
|
77
79
|
Sign up and request a key at: https://www.themoviedb.org/settings/api
|
|
78
80
|
|
|
79
81
|
---
|
|
@@ -28,22 +28,44 @@ TMDB_BASE_URL = "https://api.themoviedb.org/3"
|
|
|
28
28
|
_TMDB_TTL_DAYS = 7
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def _looks_like_read_access_token(key: str) -> bool:
|
|
32
|
+
"""Return ``True`` when *key* looks like a TMDb v4 Read Access Token.
|
|
33
|
+
|
|
34
|
+
TMDb's settings page offers two credentials: a v3 ``API Key`` (a 32-char
|
|
35
|
+
hex string used as the ``api_key`` query parameter) and a v4 ``API Read
|
|
36
|
+
Access Token`` (a JWT used as an ``Authorization: Bearer`` header). The
|
|
37
|
+
v4 token is a JWT, so it starts with ``eyJ`` and contains exactly two
|
|
38
|
+
dots; a v3 key never does.
|
|
39
|
+
"""
|
|
40
|
+
return key.startswith("eyJ") and key.count(".") == 2
|
|
41
|
+
|
|
42
|
+
|
|
31
43
|
class TmdbProvider(MetadataProvider):
|
|
32
44
|
"""Metadata provider backed by TMDb API v3."""
|
|
33
45
|
|
|
34
46
|
def __init__(self, api_key: str | None = None) -> None:
|
|
35
|
-
self._api_key = api_key or os.environ.get("TMDB_API_KEY", "")
|
|
47
|
+
self._api_key = (api_key or os.environ.get("TMDB_API_KEY", "")).strip()
|
|
36
48
|
if not self._api_key:
|
|
37
49
|
raise ValueError(
|
|
38
50
|
"TMDb API key is required. Pass --api-key, set the "
|
|
39
51
|
"TMDB_API_KEY environment variable, or add tmdb_api_key "
|
|
40
52
|
"to your config file."
|
|
41
53
|
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
# TMDb accepts either a v3 API Key (query parameter) or a v4 Read
|
|
55
|
+
# Access Token (Authorization header) against the v3 endpoints. Pick
|
|
56
|
+
# the matching auth scheme so both credential types work.
|
|
57
|
+
if _looks_like_read_access_token(self._api_key):
|
|
58
|
+
self._client = httpx.AsyncClient(
|
|
59
|
+
base_url=TMDB_BASE_URL,
|
|
60
|
+
headers={"Authorization": f"Bearer {self._api_key}"},
|
|
61
|
+
timeout=30.0,
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
self._client = httpx.AsyncClient(
|
|
65
|
+
base_url=TMDB_BASE_URL,
|
|
66
|
+
params={"api_key": self._api_key},
|
|
67
|
+
timeout=30.0,
|
|
68
|
+
)
|
|
47
69
|
|
|
48
70
|
async def close(self) -> None:
|
|
49
71
|
await self._client.aclose()
|
|
@@ -61,6 +83,13 @@ class TmdbProvider(MetadataProvider):
|
|
|
61
83
|
if cached is not None:
|
|
62
84
|
return cached
|
|
63
85
|
resp = await self._client.get(path, params=params)
|
|
86
|
+
if resp.status_code == 401:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
"TMDb rejected the credential (401 Unauthorized). Check that "
|
|
89
|
+
"your TMDb API Key (v3) or API Read Access Token (v4) is "
|
|
90
|
+
"correct. Both are available at "
|
|
91
|
+
"https://www.themoviedb.org/settings/api."
|
|
92
|
+
)
|
|
64
93
|
resp.raise_for_status()
|
|
65
94
|
data = resp.json()
|
|
66
95
|
if cache_ns and cache_key:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: riplex
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Summary: Automates the tedious manual work around MakeMKV: figuring out what to rip, which MKV files are actually what, and organizing everything into Plex-compatible folder structures.
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -164,8 +164,9 @@ class WelcomeScreen:
|
|
|
164
164
|
ft.Container(height=4),
|
|
165
165
|
self.api_key_field,
|
|
166
166
|
ft.Text(
|
|
167
|
-
"Required. Get a free API
|
|
168
|
-
"used to look up movie and
|
|
167
|
+
"Required. Get a free API Key or Read Access Token at "
|
|
168
|
+
"themoviedb.org/settings/api — used to look up movie and "
|
|
169
|
+
"TV show metadata.",
|
|
169
170
|
size=11,
|
|
170
171
|
color=ft.Colors.GREY_600,
|
|
171
172
|
),
|
|
@@ -77,7 +77,7 @@ def run_setup(force: bool = False) -> int:
|
|
|
77
77
|
value = input(f"{label}{prompt_hint}{suffix}: ").strip()
|
|
78
78
|
return value if value else current
|
|
79
79
|
|
|
80
|
-
tmdb_key = prompt("tmdb_api_key", "TMDb API key", "free at themoviedb.org/settings/api", mask=True)
|
|
80
|
+
tmdb_key = prompt("tmdb_api_key", "TMDb API key or Read Access Token", "free at themoviedb.org/settings/api", mask=True)
|
|
81
81
|
output_root = prompt("output_root", "Plex library root", "e.g. E:/Media")
|
|
82
82
|
rip_output = prompt("rip_output", "MakeMKV rip output folder", "e.g. E:/Media/Rips")
|
|
83
83
|
archive_root = prompt("archive_root", "Archive root (optional)", "move raw rips here after organizing")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from riplex.metadata.sources.tmdb import (
|
|
4
|
+
TmdbProvider,
|
|
5
|
+
_looks_like_read_access_token,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
# A JWT-shaped sample (header.payload.signature). Not a real credential.
|
|
9
|
+
_SAMPLE_READ_ACCESS_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJyaXBsZXgifQ.c2lnbmF0dXJl"
|
|
10
|
+
_SAMPLE_API_KEY = "0123456789abcdef0123456789abcdef"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestLooksLikeReadAccessToken:
|
|
14
|
+
def test_jwt_token_is_detected(self):
|
|
15
|
+
assert _looks_like_read_access_token(_SAMPLE_READ_ACCESS_TOKEN) is True
|
|
16
|
+
|
|
17
|
+
def test_v3_hex_key_is_not_detected(self):
|
|
18
|
+
assert _looks_like_read_access_token(_SAMPLE_API_KEY) is False
|
|
19
|
+
|
|
20
|
+
def test_empty_string_is_not_detected(self):
|
|
21
|
+
assert _looks_like_read_access_token("") is False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestTmdbProviderAuth:
|
|
25
|
+
@pytest.mark.asyncio
|
|
26
|
+
async def test_read_access_token_uses_authorization_header(self):
|
|
27
|
+
provider = TmdbProvider(api_key=_SAMPLE_READ_ACCESS_TOKEN)
|
|
28
|
+
try:
|
|
29
|
+
assert (
|
|
30
|
+
provider._client.headers["Authorization"]
|
|
31
|
+
== f"Bearer {_SAMPLE_READ_ACCESS_TOKEN}"
|
|
32
|
+
)
|
|
33
|
+
assert "api_key" not in provider._client.params
|
|
34
|
+
finally:
|
|
35
|
+
await provider.close()
|
|
36
|
+
|
|
37
|
+
@pytest.mark.asyncio
|
|
38
|
+
async def test_v3_key_uses_query_parameter(self):
|
|
39
|
+
provider = TmdbProvider(api_key=_SAMPLE_API_KEY)
|
|
40
|
+
try:
|
|
41
|
+
assert provider._client.params["api_key"] == _SAMPLE_API_KEY
|
|
42
|
+
assert "Authorization" not in provider._client.headers
|
|
43
|
+
finally:
|
|
44
|
+
await provider.close()
|
|
45
|
+
|
|
46
|
+
@pytest.mark.asyncio
|
|
47
|
+
async def test_whitespace_is_stripped_from_credential(self):
|
|
48
|
+
provider = TmdbProvider(api_key=f" {_SAMPLE_API_KEY} ")
|
|
49
|
+
try:
|
|
50
|
+
assert provider._client.params["api_key"] == _SAMPLE_API_KEY
|
|
51
|
+
finally:
|
|
52
|
+
await provider.close()
|
|
53
|
+
|
|
54
|
+
def test_missing_key_raises(self, monkeypatch):
|
|
55
|
+
monkeypatch.delenv("TMDB_API_KEY", raising=False)
|
|
56
|
+
with pytest.raises(ValueError, match="TMDb API key is required"):
|
|
57
|
+
TmdbProvider(api_key="")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|