Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds better method control, and overall stability.... #5

Merged
merged 1 commit into from
Nov 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading