Skip to content

adamcunnington/Bible

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bible

Interact with the Bible through an intuitive and extensible API with unprecedented ease. Traverse scripture at speed using a simple object model and run analytical queries across Character metadata through familiar python syntax. The application is designed primarily to be used via a notebook interface but can also be used to power applications.

Quick Start (Ubuntu)

Install system dependencies, create and activate a virtual environment, install the application from github and launch a python interpreter.

sudo apt update && sudo apt install -y build-essential graphviz python3.9 python3.9-dev vlc
python3.9 -m venv .venv
source .venv/bin/activate
pip install git+https://github.com/adamcunnington/Bible#egg=Bible
python

Load the ESV translation, fetch text for Genesis 1:1, fetch audio for Genesis 1:2-2 and fetch a list of mentioned character names.

import bible
esv = bible.esv()
genesis = esv[1]
genesis[1][1].text()
genesis.passage("1:2-2:").audio()
genesis.passage("1-2").characters().values("name")

Full Setup

The application can be executed in two ways:

  1. Locally
  2. Via Docker

Note: If you are running WSL or WSL2, you may need to install and configure additional dependencies to get audio working. See here for more details.

1. Local Instructions

The application can be ran locally (in editable mode) which is especially useful if changes are being made to the code. The make commands in this section assume an executable called python3.9. Alternatively, PYTHON3=x can be passed with the make target where x is the name of the python3 executable to use, e.g. PYTHON3=python3.7. Run make to see full details.

Pre Requisites

  1. Clone the repo.
  2. Install makefile dependencies, graphviz, vlc, python3.x and python3.x-dev and build-essential packages (required by python-levenshtein).
  3. Set the ESV_API_TOKEN environment variable (either explicitly or implicitly via a ./.env file). To obtain an API token, visit ESV API documentation.

Installation

  1. Install the application locally (a virtual environment will be created) with make install.

Execution

  1. Run the application locally with make run-local.

2. Docker Instructions

The application can also be ran inside of a docker container. No dependencies are required other than docker.

Pre Requisites

  1. Clone the repo.
  2. Install docker.
  3. Set the ESV_API_TOKEN environment variable, locally (either explicitly or implicitly via a ./.env file). To obtain an API token, visit ESV API documentation.

Installation

  1. Build the docker image with make build.

Execution

  1. Run an ephemeral container using the docker image with make run.

Usage

The execution of the application, whether locally or via Docker, starts a python interpreter with the bible package already imported. Translations should be accessed directly through the bible namespace, e.g. bible.esv(). All attributes are accessed through the Translation object directly, or indirectly via descendent objects.

Core API

There are 6 main objects in the core API.

  • Translation (e.g. ESV)
  • Book (e.g. Genesis)
  • Chapter (e.g. Chapter 1 of Genesis)
  • Verse (e.g. Verse 1 of Genesis 1)
  • Passage (e.g. range of verses from 1 or more chapters/books)
  • Character (e.g. Jesus)

The first 5 objects relate to the structure and content of the bible whilst the 6th relates to character metadata. The two categories will be discussed separately.

Structure

Two tables at the end of this section provide an overview of what is available through the core API. The first lists all attributes and which objects they are supported by whilst the second provides information for each attribute as well as details of any object-specific behaviour.

Each translation is responsible for providing both the metadata and content for the translation. Additionally, each translation may extend the core API (or even override, sparingly) to surface extra content or functionality (such as using an online concordance service).

For now, it suffices to say that the first four objects should be seen as a hierarcy, e.g. start with a Translation and dive into a Book, then Chapter, then Verse - much like a physical Bible. The fifth, Passage object, can be generated by using the passage() method on any object that has children (e.g. all but Verse) and passing a reference which identifies the range to generate.

Passage References

The convention that passage references must follow is consistent across Translations, Books and Chapters but the form minimises as the parent object is scoped down. It is easier to describe the form per parent:

