refspy.indexers
Indexing functions for libraries, books, and book aliases.
1"""Indexing functions for libraries, books, and book aliases.""" 2 3from refspy.models.book import Book 4from refspy.models.library import Library 5 6from refspy.types.number import Number 7 8from refspy.utils import strip_book_number 9 10PARAM_CHAR_LIMIT = 20 11"""The maximum book name or alias length to convert to URL parameter names.""" 12 13 14def index_libraries(libraries: list[Library]) -> dict[Number, Library]: 15 """Create a lookup dict for libraries by library.id""" 16 index: dict[Number, Library] = dict() 17 for library in libraries: 18 index[library.id] = library 19 return index 20 21 22def index_books(libraries: list[Library]) -> dict[tuple[Number, Number], Book]: 23 """Create a lookup dict for books by (library.id, book.id)""" 24 index: dict[tuple[Number, Number], Book] = dict() 25 for library in libraries: 26 for book in library.books: 27 index[library.id, book.id] = book 28 return index 29 30 31def add_unique_book_alias( 32 index: dict[str, tuple[Number, Number]], 33 alias: str, 34 library_id: Number, 35 book_id: Number, 36 strict: bool = True, 37): 38 """Add an index entry for a library and book if none exists 39 40 Modifies the passed index dict. 41 42 Args: 43 strict: raise an error in the case of a collision between 44 alias values. 45 """ 46 if alias in index and strict: 47 raise ValueError(f"Book alias '{alias}' is not unique.") 48 if alias == "": 49 raise ValueError(f"Book alias is '' for ({library_id},{book_id})") 50 else: 51 index[alias] = (library_id, book_id) 52 53 54def index_book_aliases( 55 libraries: list[Library], 56 include_two_letter_aliases: bool = True, 57 strict: bool = True, 58) -> dict[str, tuple[Number, Number]]: 59 """Create a lookup table for library and book IDs by book aliases. 60 61 Args: 62 libraries: A list of `refspy.library.Library` objects to be indexed. 63 include_two_letter_aliases: Allow book aliases of only two letters, 64 i.e. 'Jn' and '1 Ti'. 65 strict: Throw a ValueError if any two aliases are the same, or any 66 alias is an empty string. 67 68 Note: 69 Because books like Ruth or Mark have book.abbrev == book.name, we only 70 index the abbrev if it differs from the name. These books will not raise 71 a ValueError when strict=True. 72 """ 73 index: dict[str, tuple[Number, Number]] = dict() 74 for library in libraries: 75 for book in library.books: 76 aliases = [book.name] 77 if book.name != book.abbrev: 78 aliases.append(book.abbrev) 79 for alias in book.aliases: 80 len_alias = len(strip_book_number(alias)) 81 if (len_alias > 2 and len_alias < PARAM_CHAR_LIMIT) or ( 82 include_two_letter_aliases and len_alias == 2 83 ): 84 aliases.append(alias) 85 for alias in aliases: 86 add_unique_book_alias(index, alias, library.id, book.id, strict) 87 return index
PARAM_CHAR_LIMIT =
20
The maximum book name or alias length to convert to URL parameter names.
def
index_libraries( libraries: list[refspy.models.library.Library]) -> dict[typing.Annotated[int, Ge(ge=1), Le(le=999)], refspy.models.library.Library]:
15def index_libraries(libraries: list[Library]) -> dict[Number, Library]: 16 """Create a lookup dict for libraries by library.id""" 17 index: dict[Number, Library] = dict() 18 for library in libraries: 19 index[library.id] = library 20 return index
Create a lookup dict for libraries by library.id
def
index_books( libraries: list[refspy.models.library.Library]) -> dict[tuple[typing.Annotated[int, Ge(ge=1), Le(le=999)], typing.Annotated[int, Ge(ge=1), Le(le=999)]], refspy.models.book.Book]:
23def index_books(libraries: list[Library]) -> dict[tuple[Number, Number], Book]: 24 """Create a lookup dict for books by (library.id, book.id)""" 25 index: dict[tuple[Number, Number], Book] = dict() 26 for library in libraries: 27 for book in library.books: 28 index[library.id, book.id] = book 29 return index
Create a lookup dict for books by (library.id, book.id)
def
add_unique_book_alias( index: dict[str, tuple[typing.Annotated[int, Ge(ge=1), Le(le=999)], typing.Annotated[int, Ge(ge=1), Le(le=999)]]], alias: str, library_id: Annotated[int, Ge(ge=1), Le(le=999)], book_id: Annotated[int, Ge(ge=1), Le(le=999)], strict: bool = True):
32def add_unique_book_alias( 33 index: dict[str, tuple[Number, Number]], 34 alias: str, 35 library_id: Number, 36 book_id: Number, 37 strict: bool = True, 38): 39 """Add an index entry for a library and book if none exists 40 41 Modifies the passed index dict. 42 43 Args: 44 strict: raise an error in the case of a collision between 45 alias values. 46 """ 47 if alias in index and strict: 48 raise ValueError(f"Book alias '{alias}' is not unique.") 49 if alias == "": 50 raise ValueError(f"Book alias is '' for ({library_id},{book_id})") 51 else: 52 index[alias] = (library_id, book_id)
Add an index entry for a library and book if none exists
Modifies the passed index dict.
Arguments:
- strict: raise an error in the case of a collision between alias values.
def
index_book_aliases( libraries: list[refspy.models.library.Library], include_two_letter_aliases: bool = True, strict: bool = True) -> dict[str, tuple[typing.Annotated[int, Ge(ge=1), Le(le=999)], typing.Annotated[int, Ge(ge=1), Le(le=999)]]]:
55def index_book_aliases( 56 libraries: list[Library], 57 include_two_letter_aliases: bool = True, 58 strict: bool = True, 59) -> dict[str, tuple[Number, Number]]: 60 """Create a lookup table for library and book IDs by book aliases. 61 62 Args: 63 libraries: A list of `refspy.library.Library` objects to be indexed. 64 include_two_letter_aliases: Allow book aliases of only two letters, 65 i.e. 'Jn' and '1 Ti'. 66 strict: Throw a ValueError if any two aliases are the same, or any 67 alias is an empty string. 68 69 Note: 70 Because books like Ruth or Mark have book.abbrev == book.name, we only 71 index the abbrev if it differs from the name. These books will not raise 72 a ValueError when strict=True. 73 """ 74 index: dict[str, tuple[Number, Number]] = dict() 75 for library in libraries: 76 for book in library.books: 77 aliases = [book.name] 78 if book.name != book.abbrev: 79 aliases.append(book.abbrev) 80 for alias in book.aliases: 81 len_alias = len(strip_book_number(alias)) 82 if (len_alias > 2 and len_alias < PARAM_CHAR_LIMIT) or ( 83 include_two_letter_aliases and len_alias == 2 84 ): 85 aliases.append(alias) 86 for alias in aliases: 87 add_unique_book_alias(index, alias, library.id, book.id, strict) 88 return index
Create a lookup table for library and book IDs by book aliases.
Arguments:
- libraries: A list of
refspy.library.Libraryobjects to be indexed. - include_two_letter_aliases: Allow book aliases of only two letters, i.e. 'Jn' and '1 Ti'.
- strict: Throw a ValueError if any two aliases are the same, or any alias is an empty string.
Note:
Because books like Ruth or Mark have book.abbrev == book.name, we only index the abbrev if it differs from the name. These books will not raise a ValueError when strict=True.