bibleql 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bibleql-0.1.0/.gitignore +22 -0
- bibleql-0.1.0/CHANGELOG.md +12 -0
- bibleql-0.1.0/LICENSE +21 -0
- bibleql-0.1.0/PKG-INFO +271 -0
- bibleql-0.1.0/README.md +236 -0
- bibleql-0.1.0/pyproject.toml +74 -0
- bibleql-0.1.0/src/bibleql/__init__.py +67 -0
- bibleql-0.1.0/src/bibleql/_version.py +1 -0
- bibleql-0.1.0/src/bibleql/client.py +357 -0
- bibleql-0.1.0/src/bibleql/errors.py +52 -0
- bibleql-0.1.0/src/bibleql/models.py +309 -0
- bibleql-0.1.0/src/bibleql/py.typed +0 -0
- bibleql-0.1.0/src/bibleql/queries.py +353 -0
- bibleql-0.1.0/tests/__init__.py +0 -0
- bibleql-0.1.0/tests/conftest.py +37 -0
- bibleql-0.1.0/tests/fixtures/schema.graphql +753 -0
- bibleql-0.1.0/tests/test_client.py +492 -0
- bibleql-0.1.0/tests/test_errors.py +76 -0
- bibleql-0.1.0/tests/test_models.py +175 -0
- bibleql-0.1.0/tests/test_queries.py +93 -0
- bibleql-0.1.0/tests/test_version.py +5 -0
bibleql-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.ruff_cache/
|
|
6
|
+
.mypy_cache/
|
|
7
|
+
|
|
8
|
+
# Build artifacts
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
|
|
16
|
+
# Editors / OS
|
|
17
|
+
.vscode/
|
|
18
|
+
.idea/
|
|
19
|
+
.DS_Store
|
|
20
|
+
|
|
21
|
+
# Secrets
|
|
22
|
+
.env
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.1.0] - 2026-10-18
|
|
6
|
+
|
|
7
|
+
- Initial release.
|
|
8
|
+
- `Client` with API key from argument or `BIBLEQL_API_KEY`, configurable default translation, API URL and timeout; usable as a context manager.
|
|
9
|
+
- Queries: `translations`, `translation`, `books`, `languages`, `passage`, `chapter`, `verse`, `random_verse`, `verse_of_the_day`, `bible_index`, `search`, `semantic_search`.
|
|
10
|
+
- Concordance: `concordance` (one page), `iter_concordance` (lazy auto-pagination), `concordance_index`, and the `Testament` enum.
|
|
11
|
+
- Immutable, typed dataclass models (`py.typed` included).
|
|
12
|
+
- Error hierarchy rooted at `BibleQLError`. HTTP errors include the server's message, and "Couldn't find ..." GraphQL errors raise `NotFoundError`.
|
bibleql-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Luis Porras
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
bibleql-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bibleql
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the BibleQL GraphQL API
|
|
5
|
+
Project-URL: Homepage, https://bibleql.org
|
|
6
|
+
Project-URL: Documentation, https://docs.bibleql.org
|
|
7
|
+
Project-URL: Source, https://github.com/lporras/bibleql-python
|
|
8
|
+
Project-URL: Changelog, https://github.com/lporras/bibleql-python/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Luis Porras <lporras16@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,bible,bibleql,client,graphql
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: httpx<2,>=0.27
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build; extra == 'dev'
|
|
28
|
+
Requires-Dist: graphql-core>=3.2; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
|
+
Requires-Dist: python-dotenv>=1.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: respx>=0.22; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
33
|
+
Requires-Dist: twine; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# BibleQL Python
|
|
37
|
+
|
|
38
|
+
[](https://github.com/lporras/bibleql-python/actions/workflows/ci.yml)
|
|
39
|
+
[](https://pypi.org/project/bibleql/)
|
|
40
|
+
[](https://pypi.org/project/bibleql/)
|
|
41
|
+
|
|
42
|
+
A typed Python client for the [BibleQL](https://bibleql.org) GraphQL API. Query Bible verses, passages, translations, search and concordance across 47 translations in 31 languages, without writing GraphQL.
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install bibleql
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Requires Python 3.10+.
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
import bibleql
|
|
56
|
+
|
|
57
|
+
with bibleql.Client(api_key="bql_live_...") as client:
|
|
58
|
+
passage = client.passage("John 3:16")
|
|
59
|
+
print(passage.reference) # John 3:16
|
|
60
|
+
print(passage.text) # For God so loved the world...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Get an API key at [bibleql.org](https://bibleql.org), and try queries in the [playground](https://bibleql.org/playground).
|
|
64
|
+
|
|
65
|
+
## Configuration
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
client = bibleql.Client(
|
|
69
|
+
api_key="bql_live_...", # or set the BIBLEQL_API_KEY environment variable
|
|
70
|
+
default_translation="spa-bes", # optional, defaults to "eng-web"
|
|
71
|
+
api_url="https://custom-host.com/graphql", # optional, defaults to https://bibleql.org/graphql
|
|
72
|
+
timeout=60, # optional, seconds, defaults to 30
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If `api_key` is omitted, the client reads `BIBLEQL_API_KEY`:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
export BIBLEQL_API_KEY=bql_live_...
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
client = bibleql.Client()
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use the client as a context manager (`with ... as client:`) or call `client.close()` when done, so its connection pool is released.
|
|
87
|
+
|
|
88
|
+
Every method that takes `translation=` falls back to `default_translation` when it's omitted. The exception is `semantic_search`, which lets the API pick a translation that has embeddings.
|
|
89
|
+
|
|
90
|
+
## Usage
|
|
91
|
+
|
|
92
|
+
### Passages
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
passage = client.passage("John 3:16")
|
|
96
|
+
passage.reference # "John 3:16"
|
|
97
|
+
passage.text # "For God so loved the world..."
|
|
98
|
+
passage.translation_id # "eng-web"
|
|
99
|
+
passage.verses # [Verse(book_id='JHN', book_name='John', chapter=3, verse=16, text='...')]
|
|
100
|
+
|
|
101
|
+
client.passage("John 3:16-18")
|
|
102
|
+
client.passage("Matthew 25:31-33,46")
|
|
103
|
+
client.passage("Genesis 1")
|
|
104
|
+
client.passage("Mateo 28:18-20", translation="spa-bes")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Verses and chapters
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
verse = client.verse("MAT", 5, 3)
|
|
111
|
+
verse.book_name # "Matthew"
|
|
112
|
+
verse.text # "Blessed are the poor in spirit..."
|
|
113
|
+
|
|
114
|
+
verses = client.chapter("GEN", 1) # list[Verse]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Random verse
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
client.random_verse()
|
|
121
|
+
client.random_verse(testament="NT") # "OT" or "NT"
|
|
122
|
+
client.random_verse(books=["PSA", "PRO"]) # or books="PSA,PRO"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Verse of the day
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import datetime
|
|
129
|
+
|
|
130
|
+
client.verse_of_the_day()
|
|
131
|
+
client.verse_of_the_day(date=datetime.date(2026, 1, 1)) # or date="2026-01-01"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Search
|
|
135
|
+
|
|
136
|
+
Case-insensitive substring search, with no stemming ("love" won't match "loved"):
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
for verse in client.search("love", limit=5):
|
|
140
|
+
print(verse.book_name, verse.chapter, verse.verse, verse.text)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Semantic search finds verses by meaning, using AI embeddings:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
for result in client.semantic_search("fe y esperanza", limit=5):
|
|
147
|
+
print(f"{result.similarity:.2f}", result.verse.text)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Concordance
|
|
151
|
+
|
|
152
|
+
Exhaustive, canonically ordered occurrences of a word, with per-book and per-testament totals:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
page = client.concordance("misericordia", translation="spa-rv1909", first=10)
|
|
156
|
+
page.total_count # total occurrences
|
|
157
|
+
page.entry.surface_forms # ["misericordia", "misericordias", ...]
|
|
158
|
+
page.entry.occurrences_by_testament.old # verses in the Old Testament
|
|
159
|
+
for occurrence in page.occurrences:
|
|
160
|
+
print(occurrence.verse.book_name, occurrence.context) # context has <mark> tags
|
|
161
|
+
|
|
162
|
+
# Next page
|
|
163
|
+
if page.has_next_page:
|
|
164
|
+
page = client.concordance("misericordia", translation="spa-rv1909", after=page.end_cursor)
|
|
165
|
+
|
|
166
|
+
# Or let the client paginate for you (pages are fetched lazily)
|
|
167
|
+
for occurrence in client.iter_concordance("misericordia", translation="spa-rv1909"):
|
|
168
|
+
...
|
|
169
|
+
|
|
170
|
+
# Filters
|
|
171
|
+
client.concordance("misericordia", translation="spa-rv1909", book="Salmos")
|
|
172
|
+
client.concordance("misericordia", translation="spa-rv1909", testament=bibleql.Testament.NEW)
|
|
173
|
+
|
|
174
|
+
# Alphabetical word-frequency index
|
|
175
|
+
client.concordance_index(translation="spa-rv1909", prefix="mis", min_occurrences=5)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The `context` field contains `<mark>` HTML tags around the match, so sanitize it before rendering it as HTML.
|
|
179
|
+
|
|
180
|
+
### Translations, languages, books
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
client.translations() # list[Translation]
|
|
184
|
+
t = client.translation("eng-web")
|
|
185
|
+
t.name, t.abbrev # "World English Bible", "WEB"
|
|
186
|
+
t.books # list[LocalizedBook]
|
|
187
|
+
|
|
188
|
+
client.languages() # list[Language], each with .translations
|
|
189
|
+
client.books() # list[Book], the 66 canonical books
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Bible index
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
index = client.bible_index()
|
|
196
|
+
index[0].name # "Genesis"
|
|
197
|
+
index[0].chapters[0].verse_count # 31
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Models
|
|
201
|
+
|
|
202
|
+
All results are immutable dataclasses with snake_case attributes. To convert one to a dict:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
import dataclasses
|
|
206
|
+
|
|
207
|
+
dataclasses.asdict(client.verse("JHN", 3, 16))
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Error handling
|
|
211
|
+
|
|
212
|
+
Every error inherits from `bibleql.BibleQLError`:
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
BibleQLError
|
|
216
|
+
├── ConfigurationError no API key
|
|
217
|
+
├── NetworkError no response (DNS, connection refused, ...)
|
|
218
|
+
│ └── RequestTimeoutError
|
|
219
|
+
├── APIError (.status, .body) non-2xx HTTP response
|
|
220
|
+
│ ├── AuthenticationError 401
|
|
221
|
+
│ ├── RateLimitError 429
|
|
222
|
+
│ └── ServerError 5xx
|
|
223
|
+
└── QueryError (.errors) GraphQL errors in the response
|
|
224
|
+
└── NotFoundError
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
try:
|
|
229
|
+
client.passage("Hezekiah 1:1")
|
|
230
|
+
except bibleql.NotFoundError as e:
|
|
231
|
+
print(f"Not found: {e}")
|
|
232
|
+
except bibleql.AuthenticationError:
|
|
233
|
+
print("Invalid API key")
|
|
234
|
+
except bibleql.RateLimitError:
|
|
235
|
+
print("Too many requests")
|
|
236
|
+
except bibleql.RequestTimeoutError:
|
|
237
|
+
print("Request timed out")
|
|
238
|
+
except bibleql.NetworkError:
|
|
239
|
+
print("Connection failed")
|
|
240
|
+
except bibleql.QueryError as e:
|
|
241
|
+
print(f"GraphQL error: {e}")
|
|
242
|
+
print(e.errors) # raw error list from the API
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Development
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
asdf install # Python version from .tool-versions
|
|
249
|
+
python -m venv .venv && source .venv/bin/activate
|
|
250
|
+
python -m pip install -e ".[dev]"
|
|
251
|
+
|
|
252
|
+
pytest # run tests (HTTP is mocked with respx, no network needed)
|
|
253
|
+
ruff check . && ruff format . # lint and format
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`tests/fixtures/schema.graphql` is a snapshot of the API schema, and every query is validated against it. Refresh it from the API repo (`docs/generated/schema.graphql`) when the schema changes.
|
|
257
|
+
|
|
258
|
+
### Testing against the live API
|
|
259
|
+
|
|
260
|
+
The test suite never touches the network. To check the client against the real API, put your key in a `.env` file (gitignored) and run the smoke test, which loads it with [python-dotenv](https://pypi.org/project/python-dotenv/):
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
cp .env.example .env # then set BIBLEQL_API_KEY in .env
|
|
264
|
+
python scripts/smoke_test.py # calls every method and prints the results
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The library itself never reads `.env` files. In your own app, load them yourself (for example with `dotenv.load_dotenv()`) before creating a `Client()`.
|
|
268
|
+
|
|
269
|
+
## License
|
|
270
|
+
|
|
271
|
+
Released under the [MIT License](LICENSE).
|
bibleql-0.1.0/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# BibleQL Python
|
|
2
|
+
|
|
3
|
+
[](https://github.com/lporras/bibleql-python/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/bibleql/)
|
|
5
|
+
[](https://pypi.org/project/bibleql/)
|
|
6
|
+
|
|
7
|
+
A typed Python client for the [BibleQL](https://bibleql.org) GraphQL API. Query Bible verses, passages, translations, search and concordance across 47 translations in 31 languages, without writing GraphQL.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install bibleql
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.10+.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import bibleql
|
|
21
|
+
|
|
22
|
+
with bibleql.Client(api_key="bql_live_...") as client:
|
|
23
|
+
passage = client.passage("John 3:16")
|
|
24
|
+
print(passage.reference) # John 3:16
|
|
25
|
+
print(passage.text) # For God so loved the world...
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Get an API key at [bibleql.org](https://bibleql.org), and try queries in the [playground](https://bibleql.org/playground).
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
client = bibleql.Client(
|
|
34
|
+
api_key="bql_live_...", # or set the BIBLEQL_API_KEY environment variable
|
|
35
|
+
default_translation="spa-bes", # optional, defaults to "eng-web"
|
|
36
|
+
api_url="https://custom-host.com/graphql", # optional, defaults to https://bibleql.org/graphql
|
|
37
|
+
timeout=60, # optional, seconds, defaults to 30
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If `api_key` is omitted, the client reads `BIBLEQL_API_KEY`:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
export BIBLEQL_API_KEY=bql_live_...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
client = bibleql.Client()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Use the client as a context manager (`with ... as client:`) or call `client.close()` when done, so its connection pool is released.
|
|
52
|
+
|
|
53
|
+
Every method that takes `translation=` falls back to `default_translation` when it's omitted. The exception is `semantic_search`, which lets the API pick a translation that has embeddings.
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
### Passages
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
passage = client.passage("John 3:16")
|
|
61
|
+
passage.reference # "John 3:16"
|
|
62
|
+
passage.text # "For God so loved the world..."
|
|
63
|
+
passage.translation_id # "eng-web"
|
|
64
|
+
passage.verses # [Verse(book_id='JHN', book_name='John', chapter=3, verse=16, text='...')]
|
|
65
|
+
|
|
66
|
+
client.passage("John 3:16-18")
|
|
67
|
+
client.passage("Matthew 25:31-33,46")
|
|
68
|
+
client.passage("Genesis 1")
|
|
69
|
+
client.passage("Mateo 28:18-20", translation="spa-bes")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Verses and chapters
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
verse = client.verse("MAT", 5, 3)
|
|
76
|
+
verse.book_name # "Matthew"
|
|
77
|
+
verse.text # "Blessed are the poor in spirit..."
|
|
78
|
+
|
|
79
|
+
verses = client.chapter("GEN", 1) # list[Verse]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Random verse
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
client.random_verse()
|
|
86
|
+
client.random_verse(testament="NT") # "OT" or "NT"
|
|
87
|
+
client.random_verse(books=["PSA", "PRO"]) # or books="PSA,PRO"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Verse of the day
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import datetime
|
|
94
|
+
|
|
95
|
+
client.verse_of_the_day()
|
|
96
|
+
client.verse_of_the_day(date=datetime.date(2026, 1, 1)) # or date="2026-01-01"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Search
|
|
100
|
+
|
|
101
|
+
Case-insensitive substring search, with no stemming ("love" won't match "loved"):
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
for verse in client.search("love", limit=5):
|
|
105
|
+
print(verse.book_name, verse.chapter, verse.verse, verse.text)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Semantic search finds verses by meaning, using AI embeddings:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
for result in client.semantic_search("fe y esperanza", limit=5):
|
|
112
|
+
print(f"{result.similarity:.2f}", result.verse.text)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Concordance
|
|
116
|
+
|
|
117
|
+
Exhaustive, canonically ordered occurrences of a word, with per-book and per-testament totals:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
page = client.concordance("misericordia", translation="spa-rv1909", first=10)
|
|
121
|
+
page.total_count # total occurrences
|
|
122
|
+
page.entry.surface_forms # ["misericordia", "misericordias", ...]
|
|
123
|
+
page.entry.occurrences_by_testament.old # verses in the Old Testament
|
|
124
|
+
for occurrence in page.occurrences:
|
|
125
|
+
print(occurrence.verse.book_name, occurrence.context) # context has <mark> tags
|
|
126
|
+
|
|
127
|
+
# Next page
|
|
128
|
+
if page.has_next_page:
|
|
129
|
+
page = client.concordance("misericordia", translation="spa-rv1909", after=page.end_cursor)
|
|
130
|
+
|
|
131
|
+
# Or let the client paginate for you (pages are fetched lazily)
|
|
132
|
+
for occurrence in client.iter_concordance("misericordia", translation="spa-rv1909"):
|
|
133
|
+
...
|
|
134
|
+
|
|
135
|
+
# Filters
|
|
136
|
+
client.concordance("misericordia", translation="spa-rv1909", book="Salmos")
|
|
137
|
+
client.concordance("misericordia", translation="spa-rv1909", testament=bibleql.Testament.NEW)
|
|
138
|
+
|
|
139
|
+
# Alphabetical word-frequency index
|
|
140
|
+
client.concordance_index(translation="spa-rv1909", prefix="mis", min_occurrences=5)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The `context` field contains `<mark>` HTML tags around the match, so sanitize it before rendering it as HTML.
|
|
144
|
+
|
|
145
|
+
### Translations, languages, books
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
client.translations() # list[Translation]
|
|
149
|
+
t = client.translation("eng-web")
|
|
150
|
+
t.name, t.abbrev # "World English Bible", "WEB"
|
|
151
|
+
t.books # list[LocalizedBook]
|
|
152
|
+
|
|
153
|
+
client.languages() # list[Language], each with .translations
|
|
154
|
+
client.books() # list[Book], the 66 canonical books
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Bible index
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
index = client.bible_index()
|
|
161
|
+
index[0].name # "Genesis"
|
|
162
|
+
index[0].chapters[0].verse_count # 31
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Models
|
|
166
|
+
|
|
167
|
+
All results are immutable dataclasses with snake_case attributes. To convert one to a dict:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
import dataclasses
|
|
171
|
+
|
|
172
|
+
dataclasses.asdict(client.verse("JHN", 3, 16))
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Error handling
|
|
176
|
+
|
|
177
|
+
Every error inherits from `bibleql.BibleQLError`:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
BibleQLError
|
|
181
|
+
├── ConfigurationError no API key
|
|
182
|
+
├── NetworkError no response (DNS, connection refused, ...)
|
|
183
|
+
│ └── RequestTimeoutError
|
|
184
|
+
├── APIError (.status, .body) non-2xx HTTP response
|
|
185
|
+
│ ├── AuthenticationError 401
|
|
186
|
+
│ ├── RateLimitError 429
|
|
187
|
+
│ └── ServerError 5xx
|
|
188
|
+
└── QueryError (.errors) GraphQL errors in the response
|
|
189
|
+
└── NotFoundError
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
try:
|
|
194
|
+
client.passage("Hezekiah 1:1")
|
|
195
|
+
except bibleql.NotFoundError as e:
|
|
196
|
+
print(f"Not found: {e}")
|
|
197
|
+
except bibleql.AuthenticationError:
|
|
198
|
+
print("Invalid API key")
|
|
199
|
+
except bibleql.RateLimitError:
|
|
200
|
+
print("Too many requests")
|
|
201
|
+
except bibleql.RequestTimeoutError:
|
|
202
|
+
print("Request timed out")
|
|
203
|
+
except bibleql.NetworkError:
|
|
204
|
+
print("Connection failed")
|
|
205
|
+
except bibleql.QueryError as e:
|
|
206
|
+
print(f"GraphQL error: {e}")
|
|
207
|
+
print(e.errors) # raw error list from the API
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Development
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
asdf install # Python version from .tool-versions
|
|
214
|
+
python -m venv .venv && source .venv/bin/activate
|
|
215
|
+
python -m pip install -e ".[dev]"
|
|
216
|
+
|
|
217
|
+
pytest # run tests (HTTP is mocked with respx, no network needed)
|
|
218
|
+
ruff check . && ruff format . # lint and format
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`tests/fixtures/schema.graphql` is a snapshot of the API schema, and every query is validated against it. Refresh it from the API repo (`docs/generated/schema.graphql`) when the schema changes.
|
|
222
|
+
|
|
223
|
+
### Testing against the live API
|
|
224
|
+
|
|
225
|
+
The test suite never touches the network. To check the client against the real API, put your key in a `.env` file (gitignored) and run the smoke test, which loads it with [python-dotenv](https://pypi.org/project/python-dotenv/):
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
cp .env.example .env # then set BIBLEQL_API_KEY in .env
|
|
229
|
+
python scripts/smoke_test.py # calls every method and prints the results
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The library itself never reads `.env` files. In your own app, load them yourself (for example with `dotenv.load_dotenv()`) before creating a `Client()`.
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bibleql"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Python client for the BibleQL GraphQL API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Luis Porras", email = "lporras16@gmail.com" }]
|
|
14
|
+
keywords = ["bible", "graphql", "api", "client", "bibleql"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Programming Language :: Python :: 3.15",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"httpx>=0.27,<2",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8",
|
|
35
|
+
"respx>=0.22",
|
|
36
|
+
"ruff>=0.6",
|
|
37
|
+
"graphql-core>=3.2",
|
|
38
|
+
"python-dotenv>=1.0",
|
|
39
|
+
"build",
|
|
40
|
+
"twine",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://bibleql.org"
|
|
45
|
+
Documentation = "https://docs.bibleql.org"
|
|
46
|
+
Source = "https://github.com/lporras/bibleql-python"
|
|
47
|
+
Changelog = "https://github.com/lporras/bibleql-python/blob/main/CHANGELOG.md"
|
|
48
|
+
|
|
49
|
+
# Read the version from __version__ in _version.py (like version.rb)
|
|
50
|
+
[tool.hatch.version]
|
|
51
|
+
path = "src/bibleql/_version.py"
|
|
52
|
+
|
|
53
|
+
# Allowlist for the source archive (.tar.gz), so local files such as .env or
|
|
54
|
+
# editor/agent settings can never be published, whatever is in .gitignore.
|
|
55
|
+
[tool.hatch.build.targets.sdist]
|
|
56
|
+
include = [
|
|
57
|
+
"src/bibleql",
|
|
58
|
+
"tests",
|
|
59
|
+
"CHANGELOG.md",
|
|
60
|
+
"LICENSE",
|
|
61
|
+
"README.md",
|
|
62
|
+
"pyproject.toml",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
testpaths = ["tests"]
|
|
67
|
+
|
|
68
|
+
[tool.ruff]
|
|
69
|
+
line-length = 100
|
|
70
|
+
target-version = "py310"
|
|
71
|
+
|
|
72
|
+
[tool.ruff.lint]
|
|
73
|
+
# E/F = pycodestyle/pyflakes, I = import sorting, UP = modern syntax, B = common bugs
|
|
74
|
+
select = ["E", "F", "I", "UP", "B"]
|