Translation.passage(reference=None, int_reference=None)
  • reference - takes the form, <book> <chapter>:<verse> - <book> <chapter>:<verse> where spaces are optional, each component is optional, book can be a number, fuzzy matched sluggified name or even fuzzy matched alternative name, and chapter/verse should be numbers. If a component is omitted from the left hand side, it will be assumed to be 1 whereas if a component is omitted from the right hand side, it will either be: i) assumed to be the final entity if there were no components provided afterwards or ii) the same value as the left hand side if there were components provided afterwards. In the case of i), note that this assumption cascades such that the extreme case of reference=x- will actually return a Passage object that spans the rest of the Bible (to the final verse of final chapter of final book) from x onwards. In the case of ii) a more intuitive short hand experience is realised, i.e. the desired behaviour of Genesis 3-16 is Genesis 3 - Genesis 16 rather than Genesis 3 - Revelation 16. It is also possible to return a single book/chapter/verse by omitting the right hand side entirely as well as the - character. If provided, the right hand side must be greater than the left.
  • int_reference - takes a simplified form, XXYYYZZZ - XXYYYZZZ where spaces are optional, XX is an optionally 0-padded book number (i.e. both 6 and 06 are acceptable), YYY is a 00-padded chapter number and ZZZ is a 00-padded verse number. For example, Genesis 1:1 - Exodus 3:6 would be represented as 01001001 - 02003006. Each side is optional but the component parts that make up the side are not. If provided, the right hand side must be canonically after the left hand side.
Book.passage(reference="-")
  • reference - behaves exactly as reference above except it takes the simplified form, <chapter>:<verse> - <chapter>:<verse> as the book comes implicitly from the parent object.
Chapter.passage(reference="-")
  • reference - behaves exactly as reference above except it takes the simplified form, <verse> - <verse> as the chapter and book come implicitly from the parent object.

Examples:

PASSAGE REFERENCE BOOK START CHAPTER START VERSE START BOOK END CHAPTER END VERSE END
Translation.passage("-") 1 (Genesis) 1 1 66 (Revelation) 22 21
Translation.passage("Matth-") 40 (Matthew) 1 1 66 (Relevation) 22 21
Translation.passage("John 2:3-John") 43 (John) 2 3 43 (John) 21 25
Translation.passage("John 2:3 - John 2") 43 (John) 2 3 43 (John) 2 25
Translation.passage("John 2-6") 43 (John) 2 1 43 (John) 6 71
Translation.passage("John 2:3-6") 43 (John) 2 3 43 (John) 2 6
Translation.passage("- Exo") 1 (Genesis) 1 1 2 (Exodus) 40 38
Translation.passage(None, "01001001-02003006") 1 (Genesis) 1 1 2 (Exodus) 3 6
Translation.passage(None, "37002003-") 37 (Haggai) 2 3 66 (Revelation) 22 21
Translation.passage(None, "4002009") 4 (Numbers) 2 9 4 (Numbers) 2 9
Translation.passage(None, " -2003019") 1 (Genesis) 1 1 2 (Exodus) 3 19
<Genesis>.passage("7:13-9:21") 1 (Genesis) 7 13 1 (Genesis) 9 21
<Genesis>.passage("7:13-21") 1 (Genesis) 7 13 1 (Genesis) 7 21
<Genesis>.passage("7-21") 1 (Genesis) 7 1 1 (Genesis) 21 34
<Genesis>.passage("-3:") 1 (Genesis) 1 1 1 (Genesis) 3 24
<Genesis>.passage() 1 (Genesis) 1 1 1 (Genesis) 50 26
<John 3>.passage("9-16") 43 (John) 3 9 43 (John) 3 16
<John 3>.passage("13") 43 (John) 3 13 43 (John) 3 13
<John 3>.passage() 43 (John) 3 1 43 (John) 3 36

