LSBible API client - structured, type-safe Bible SDK
Project description
LSBible Python SDK
A structured, type-safe Python client for the LSBible API at read.lsbible.org.
Disclaimer: This is an unofficial, third-party SDK and is not affiliated with, endorsed by, or connected to LSBible or its creators. This project is an independent client library for educational and development purposes.
Why the LSB?
The Legacy Standard Bible (LSB) is a modern literal translation that prioritizes accuracy and consistency:
- ๐ Formal Equivalence - Word-for-word translation philosophy preserving original structure
- โ๏ธ Based on NASB95 - Built on the respected New American Standard Bible, updated for modern scholarship
- ๐ค Consistent Translation - Same Hebrew/Greek words translated consistently throughout
- ๐ Divine Name - Uses "Yahweh" for the Tetragrammaton (YHWH) instead of "LORD"
- ๐ Freely Accessible - Available for use in applications and tools
- ๐ Modern Scholarship - Published in 2021 with latest textual research
The LSB's literal approach makes it ideal for serious Bible study, and its structured HTML output is perfect for SDK development, preserving formatting like red-letter text for Jesus' words and italics for translator clarifications.
Table of Contents
- Why the LSB?
- Features at a Glance
- Installation
- Quick Start
- SDK Usage Guide
- API Reference
- Development
- Contributing
- License
Features at a Glance
โจ What makes this SDK special:
- โ 100% Type-Safe - Full Pydantic validation with type hints throughout
- โ Structured Parameters - No string parsing, explicit book/chapter/verse parameters
- โ IDE Autocomplete - Enum-based book names with all 66 books
- โ Strict Validation - Early error detection before API calls
- โ Response Caching - Built-in TTL-based caching for better performance
- โ Rich Formatting - Extract red-letter text, italics, small-caps, and more
- โ Search Analytics - Distribution metadata across Bible sections and books
- โ Immutable Models - All data structures are frozen for safety
- โ Comprehensive Tests - >80% code coverage with thorough test suite
Installation
Install the LSBible Python SDK using your preferred package manager:
# Using uv (recommended)
uv pip install lsbible
# Using pip
pip install lsbible
Quick Start
import asyncio
from lsbible import LSBibleClient, BookName
async def main():
# Initialize client (use async context manager for automatic cleanup)
async with LSBibleClient() as client:
# Get a single verse
passage = await client.get_verse(BookName.JOHN, 3, 16)
print(f"Reference: {passage.title}")
print(f"Text: {passage.verses[0].plain_text}")
# Get a passage range
passage = await client.get_passage(
BookName.JOHN, 3, 16,
BookName.JOHN, 3, 18
)
print(f"Got {passage.verse_count} verses")
# Get an entire chapter
chapter = await client.get_chapter(BookName.JOHN, 3)
print(f"John 3 has {chapter.verse_count} verses")
# Search for text
results = await client.search("love")
print(f"Found {results.match_count} passages")
asyncio.run(main())
SDK Usage Guide
Core Concepts
Design Philosophy: Structured Parameters Over String Parsing
This SDK uses explicit, validated parameters instead of parsing strings:
from lsbible import LSBibleClient, BookName
client = LSBibleClient()
# โ
GOOD - Type-safe with validation
passage = client.get_verse(BookName.JOHN, 3, 16)
# โ
ALSO GOOD - String validated at runtime
passage = client.get_verse("John", 3, 16)
# โ NOT SUPPORTED - String parsing
passage = client.get_verse("John 3:16") # This won't work
Why this approach?
- Full IDE autocomplete - Type
BookName.and see all 66 books - Catch errors early - Invalid references caught before API calls
- No parsing ambiguity - Clear distinction between book, chapter, verse
- Better testing - Easy to generate test cases programmatically
- Type safety - Python type checkers can verify your code
Using Book Names
You have two options for specifying books:
Option 1: BookName Enum (Recommended)
from lsbible import BookName
# Full IDE autocomplete support
BookName.JOHN
BookName.GENESIS
BookName.REVELATION
BookName.SAMUEL_1 # For numbered books
BookName.CORINTHIANS_2
Option 2: Strings (Also Valid)
# Simple string (case-insensitive, normalized)
client.get_verse("John", 3, 16)
client.get_verse("john", 3, 16) # Also works
client.get_verse("1 John", 1, 1) # Numbered books
Basic Examples
Fetching a Single Verse
from lsbible import LSBibleClient, BookName
with LSBibleClient() as client:
passage = client.get_verse(BookName.JOHN, 3, 16)
# Access basic info
verse = passage.verses[0]
print(f"Reference: {verse.reference}") # "John 3:16"
print(f"Plain text: {verse.plain_text}")
# Check if it's a single verse
print(f"Single verse: {passage.is_single_verse}") # True
Fetching a Passage Range
# Same book, same chapter
passage = client.get_passage(
BookName.JOHN, 3, 16, # From: John 3:16
BookName.JOHN, 3, 18 # To: John 3:18
)
# Same book, different chapters
passage = client.get_passage(
BookName.JOHN, 1, 1, # From: John 1:1
BookName.JOHN, 2, 11 # To: John 2:11
)
# Different books
passage = client.get_passage(
BookName.JUDE, 1, 24, # From: Jude 1:24
BookName.JUDE, 1, 25 # To: Jude 1:25
)
print(f"Got {passage.verse_count} verses")
Fetching an Entire Chapter
# Get all verses in John chapter 3
chapter = client.get_chapter(BookName.JOHN, 3)
print(f"Chapter title: {chapter.title}")
print(f"Total verses: {chapter.verse_count}")
for verse in chapter.verses:
print(f"Verse {verse.verse_number}: {verse.plain_text}")
Searching for Text
# Search for verses containing "love"
results = client.search("love")
print(f"Query: {results.query}")
print(f"Total matches: {results.match_count}")
print(f"Passages returned: {results.passage_count}")
# Display results
for passage in results.passages:
print(f"\n{passage.title}")
for verse in passage.verses:
print(f" {verse.plain_text}")
Advanced Features
Accessing Rich Formatting
The SDK preserves all formatting from the LSB translation:
passage = client.get_verse(BookName.JOHN, 3, 16)
verse = passage.verses[0]
# Iterate through text segments with formatting metadata
for segment in verse.segments:
# Check formatting flags
if segment.is_red_letter:
print(f'๐ด Jesus said: "{segment.text}"')
elif segment.is_italic:
print(f'๐ Clarification: [{segment.text}]')
elif segment.is_small_caps:
print(f'โจ LORD (YHWH): {segment.text}')
elif segment.is_bold:
print(f'๐ Emphasis: {segment.text}')
else:
print(f' Regular: {segment.text}')
# Or use the formatted_text property for simple formatting
print(verse.formatted_text)
# Output: "For God so loved the world, that He gave..."
Formatting Metadata:
is_red_letter: Words of Jesus Christ (traditionally printed in red)is_italic: Clarifying words added by translatorsis_small_caps: "LORD" representing YHWH (Yahweh)is_bold: Emphasis in the original text- Poetry/prose detection:
verse.is_poetryorverse.is_prose - Subheadings:
verse.has_subheadingandverse.subheading_text
Working with Verse Metadata
passage = client.get_verse(BookName.PSALMS, 23, 1)
verse = passage.verses[0]
# Check structural features
if verse.has_subheading:
print(f"Subheading: {verse.subheading_text}")
if verse.is_poetry:
print("This is poetic text")
if verse.chapter_start:
print("This is the first verse of a chapter")
# Access reference details
ref = verse.reference
print(f"Book: {ref.book_name.value}") # "Psalms"
print(f"Chapter: {ref.chapter}") # 23
print(f"Verse: {ref.verse}") # 1
print(f"Book number: {ref.book_number}") # 19 (Psalms is the 19th book)
Configuring the Client
from lsbible import LSBibleClient, MemoryCacheProvider, CacheTTL
# Configure cache with per-operation TTLs
async with LSBibleClient(
cache={
"provider": MemoryCacheProvider(),
"ttl": {
"verse": CacheTTL.BIBLE_CONTENT, # 30 days
"passage": CacheTTL.BIBLE_CONTENT, # 30 days
"chapter": CacheTTL.BIBLE_CONTENT, # 30 days
"search": CacheTTL.SEARCH_RESULTS, # 7 days
}
},
timeout=60, # Request timeout in seconds (default: 30)
build_id="custom" # Optional: provide build ID (default: auto-detect)
) as client:
# Use the client
passage = await client.get_verse("John", 3, 16)
# Clear cache manually (MemoryCacheProvider only)
client.clear_cache()
# Use custom headers (e.g., for tracking)
custom_headers = {
"X-App-Name": "My Bible App",
"X-App-Version": "1.0.0"
}
async with LSBibleClient(headers=custom_headers) as client:
results = await client.search("love")
Search Distribution Metadata
When you search for text (not a Bible reference), the API returns rich metadata showing how matches are distributed across Scripture.
Understanding Search Responses
results = client.search("love")
# Basic info available for ALL searches
print(f"Query: {results.query}")
print(f"Total matches: {results.match_count}")
print(f"Passages returned: {results.passage_count}")
# Distribution metadata (only for TEXT searches)
if results.has_search_metadata:
print("\n๐ Distribution Metadata Available!")
print(f"Total count: {results.total_count}")
print(f"Filtered count: {results.filtered_count}")
else:
print("No distribution metadata (Bible reference lookup)")
Distribution by Bible Section
The Bible is divided into 8 major sections. Search metadata shows match counts for each:
results = client.search("love")
if results.has_search_metadata:
from lsbible.books import SECTION_NAMES
print("\n๐ Distribution by Section:")
for section_id, count in results.counts_by_section.items():
section_name = SECTION_NAMES[section_id]
print(f" {section_name}: {count} matches")
The 8 Bible Sections:
- Pentateuch (Genesis - Deuteronomy)
- History (Joshua - Esther)
- Wisdom and Poetry (Job - Song of Songs)
- Major Prophets (Isaiah - Daniel)
- Minor Prophets (Hosea - Malachi)
- Gospels and Acts (Matthew - Acts)
- Pauline Epistles (Romans - Philemon)
- General Epistles (Hebrews - Revelation)
Distribution by Individual Books
See which specific books contain the most matches:
results = client.search("faith")
if results.has_search_metadata:
from lsbible.books import BIBLE_STRUCTURE
print("\n๐ Top 10 Books by Match Count:")
# Sort books by match count (descending)
sorted_books = sorted(
results.counts_by_book.items(),
key=lambda x: x[1],
reverse=True
)
for book_num, count in sorted_books[:10]:
book_name = BIBLE_STRUCTURE[book_num]["name"]
print(f" {book_name}: {count} matches")
Example: Complete Search Analysis
from lsbible import LSBibleClient
from lsbible.books import SECTION_NAMES, BIBLE_STRUCTURE
with LSBibleClient() as client:
results = client.search("salvation")
print(f"๐ Search Query: '{results.query}'")
print(f"โ
Total Matches: {results.match_count}")
print(f"๐ Passages Returned: {results.passage_count}")
if results.has_search_metadata:
print(f"\n๐ DISTRIBUTION ANALYTICS")
print(f" Total: {results.total_count}")
print(f" Filtered: {results.filtered_count}")
# Section distribution
print(f"\n๐ By Section:")
for section_id, count in sorted(results.counts_by_section.items()):
section_name = SECTION_NAMES[section_id]
percentage = (count / results.total_count) * 100
print(f" {section_name:25} {count:4} ({percentage:5.1f}%)")
# Top books
print(f"\n๐ Top 5 Books:")
sorted_books = sorted(
results.counts_by_book.items(),
key=lambda x: x[1],
reverse=True
)
for book_num, count in sorted_books[:5]:
book_name = BIBLE_STRUCTURE[book_num]["name"]
print(f" {book_name:25} {count:4} matches")
# Display sample verses
print(f"\n๐ Sample Verses:")
for passage in results.passages[:3]: # First 3 passages
for verse in passage.verses:
print(f" {verse.reference}: {verse.plain_text[:100]}...")
When Distribution Metadata is Available
- โ
Text searches:
client.search("love")โ Has metadata - โ Bible references:
client.search("John 3:16")โ No metadata (useget_verseinstead)
Error Handling
The SDK provides specific exceptions for different error cases:
from lsbible import (
LSBibleClient,
BookName,
InvalidReferenceError,
APIError,
BuildIDError
)
with LSBibleClient() as client:
try:
# Invalid chapter (John only has 21 chapters)
passage = client.get_verse(BookName.JOHN, 99, 1)
except InvalidReferenceError as e:
print(f"โ Invalid reference: {e}")
# Output: "John only has 21 chapters, but chapter 99 was requested"
try:
# Invalid verse (John 3 only has 36 verses)
passage = client.get_verse(BookName.JOHN, 3, 999)
except InvalidReferenceError as e:
print(f"โ Invalid reference: {e}")
# Output: "John 3 only has 36 verses, but verse 999 was requested"
try:
# Invalid book name
passage = client.get_verse("NotABook", 1, 1)
except InvalidReferenceError as e:
print(f"โ Invalid reference: {e}")
# Output: "Unknown book: NotABook"
try:
# API error (network issues, etc.)
passage = client.get_verse(BookName.JOHN, 3, 16)
except APIError as e:
print(f"โ API error: {e}")
try:
# Build ID detection failure
passage = client.get_verse(BookName.JOHN, 3, 16)
except BuildIDError as e:
print(f"โ Build ID error: {e}")
Exception Hierarchy:
LSBibleError(base exception)InvalidReferenceError- Invalid book/chapter/verseAPIError- API request failuresBuildIDError- Build ID detection failures
API Reference
LSBibleClient
Main async client class for interacting with the LSBible API.
__init__(cache=None, timeout=30, build_id=None, headers=None)
Initialize the client.
Parameters:
cache(CacheOptions | None): Cache configuration (optional)timeout(int): Request timeout in seconds (default: 30)build_id(str, optional): Next.js build ID (auto-detected if not provided)headers(dict, optional): Custom HTTP headers
Example:
from lsbible import LSBibleClient, MemoryCacheProvider, CacheTTL
async with LSBibleClient(
cache={
"provider": MemoryCacheProvider(),
"ttl": {
"verse": CacheTTL.BIBLE_CONTENT,
"search": CacheTTL.SEARCH_RESULTS,
}
},
timeout=60
) as client:
passage = await client.get_verse("John", 3, 16)
async search(query: str) -> SearchResponse
Search for passages containing text.
Parameters:
query(str): Search text or Bible reference
Returns: SearchResponse with structured passage data
Raises:
APIError: If API request fails
async get_verse(book, chapter, verse) -> Passage
Get a specific verse with validated parameters.
Parameters:
book(BookName | str): Book name (enum or string)chapter(int): Chapter numberverse(int): Verse number
Returns: Passage containing the verse
Raises:
InvalidReferenceError: If reference is invalidAPIError: If API request fails
async get_passage(from_book, from_chapter, from_verse, to_book, to_chapter, to_verse) -> Passage
Get a passage spanning multiple verses.
Parameters:
from_book(BookName | str): Starting bookfrom_chapter(int): Starting chapterfrom_verse(int): Starting verseto_book(BookName | str): Ending bookto_chapter(int): Ending chapterto_verse(int): Ending verse
Returns: Passage containing all verses in range
Raises:
InvalidReferenceError: If any reference is invalidAPIError: If API request fails
async get_chapter(book, chapter) -> Passage
Get an entire chapter.
Parameters:
book(BookName | str): Book namechapter(int): Chapter number
Returns: Passage containing all verses in the chapter
Raises:
InvalidReferenceError: If reference is invalidAPIError: If API request fails
clear_cache() -> None
Clear the response cache (only works with MemoryCacheProvider).
async close() -> None
Close the HTTP client.
Models
All models are immutable Pydantic models with full validation.
BookName (Enum)
Enumeration of all 66 Bible books:
from lsbible import BookName
# Old Testament
BookName.GENESIS
BookName.EXODUS
# ... through ...
BookName.MALACHI
# New Testament
BookName.MATTHEW
BookName.MARK
# ... through ...
BookName.REVELATION
# Numbered books
BookName.SAMUEL_1 # "1 Samuel"
BookName.KINGS_2 # "2 Kings"
BookName.CORINTHIANS_1 # "1 Corinthians"
VerseReference
Immutable reference to a specific verse:
Fields:
book_number(int): Book number (1-66)chapter(int): Chapter numberverse(int): Verse number
Properties:
book_name(BookName): The book name enum
Example:
ref = VerseReference(book_number=43, chapter=3, verse=16)
print(ref.book_name) # BookName.JOHN
print(str(ref)) # "John 3:16"
TextSegment
Text with formatting metadata:
Fields:
text(str): The text contentis_red_letter(bool): Jesus' wordsis_italic(bool): Clarificationsis_bold(bool): Emphasisis_small_caps(bool): LORD (YHWH)
VerseContent
Complete structured content of a verse:
Fields:
reference(VerseReference): Verse referenceverse_number(int): Verse numbersegments(list[TextSegment]): Text segments with formattinghas_subheading(bool): Whether verse has a subheadingsubheading_text(str | None): Subheading textis_poetry(bool): Poetic structureis_prose(bool): Prose structurechapter_start(bool): First verse of chapter
Properties:
plain_text(str): Text without formattingformatted_text(str): Text with simple formatting markers
Passage
A passage containing one or more verses:
Fields:
from_ref(VerseReference): Starting referenceto_ref(VerseReference): Ending referencetitle(str): Passage titleverses(list[VerseContent]): List of verses
Properties:
is_single_verse(bool): Whether passage is a single verseverse_count(int): Number of verses
SearchResponse
Response from a search or verse lookup:
Fields:
query(str): Original querymatch_count(int): Number of matchespassages(list[Passage]): List of passagesduration_ms(int): Query duration in millisecondstimestamp(int): Unix timestamp in millisecondstotal_count(int | None): Total matches (text search only)filtered_count(int | None): Filtered matches (text search only)counts_by_book(dict | None): Match distribution by bookcounts_by_section(dict | None): Match distribution by section
Properties:
passage_count(int): Number of passages returnedtotal_verses(int): Total verses across all passageshas_search_metadata(bool): Whether response includes distribution metadata
Caching
The SDK supports pluggable caching through the CacheProvider protocol, allowing you to choose the right caching strategy for your deployment environment.
Built-in Cache Providers
MemoryCacheProvider - In-memory caching with TTL support
- โ Local development, testing, single-process apps
- โ Multi-process apps, high-traffic production servers
NoopCacheProvider - Disables caching entirely
- Useful for debugging or when caching isn't beneficial
Cache Configuration
from lsbible import LSBibleClient, MemoryCacheProvider, CacheTTL
async with LSBibleClient(
cache={
"provider": MemoryCacheProvider(),
"ttl": {
"verse": CacheTTL.BIBLE_CONTENT, # 30 days
"passage": CacheTTL.BIBLE_CONTENT, # 30 days
"chapter": CacheTTL.BIBLE_CONTENT, # 30 days
"search": CacheTTL.SEARCH_RESULTS, # 7 days
}
}
) as client:
passage = await client.get_verse("John", 3, 16)
Recommended TTL constants:
CacheTTL.BIBLE_CONTENT(30 days) - Bible text is immutableCacheTTL.SEARCH_RESULTS(7 days) - May change with API updatesCacheTTL.STATIC(1 year) - Never changes
Custom Cache Providers
Implement the CacheProvider protocol for custom backends like Redis, Memcached, or DynamoDB:
from typing import Any
class CacheProvider:
async def get(self, key: str) -> Any | None:
"""Get value from cache, return None if not found"""
...
async def set(self, key: str, value: Any, ttl: int) -> None:
"""Set value in cache with TTL in seconds"""
...
Complete examples:
- cache_memory.py - Demonstrates cache hits/misses and manual clearing
- cache_redis.py - Full Redis implementation with redis-py
- cache_custom.py - Template for building your own cache provider
Cache Keys
The SDK uses these cache key patterns:
verse:{book} {chapter}:{verse}- Single versespassage:{query}- Passage rangeschapter:{book} {chapter}- Full chapterssearch:{query}- Text searches
Development
Setup
# Clone repository
git clone https://github.com/kdcokenny/lsbible.git
cd lsbible/packages/python-sdk
# Install dependencies
uv sync
Commands
# Run tests with coverage
uv run pytest
# Run specific test file
uv run pytest tests/test_client.py
# Run single test
uv run pytest tests/test_client.py::test_get_verse
# Type checking
uv run ty check lsbible
# Linting
uv run ruff check lsbible
uv run ruff check lsbible --fix # Auto-fix
# Formatting
uv run ruff format lsbible
Project Structure
packages/python-sdk/
โโโ lsbible/ # SDK source code
โ โโโ __init__.py # Public API exports
โ โโโ client.py # HTTP client
โ โโโ models.py # Pydantic models
โ โโโ parser.py # HTML parser
โ โโโ validators.py # Reference validation
โ โโโ books.py # Bible structure data
โ โโโ cache.py # Response caching
โ โโโ exceptions.py # Custom exceptions
โโโ tests/ # Test suite
โโโ examples/ # Usage examples
โโโ README.md # This file
โโโ pyproject.toml # Project configuration
Contributing
We welcome contributions! See the main Contributing Guidelines for details on:
- Reporting bugs
- Suggesting features
- Submitting pull requests
- Code style and standards
Quick Tips
- Add tests for new features
- Update docs for API changes
- Follow code style (run
ruff format) - Run tests before submitting (
pytest)
License
MIT License - See LICENSE file for details.
Made with โค๏ธ for Bible software developers
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lsbible-0.3.0.tar.gz.
File metadata
- Download URL: lsbible-0.3.0.tar.gz
- Upload date:
- Size: 39.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
483c0ec8e61ecffa8db62ff13d576ea73f6e2ea09007a50489d0538ad1fc239e
|
|
| MD5 |
e07c27be21e9ab01b6dd8af753b02453
|
|
| BLAKE2b-256 |
f092d0df2a56c871bf18c4016a6876501ba6fab8d080e37666cf4e3356dd4060
|
File details
Details for the file lsbible-0.3.0-py3-none-any.whl.
File metadata
- Download URL: lsbible-0.3.0-py3-none-any.whl
- Upload date:
- Size: 27.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
670af4058ab3143e56792d7a92f9334c71bc3393df9b5f06efe40591c2e94814
|
|
| MD5 |
057a11d08e3e0412128b7cb418c94f3d
|
|
| BLAKE2b-256 |
3e576c4cfb7d0f288af987421494c13dd2151616929d4160f6453c30b4365951
|