Skip to content

Commit

Permalink
Adds better method control, and overall stability. Adds valid_referen…
Browse files Browse the repository at this point in the history
…ce and valid_translation methods.
  • Loading branch information
Llewellynvdm committed Nov 25, 2023
1 parent 07f1c95 commit 8901087
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 165 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name="getbible",
version="1.0.2",
version="1.0.3",
author="Llewellyn van der Merwe",
author_email="[email protected]",
description="A Python package to retrieving Bible references with ease.",
Expand Down
121 changes: 74 additions & 47 deletions src/getbible/getbible.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import os
import re
import json
import requests
import threading
import time
from datetime import datetime, timedelta
from typing import Dict, Optional, Union
from getbible import GetBibleReference


class GetBible:
def __init__(self, repo_path="https://api.getbible.net", version='v2'):
def __init__(self, repo_path: str = "https://api.getbible.net", version: str = 'v2') -> None:
"""
Initialize the GetBible class.
Expand All @@ -24,10 +26,69 @@ def __init__(self, repo_path="https://api.getbible.net", version='v2'):
self.__books_cache = {}
self.__chapters_cache = {}
self.__start_cache_reset_thread()
self.__pattern = re.compile(r'^[a-zA-Z0-9]{1,30}$')
# Determine if the repository path is a URL
self.__repo_path_url = self.__repo_path.startswith("http://") or self.__repo_path.startswith("https://")

def __start_cache_reset_thread(self):
def select(self, reference: str, abbreviation: Optional[str] = 'kjv') -> Dict[str, Union[Dict, str]]:
"""
Select and return Bible verses based on the reference and abbreviation.
:param reference: The Bible reference (e.g., John 3:16).
:param abbreviation: The abbreviation for the Bible translation.
:return: dictionary of the selected Bible verses.
"""
self.__check_translation(abbreviation)
result = {}
references = reference.split(';')
for ref in references:
try:
reference = self.__get.ref(ref, abbreviation)
except ValueError:
raise ValueError(f"Invalid reference '{ref}'.")

self.__set_verse(abbreviation, reference.book, reference.chapter, reference.verses, result)

return result

def scripture(self, reference: str, abbreviation: Optional[str] = 'kjv') -> str:
"""
Select and return Bible verses based on the reference and abbreviation.
:param reference: The Bible reference (e.g., John 3:16).
:param abbreviation: The abbreviation for the Bible translation.
:return: JSON string of the selected Bible verses.
"""

return json.dumps(self.select(reference, abbreviation))

def valid_reference(self, reference: str, abbreviation: Optional[str] = 'kjv') -> bool:
"""
Validate a scripture reference and check its presence in the cache.
:param reference: Scripture reference string.
:param abbreviation: Optional translation code.
:return: True if valid and present, False otherwise.
"""
return self.__get.valid(reference, abbreviation)

def valid_translation(self, abbreviation: str) -> bool:
"""
Check if the given translation is valid.
:param abbreviation: The abbreviation of the Bible translation to check.
:return: True if the translation is available, False otherwise.
"""
if self.__pattern.match(abbreviation):
path = self.__generate_path(abbreviation, "books.json")
# Check if the translation is already in the cache
if abbreviation not in self.__books_cache:
self.__books_cache[abbreviation] = self.__fetch_data(path)
# Return True if the translation is available, False otherwise
return self.__books_cache[abbreviation] is not None
return False

def __start_cache_reset_thread(self) -> None:
"""
Start a background thread to reset the cache monthly.
Expand All @@ -38,7 +99,7 @@ def __start_cache_reset_thread(self):
reset_thread.daemon = True # Daemonize thread
reset_thread.start()

def __reset_cache_monthly(self):
def __reset_cache_monthly(self) -> None:
"""
Periodically clears the cache on the first day of each month.
Expand All @@ -51,7 +112,7 @@ def __reset_cache_monthly(self):
self.__chapters_cache.clear()
print(f"Cache cleared on {datetime.now()}")

def __calculate_time_until_next_month(self):
def __calculate_time_until_next_month(self) -> float:
"""
Calculate the seconds until the start of the next month.
Expand All @@ -66,39 +127,7 @@ def __calculate_time_until_next_month(self):
first_of_next_month = (now.replace(day=1) + timedelta(days=32)).replace(day=1)
return (first_of_next_month - now).total_seconds()

def select(self, reference, abbreviation='kjv'):
"""
Select and return Bible verses based on the reference and abbreviation.
:param reference: The Bible reference (e.g., John 3:16).
:param abbreviation: The abbreviation for the Bible translation.
:return: dictionary of the selected Bible verses.
"""
self.__check_translation(abbreviation)
result = {}
references = reference.split(';')
for ref in references:
try:
reference = self.__get.ref(ref, abbreviation)
except ValueError:
raise ValueError(f"Invalid reference format.")

self.__set_verse(abbreviation, reference.book, reference.chapter, reference.verses, result)

return result

def scripture(self, reference, abbreviation='kjv'):
"""
Select and return Bible verses based on the reference and abbreviation.
:param reference: The Bible reference (e.g., John 3:16).
:param abbreviation: The abbreviation for the Bible translation.
:return: JSON string of the selected Bible verses.
"""