Attribute Map
ATTRIBUTE TRANSLATION BOOK CHAPTER VERSE PASSAGE
d[k] ✔️ ✔️ ✔️
k in d ✔️ ✔️ ✔️
iter() ✔️ ✔️ ✔️
len() ✔️ ✔️ ✔️ ✔️
repr() ✔️ ✔️ ✔️ ✔️ ✔️
str() ✔️ ✔️ ✔️ ✔️ ✔️
.alt_ids ✔️
.alt_names ✔️
.author ✔️
.book ✔️ ✔️
.book_end ✔️
.book_start ✔️
.categories ✔️ ✔️
.chapter ✔️
.chapter_end ✔️
.chapter_start ✔️
.id ✔️
.int_reference ✔️ ✔️ ✔️ ✔️
.is_first ✔️ ✔️ ✔️
.is_last ✔️ ✔️ ✔️
.language ✔️
.name ✔️ ✔️
.number ✔️ ✔️ ✔️
.translation ✔️ ✔️ ✔️
.verse_end ✔️
.verse_start ✔️
audio() ✔️ ✔️ ✔️ ✔️
books() ✔️ ✔️
chapters() ✔️ ✔️
characters(field=None) ✔️ ✔️ ✔️ ✔️ ✔️
first() ✔️ ✔️ ✔️
last() ✔️ ✔️ ✔️
next(overspill=True) ✔️ ✔️ ✔️
passage(...) ✔️ ✔️ ✔️
previous(overspill=True) ✔️ ✔️ ✔️
text() ✔️ ✔️ ✔️ ✔️
verses() ✔️
Attribute Details
ATTRIBUTE CATEGORY DESCRIPTION SPECIAL NOTES
d[k] Magic Method Fetches a child object of the parent (e.g. verse number of chapter). Translation supports fuzzy lookup using number, id, alt_ids.
k in d Magic Method Checks whether an object belongs to a parent (e.g. verse in chapter). Translation supports fuzzy lookup using number, id, alt_ids.
iter() Magic Method Iterates over parent to yield child objects (e.g. verses of chapter).
len() Magic Method Finds out how many children the parent has (e.g. verses in a chapter). Passage object length is the number of verses in the range.
repr() Magic Method Prints a scripture-oriented representation of the object.
str() Magic Method Prints a human-readable scripture reference for the object.
.alt_ids Property The alternative ids (sluggified names) that the object is known by.
.alt_names Property The alternative names that the object is known by.
.author Property The author/writer of the text.
.book Property The Book object that the object belongs to.
.book_end Property The Book object where the ranged object finishes (e.g. -Exo).
.book_start Property The Book object where the ranged object starts. (e.g. Gen-).
.categories Property The categories that the object belongs to (e.g. Old Testament).
.chapter Property The Chapter object that the object belongs to.
.chapter_end Property The Chapter object where the ranged object finishes (e.g. -Exo 4).
.chapter_start Property The Chapter object where the ranged object starts (e.g. Gen 4-).
.id Property The id (sluggified name) that the object is primarily known by.
.int_reference Property The object's numeric reference form, XXYYYZZZ (book, chapter, verse).
.is_first Property Whether the object is the first in parent (e.g. chapter 1).
.is_last Property Whether the object is the last in parent (e.g. last chapter of book).
.language Property The language the text was written in.
.name Property The name that the object is primarily known by.
.number Property The number that the object is identified by (based on order).
.translation Property The Translation object that the object belongs to.
.verse_end Property The Verse object where the ranged object finishes (e.g. -Exo :10).
.verse_start Property The Verse object where the ranged object starts (e.g. Gen :9-).
audio() Method Fetches and plays the audio that relates to the object's text.
books() Method Returns a generator of Book objects that relate to the object.
chapters() Method Returns a generator of Chapter objects that relate to the object.
characters(field=None) Method Returns a Characters object containing Character objects for querying.
first() Method Returns the first child object of parent (e.g. first chapter).
last() Method Returns the last child object of parent (e.g. last chapter of book).
next(overspill=True) Method Returns the next object. Spill into next parent object/None.
passage(...) Method Returns a Passage object ranging across many children (e.g. many verses). Translation supports a second parameter, int_reference.
previous(overspill=True) Method Returns the previous object. Spill into previous parent object/None.
text() Method Fetches and prints the text that relates to the object.
verses() Method Returns a generator of verse objects that relate to the object.

Character

