Adds efficient data retrieval, query optimization, for server performance. Streamlines the caching mechanism and ensures that the methods are efficient and robust. Clear the caching once a month to keep scripture in sync with API. Adds workflow testing.
This commit is contained in:
parent
25e720788a
commit
270c8f3d48
37
.github/workflows/python-unittest.yml
vendored
Normal file
37
.github/workflows/python-unittest.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: Python Unittest
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.7', '3.8', '3.9', '3.10']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Install the package
|
||||
run: |
|
||||
pip install -e .
|
||||
|
||||
- name: Run unittests
|
||||
run: |
|
||||
python -m unittest discover -s tests
|
10
setup.py
10
setup.py
|
@ -1,11 +1,8 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
with open('requirements.txt') as f:
|
||||
required = f.read().splitlines()
|
||||
|
||||
setup(
|
||||
name="getBible-librarian",
|
||||
version="0.2.0",
|
||||
version="0.2.1",
|
||||
author="Llewellyn van der Merwe",
|
||||
author_email="getbible@vdm.io",
|
||||
description="A Python package to retrieving Bible references with ease.",
|
||||
|
@ -16,7 +13,10 @@ setup(
|
|||
packages=find_packages(where="src"),
|
||||
package_data={"getbible": ["data/*.json"]},
|
||||
include_package_data=True,
|
||||
install_requires=required,
|
||||
install_requires=[
|
||||
"requests~=2.31.0",
|
||||
"setuptools>=65.5.1"
|
||||
],
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)",
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import os
|
||||
import json
|
||||
import requests
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from getbible import GetBibleReference
|
||||
|
||||
|
||||
|
@ -9,6 +12,9 @@ class GetBible:
|
|||
"""
|
||||
Initialize the GetBible class.
|
||||
|
||||
Sets up the class by initializing the cache, starting the background thread for
|
||||
monthly cache reset, and other necessary setups.
|
||||
|
||||
:param repo_path: The repository path, which can be a URL or a local file path.
|
||||
:param version: The version of the Bible repository.
|
||||
"""
|
||||
|
@ -17,9 +23,49 @@ class GetBible:
|
|||
self.__repo_version = version
|
||||
self.__books_cache = {}
|
||||
self.__chapters_cache = {}
|
||||
self.__start_cache_reset_thread()
|
||||
# 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):
|
||||
"""
|
||||
Start a background thread to reset the cache monthly.
|
||||
|
||||
This method creates and starts a daemon thread that runs the cache reset function
|
||||
every month.
|
||||
"""
|
||||
reset_thread = threading.Thread(target=self.__reset_cache_monthly)
|
||||
reset_thread.daemon = True # Daemonize thread
|
||||
reset_thread.start()
|
||||
|
||||
def __reset_cache_monthly(self):
|
||||
"""
|
||||
Periodically clears the cache on the first day of each month.
|
||||
|
||||
This method runs in a background thread and calculates the time until the start
|
||||
of the next month. It sleeps until that time and then clears the cache.
|
||||
"""
|
||||
while True:
|
||||
time_to_sleep = self.__calculate_time_until_next_month()
|
||||
time.sleep(time_to_sleep)
|
||||
self.__chapters_cache.clear()
|
||||
print(f"Cache cleared on {datetime.now()}")
|
||||
|
||||
def __calculate_time_until_next_month(self):
|
||||
"""
|
||||
Calculate the seconds until the start of the next month.
|
||||
|
||||
Determines how many seconds are left until the first day of the next month
|
||||
from the current time. This duration is used by the cache reset thread to
|
||||
sleep until the cache needs to be cleared.
|
||||
|
||||
:return: Number of seconds until the start of the next month.
|
||||
"""
|
||||
now = datetime.now()
|
||||
# Calculate the first day of the next month
|
||||
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.
|
||||
|
@ -37,8 +83,7 @@ class GetBible:
|
|||
except ValueError:
|
||||
raise ValueError(f"Invalid reference format.")
|
||||
|
||||
for verse in reference.verses:
|
||||
self.__set_verse(abbreviation, reference.book, reference.chapter, verse, result)
|
||||
self.__set_verse(abbreviation, reference.book, reference.chapter, reference.verses, result)
|
||||
|
||||
return result
|
||||
|
||||
|
@ -53,32 +98,38 @@ class GetBible:
|
|||
|
||||
return json.dumps(self.select(reference, abbreviation))
|
||||
|
||||
def __set_verse(self, abbreviation, book, chapter, verse, result):
|
||||
def __set_verse(self, abbreviation, book, chapter, verses, result):
|
||||
"""
|
||||
Set verse information into the result JSON.
|
||||
|
||||
:param abbreviation: Bible translation abbreviation.
|
||||
:param book: The book of the Bible.
|
||||
:param chapter: The chapter number.
|
||||
:param verse: The verse number.
|
||||
:param verses: List of verse numbers.
|
||||
:param result: The dictionary to store verse information.
|
||||
"""
|
||||
cache_key = f"{abbreviation}_{book}_{chapter}"
|
||||
if cache_key not in self.__chapters_cache:
|
||||
self.__chapters_cache[cache_key] = self.__retrieve_chapter_data(abbreviation, book, chapter)
|
||||
chapter_data = self.__chapters_cache[cache_key]
|
||||
verse_info = [v for v in chapter_data.get("verses", []) if str(v.get("verse")) == str(verse)]
|
||||
if not verse_info:
|
||||
raise ValueError(f"Verse {verse} not found in book {book}, chapter {chapter}.")
|
||||
|
||||
if cache_key in result:
|
||||
existing_verses = {str(v["verse"]) for v in result[cache_key].get("verses", [])}
|
||||
new_verses = [v for v in verse_info if str(v["verse"]) not in existing_verses]
|
||||
result[cache_key]["verses"].extend(new_verses)
|
||||
chapter_data = self.__retrieve_chapter_data(abbreviation, book, chapter)
|
||||
# Convert verses list to dictionary for faster lookup
|
||||
verse_dict = {str(v["verse"]): v for v in chapter_data.get("verses", [])}
|
||||
chapter_data["verses"] = verse_dict
|
||||
self.__chapters_cache[cache_key] = chapter_data
|
||||
else:
|
||||
verse_data = chapter_data.copy()
|
||||
verse_data["verses"] = verse_info
|
||||
result[cache_key] = verse_data
|
||||
chapter_data = self.__chapters_cache[cache_key]
|
||||
|
||||
for verse in verses:
|
||||
verse_info = chapter_data["verses"].get(str(verse))
|
||||
if not verse_info:
|
||||
raise ValueError(f"Verse {verse} not found in book {book}, chapter {chapter}.")
|
||||
|
||||
if cache_key in result:
|
||||
existing_verses = {str(v["verse"]) for v in result[cache_key].get("verses", [])}
|
||||
if str(verse) not in existing_verses:
|
||||
result[cache_key]["verses"].append(verse_info)
|
||||
else:
|
||||
# Include all other relevant elements of your JSON structure
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -66,6 +66,74 @@ class TestGetBible(unittest.TestCase):
|
|||
"text": "Wat van die begin af was, wat ons gehoor het, wat ons met ons o\u00eb gesien het, wat ons aanskou het en ons hande getas het aangaande die Woord van die lewe \u2014 "}]}}
|
||||
self.assertEqual(actual_result, expected_result, "Failed to find 'Ge1:1;Jn1:1;1Jn1:1' scripture.")
|
||||
|
||||
def test_valid_multiple_reference_select_aleppo(self):
|
||||
actual_result = self.getbible.select('Ge1:1-3;Ps1:1;ps1:1-2;Ge1:6-7,10', 'aleppo')
|
||||
expected_result = {
|
||||
'aleppo_19_1': {'abbreviation': 'aleppo',
|
||||
'book_name': 'תְּהִלִּים',
|
||||
'book_nr': 19,
|
||||
'chapter': 1,
|
||||
'direction': 'RTL',
|
||||
'encoding': 'UTF-8',
|
||||
'lang': 'hbo',
|
||||
'language': 'Hebrew',
|
||||
'name': 'תְּהִלִּים 1',
|
||||
'translation': 'Aleppo Codex',
|
||||
'verses': [{'chapter': 1,
|
||||
'name': 'תְּהִלִּים 1:1',
|
||||
'text': '\xa0\xa0אשרי האיש— \xa0\xa0 אשר לא הלך '
|
||||
'בעצת רשעיםובדרך חטאים לא עמד \xa0\xa0 '
|
||||
'ובמושב לצים לא ישב ',
|
||||
'verse': 1},
|
||||
{'chapter': 1,
|
||||
'name': 'תְּהִלִּים 1:2',
|
||||
'text': '\xa0\xa0כי אם בתורת יהוה חפצו \xa0\xa0 '
|
||||
'ובתורתו יהגה יומם ולילה ',
|
||||
'verse': 2}]},
|
||||
'aleppo_1_1': {'abbreviation': 'aleppo',
|
||||
'book_name': 'בְּרֵאשִׁית',
|
||||
'book_nr': 1,
|
||||
'chapter': 1,
|
||||
'direction': 'RTL',
|
||||
'encoding': 'UTF-8',
|
||||
'lang': 'hbo',
|
||||
'language': 'Hebrew',
|
||||
'name': 'בְּרֵאשִׁית 1',
|
||||
'translation': 'Aleppo Codex',
|
||||
'verses': [{'chapter': 1,
|
||||
'name': 'בְּרֵאשִׁית 1:1',
|
||||
'text': 'בראשית ברא אלהים את השמים ואת הארץ ',
|
||||
'verse': 1},
|
||||
{'chapter': 1,
|
||||
'name': 'בְּרֵאשִׁית 1:2',
|
||||
'text': 'והארץ היתה תהו ובהו וחשך על פני תהום ורוח '
|
||||
'אלהים מרחפת על פני המים ',
|
||||
'verse': 2},
|
||||
{'chapter': 1,
|
||||
'name': 'בְּרֵאשִׁית 1:3',
|
||||
'text': 'ויאמר אלהים יהי אור ויהי אור ',
|
||||
'verse': 3},
|
||||
{'chapter': 1,
|
||||
'name': 'בְּרֵאשִׁית 1:6',
|
||||
'text': 'ויאמר אלהים יהי רקיע בתוך המים ויהי מבדיל '
|
||||
'בין מים למים ',
|
||||
'verse': 6},
|
||||
{'chapter': 1,
|
||||
'name': 'בְּרֵאשִׁית 1:7',
|
||||
'text': 'ויעש אלהים את הרקיע ויבדל בין המים אשר '
|
||||
'מתחת לרקיע ובין המים אשר מעל לרקיע ויהי '
|
||||
'כן ',
|
||||
'verse': 7},
|
||||
{'chapter': 1,
|
||||
'name': 'בְּרֵאשִׁית 1:10',
|
||||
'text': 'ויקרא אלהים ליבשה ארץ ולמקוה המים קרא '
|
||||
'ימים וירא אלהים כי טוב ',
|
||||
'verse': 10}]
|
||||
}
|
||||
}
|
||||
|
||||
self.assertEqual(actual_result, expected_result, "Failed to find 'Ge1:1-3;Ps1:1;ps1:1-2;Ge1:6-7,1' scripture.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in New Issue
Block a user