return json.dumps(self.select(reference, abbreviation))

def __set_verse(self, abbreviation, book, chapter, verses, result):
def __set_verse(self, abbreviation: str, book: int, chapter: int, verses: list, result: Dict) -> None:
"""
Set verse information into the result JSON.
:param abbreviation: Bible translation abbreviation.
Expand Down Expand Up @@ -131,20 +160,18 @@ def __set_verse(self, abbreviation, book, chapter, verses, result):
result[cache_key] = {key: chapter_data[key] for key in chapter_data if key != "verses"}
result[cache_key]["verses"] = [verse_info]

def __check_translation(self, abbreviation):
def __check_translation(self, abbreviation: str) -> None:
"""
Check if the given translation is available.
Check if the given translation is available and raises an exception if not found.
:param abbreviation: The abbreviation of the Bible translation to check.
:raises FileNotFoundError: If the translation is not found.
"""
path = self.__generate_path(abbreviation, "books.json")
if abbreviation not in self.__books_cache:
self.__books_cache[abbreviation] = self.__fetch_data(path)
if self.__books_cache[abbreviation] is None:
raise FileNotFoundError(f"Translation ({abbreviation}) not found in this API.")
# Use valid_translation to check if the translation is available
if not self.valid_translation(abbreviation):
raise FileNotFoundError(f"Translation ({abbreviation}) not found in this API.")

def __generate_path(self, abbreviation, file_name):
def __generate_path(self, abbreviation: str, file_name: str) -> str:
"""
Generate the path or URL for a given file.
Expand All @@ -157,7 +184,7 @@ def __generate_path(self, abbreviation, file_name):
else:
return os.path.join(self.__repo_path, self.__repo_version, abbreviation, file_name)

def __fetch_data(self, path):
def __fetch_data(self, path: str) -> Optional[Dict]:
"""
Fetch data from either a URL or a local file path.
Expand All @@ -177,7 +204,7 @@ def __fetch_data(self, path):
else:
return None

def __retrieve_chapter_data(self, abbreviation, book, chapter):
def __retrieve_chapter_data(self, abbreviation: str, book: int, chapter: int) -> Dict:
"""
Retrieve chapter data for a given book and chapter.
Expand Down
58 changes: 44 additions & 14 deletions src/getbible/getbible_book_number.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
from .getbible_reference_trie import GetBibleReferenceTrie
import os
from typing import Any, List, Optional


class GetBibleBookNumber:
def __init__(self):
def __init__(self) -> None:
"""
Initialize the GetBibleBookNumber class.
Sets up the class by loading all translation tries from the data directory.
"""
self._tries = {}
self._data_path = os.path.join(os.path.dirname(__file__), 'data')
self._load_all_translations()
self.__load_all_translations()

def __load_translation(self, filename: str) -> None:
"""
Load a translation trie from a specified file.
def _load_translation(self, filename):
:param filename: The name of the file to load.
:raises IOError: If there is an error loading the file.
"""
trie = GetBibleReferenceTrie()
translation_code = filename.split('.')[0]
try:
Expand All @@ -17,27 +29,38 @@ def _load_translation(self, filename):
raise IOError(f"Error loading translation {translation_code}: {e}")
self._tries[translation_code] = trie

def _load_all_translations(self):
def __load_all_translations(self) -> None:
"""
Load all translation tries from the data directory.
"""
for filename in os.listdir(self._data_path):
if filename.endswith('.json'):
self._load_translation(filename)
self.__load_translation(filename)

def number(self, reference, translation_code=None, fallback_translations=None):
# Default to 'kjv' if no translation code is provided
def number(self, reference: str, translation_code: Optional[str] = None,
fallback_translations: Optional[List[str]] = None) -> Optional[int]:
"""
Get the book number based on a reference and translation code.
:param reference: The reference to search for.
:param translation_code: The code for the translation to use.
:param fallback_translations: A list of fallback translations to use if necessary.
:return: The book number as an integer if found, None otherwise.
"""
if not translation_code or translation_code not in self._tries:
translation_code = 'kjv'

translation = self._tries.get(translation_code)
result = translation.search(reference) if translation else None
if result:
return result
if result and result.isdigit():
return int(result)

# If 'kjv' is not the original choice, try it next
if translation_code != 'kjv':
translation = self._tries.get('kjv')
result = translation.search(reference) if translation else None
if result:
return result
if result and result.isdigit():
return int(result)

# Fallback to other translations
if fallback_translations is None:
Expand All @@ -46,12 +69,19 @@ def number(self, reference, translation_code=None, fallback_translations=None):
for code in fallback_translations:
translation = self._tries.get(code)
result = translation.search(reference) if translation else None
if result:
return result
if result and result.isdigit():
return int(result)

return None

def dump(self, translation_code, filename):
def dump(self, translation_code: str, filename: str) -> None:
"""
Dump the trie data for a specific translation to a file.
:param translation_code: The code for the translation.
:param filename: The name of the file to dump to.
:raises ValueError: If no data is available for the specified translation.
"""
if translation_code in self._tries:
self._tries[translation_code].dump(filename)
else:
Expand Down
Loading

0 comments on commit 8901087

Please sign in to comment.