The Character objects expose all of the attributes described in Character attributes plus some additional derived attributes for convenience such as brothers, sisters, husbands, wives etc. (access .fields for a full list) but as indicated in the above table, when the .characters(field=None) method is called on any other core API object, a Characters object is returned which represents a collection of characters. Any supported logical operation (like SQL predicates) or attempt to access a character attribute will return a new, filtered-down Characters object. Additional reduction methods allow the selection of values (like SQL selects) as well as some special methods that provide geanealogy-specific functionality. The following table summarises what is possible:

ATTRIBUTE CATEGORY DESCRIPTION EXAMPLE
dataclass Property The dataclass that the object's collection are instances of. (read only). c.dataclass
field Property The default attribute that will be used for logical and reduction methods. c.field = "name"
fields Property The tuple of attributes that the collection of objects support. c.fields
__eq__ Magic Method Return a new Characters object filtering to characters whose attribute == the value. c.name == "Adam"
__ge__ Magic Method Return a new Characters object filtering to characters whose attribute was >= value. c.age >= 35
__getattr__ Magic Method Return a new Characters object with the field attribute set to the name. c.name
__getitem__ Magic Method Return the Character object based on number exact match or name fuzzy match. c["Ada"]
__gt__ Magic Method Return a new Characters object filtering to characters whose attribute was > value. c.age > 35
__iter__ Magic Method Return an iterable of Character objects currently contained. for character in c: ...
__le__ Magic Method Return a new Characters object filtering to characters whose attribute <= the value. c.age <= "Adam"
__len__ Magic Method Return the number of Character objects currently contained. len(c)
__lt__ Magic Method Return a new Characters object filtering to characters whose attribute < the value. c.age < 35
__ne__ Magic Method Return a new Characters object filtering to characters whose attribute != the value. c.name != "Adam"
any(*values, not_=False) Logical Method Return a new Characters object like eq (ne if not_=True) but for > 1 value. c.name.any("Adam", "Eve")
combine(*filterables) Logical Method Return a new Characters object filtering to characters described by any filterables. c.combine(c.born > 200, c.age > 30)
contains(value, not_=False) Logical Method Return a new Characters object behaves like in (or not in if not_=True). c.spouses.contains(c[4], c[5])
false() Logical Method Return a new Characters object filtering to characters whose attribute is false. c.male.false()
like(*values, not_=False, threshold=0.6) Logical Method Return a new Characters object like like() but for > 1 value. c.name.like("ada", "Ev")
true() Logical Method Return a new Characters object filtering to characters whose attribute is true. c.male.true()
all(limit=None) Reduction Method Return a generator of limit/all Character objects currently contained. c.all()
one(error=True) Reduction Method Return the one matched Character, errors if > 1 unless error=False. jesus = c.one()
select(*fields, limit=None) Reduction Method Return a generator of limit/all dicts mapping fields (self.field if None) to values. characters = c.select("name", "age")
values(field=None, limit=None) Reduction Method Return a tuple of limit/all field (self.field if None) values. names = c.values("name")
lineage(ancestor, descendant) Geanealogy Method Return a new Characters object filtering direct lineage between ancestor - descendant. c.lineage(c[1], c[4])
tree(view=True) Genealogy Method Render a tree of characters currently contained (open in default photo app if view=True). c.tree()

ESV API Specifics

For the most part, the ESV translation sticks to the core API. The following additions apply.

Translation Object Extensions

Translation.search(query, page_size=100)

Search the bible for verses that are related to the query and return a generator.

  • query - a word or phrase to search for.

ESVText Object Addition

Calling the text() method on any object that supports it will return a ESVText object with the following attributes:

len(ESVText) -> len(ESVText.body.split())
repr(ESVText) -> ESVText.body
ESVText.body -> String text body
ESVText.footnotes -> String footnotes
ESVText.title -> String title (where relevant, and typically only first verses)

Developing Translations

Adding a translation to the codebase entails 3 tasks:

  1. Create a python package under bible/translations/
  2. Create the translation-specific metadata - typically bible/translations/<translation>/data.json
  3. Add a function that will load the translation to bible/__init__.py

Each of these tasks will be explored in greater detail. It is useful to refer to bible/translations/esv/ as an existing example.

