mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-11-15 20:58:18 +00:00
## feat(metadata): add Deutsche Nationalbibliothek (DNB) metadata provider This adds the Deutsche Nationalbibliothek (DNB) as a metadata provider. It covers German editions far better than the existing providers. No API key, disabled by default, and the same pattern as the other providers. It uses DNB's public SRU interface and parses MARC21-XML. Thats one reason why this needs more code than the Google Books or Open Library providers. The others are the search (DNB has no relevance ranking) and the language filter, see below. ### Notes - **Search box.** Search-box text goes from precise to loose until a stage has hits: the complete title, then the title as a phrase, then every word in a title or a person's name, last without German articles. - **"Only show editions in my book languages"** (optional, on by default). DNB also catalogs a lot of translations. DNB filters by the searching user's Default Book Languages, else the system default. - **No covers.** DNB's cover service is behind Anubis bot protection, so the provider returns no cover URLs. - **Proxy support** already included like Open Library since #1526 ### Changes outside the provider - DNB is in the onboarding dropdown. - Calibre's `dnb-idn` identifier is mapped, so DNB results show library ownership - In the env-var docs I added only the DNB section. Regenerating the whole file on main currently also reorders unrelated sections and drops `SHELFMARK_API_KEY_READONLY` (and the Databáze knih section sits inside the Moly.hu block). ### Ideas - **Deduplication.** DNB lists every edition. Kafka's "Die Verwandlung" has around 200 German editions of the same text. Grouping them would touch the API, the frontend and release search. - **Language-aware metadata search** for all providers. The other providers ignore Default Book Languages in metadata search. The `user_id` added here could serve them too.
96 lines
3.4 KiB
Python
96 lines
3.4 KiB
Python
"""The metadata search and book endpoints carry per-format library ownership."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from shelfmark.metadata_providers import BookMetadata, SearchResult
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def main_module():
|
|
with patch("shelfmark.download.orchestrator.start"):
|
|
import shelfmark.main as main
|
|
|
|
importlib.reload(main)
|
|
return main
|
|
|
|
|
|
@pytest.fixture
|
|
def client(main_module):
|
|
return main_module.app.test_client()
|
|
|
|
|
|
class _StubProvider:
|
|
name = "stub"
|
|
display_name = "Stub"
|
|
search_fields: list[object] = []
|
|
|
|
def __init__(self, books: list[BookMetadata]) -> None:
|
|
self._books = books
|
|
|
|
def is_available(self) -> bool:
|
|
return True
|
|
|
|
def search_paginated(self, options):
|
|
return SearchResult(books=self._books, page=1, total_found=len(self._books), has_more=False)
|
|
|
|
def get_book(self, book_id: str) -> BookMetadata | None:
|
|
return next((b for b in self._books if b.provider_id == book_id), None)
|
|
|
|
|
|
def _search(main_module, client, ownership):
|
|
book = BookMetadata(provider="stub", provider_id="42", title="Dungeon Crawler Carl")
|
|
stub = _StubProvider([book])
|
|
with (
|
|
patch.object(main_module, "get_auth_mode", return_value="none"),
|
|
patch("shelfmark.metadata_providers.is_provider_registered", return_value=True),
|
|
patch("shelfmark.metadata_providers.is_provider_enabled", return_value=True),
|
|
patch("shelfmark.metadata_providers.get_provider_kwargs", return_value={}),
|
|
patch("shelfmark.metadata_providers.get_provider", return_value=stub),
|
|
patch("shelfmark.metadata_providers.get_configured_provider", return_value=stub),
|
|
patch("shelfmark.core.library_index.ownership", ownership),
|
|
):
|
|
response = client.get("/api/metadata/search?query=carl&provider=stub")
|
|
assert response.status_code == 200, response.get_json()
|
|
return response.get_json()["books"][0]
|
|
|
|
|
|
def test_search_results_carry_library_ownership(main_module, client):
|
|
result = _search(main_module, client, lambda book: {"ebook": True, "audiobook": False})
|
|
|
|
assert result["library"] == {"ebook": True, "audiobook": False}
|
|
|
|
|
|
def test_search_results_omit_library_when_no_check_is_enabled(main_module, client):
|
|
result = _search(main_module, client, lambda book: None)
|
|
|
|
assert "library" not in result
|
|
|
|
|
|
def test_search_passes_the_logged_in_user_to_the_provider(main_module, client):
|
|
# Providers may resolve per-user settings, e.g. DNB's book languages
|
|
seen = []
|
|
|
|
class _RecordingProvider(_StubProvider):
|
|
def search_paginated(self, options):
|
|
seen.append(options.user_id)
|
|
return super().search_paginated(options)
|
|
|
|
with client.session_transaction() as session:
|
|
session["db_user_id"] = 7
|
|
with (
|
|
patch.object(main_module, "get_auth_mode", return_value="none"),
|
|
patch("shelfmark.metadata_providers.is_provider_registered", return_value=True),
|
|
patch("shelfmark.metadata_providers.is_provider_enabled", return_value=True),
|
|
patch("shelfmark.metadata_providers.get_provider_kwargs", return_value={}),
|
|
patch("shelfmark.metadata_providers.get_provider", return_value=_RecordingProvider([])),
|
|
):
|
|
response = client.get("/api/metadata/search?query=carl&provider=stub")
|
|
|
|
assert response.status_code == 200, response.get_json()
|
|
assert seen == [7]
|