1. New Python Package

A typical translation should consist of:

bible/translations/<translation>/__init__.py - to organise the translation as a python package; can be empty
bible/translations/<translation>/api.py - to hold the logic for the translation; the file name is irrelevant but api.py is suggested for consistency

In api.py, the Translation, Book, Chapter, Verse, Passage and Character classes from bible.api should be inherited and implementations should be provided for the text() and audio() methods. Typically, content for these will come from 3rd party API services. The MixIn class pattern is well suited. Optionally, extensions to the API can also be made.

It is likely that additional environment variables will be required to accommodate API secrets and possibly additional python dependencies too. Therefore, it is expected that the following files in the root of the project may also need changing accordingly:

  • Dockerfile
  • README.md
  • requirements.txt
  • Makefile

2. Translation-Specific Metadata

The python package alone is not enough. Each translation must provide metadata for the bible structure (as there are subtle variations between translations) and characters.

The base metadata is defined in bible/data.json. Translation-specific should be provided (e.g. bible/translations/<translation>/data.json) and this data will take precedence when merged into the base metadata. The properties that relate to the bible book structures are self explanatory - refer to bible/translations/esv/data.json for a more concrete example. The only detail to call out is the special syntax for expressing enum values. Any string values inside the JSON file can take the form of "X.Y" where X is the name of the enum class and Y is the name of a valid enum within the class. When deserialised, the enum value will be imported as a regular string but this serves to validate the provided values in the JSON.

Regarding character metadata, the below table details the properties available - all of which are optional except for id and passages.

Field Name Type Description Example
number string The identifier of the character. "1"
passages array of strings Each item should be a valid Translation.passage reference. ["Matthew"]
age integer The age the character died/left earth at. 35
aliases array of strings Alternative names the character is known by. ["Son of Man", "Cornerstone"]
born integer The year the character was born. Negative number for BC, positive for AD. 0
cause_of_death enum A string (from a consistent list) that describes how the character died. "Crucified"
died integer The year the character died. Negative number for BC, positive for AD. 35
father string The identifier of the mother character. "4"
mother string The identifier of the mother character. "5"
name string The primary name the character is known by. "Jesus"
nationality string The place/nation where the character is considerd to be from. Often not birthplace. "Nazareth"
place_of_death enum A string (from a consistent list) that describes where the character died. "Golgotha"
primary_occupation enum A string (from a consistent list) that describes the character's main job / passtime. "Carpenter/Savior!"
spouses array of strings The identifierss of the character's husbands/wives. ["1"]

For passages, it can be difficult to know how to accurately represent the range of passages that refer to a particular character. The following rule serves as useful guidance:

  • If the character is seldom mentioned (e.g. Melchizedek), then a list of very specific verses is most appropriate.
  • If the character is described in the context of a story, limit the specifity to entire chapters or even entire books if appropriate (e.g. Jesus).

3. Loading the Translation

This is the simplest step. bible/__init__.py should be altered in two ways:

  • An additional import will be needed; from bible.translations.<translation> import api as <translation>_api
  • An additional function will be needed; def <translation>: return utils.load_translation(...)

The function, utils.load_translation takes the following parameters:

  • data_file_path=None - an absolute file path to the translation-specific data. If omitted, a JSON (data.json if available) will be found alongside the module of any of the below provided classes. If none are provided, no translation-specific data will be loaded; only the base data.
  • translation_cls=None - the Translation class to use; if omitted, falls back to bible.api.Translation.
  • book_cls=None - the Book class to use; if omitted, falls back to bible.api.Book.
  • chapter_cls=None - the Chapter class to use; if omitted, falls back to bible.api.Chapter.
  • verse_cls=None - the Verse class to use; if omitted, falls back to bible.api.Verse.
  • passage_cls=None - the Passage class to use; if omitted, falls back to bible.api.Passage.
  • character_cls=None - the Character class to use; if omitted, falls back to bible.api.Character.
  • enum_classes=() - an iterable of enum classes to use to validate the loaded JSON; if omitted, falls back to all enum classes defined in bible.enums.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published