libex-core 0.20.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.
- libex_core-0.20.0/LICENSE +21 -0
- libex_core-0.20.0/PKG-INFO +215 -0
- libex_core-0.20.0/PYPI.md +185 -0
- libex_core-0.20.0/libex-core-data/share/bash-completion/completions/libex-core +385 -0
- libex_core-0.20.0/libex-core-data/share/fish/vendor_completions.d/libex-core.fish +447 -0
- libex_core-0.20.0/libex-core-data/share/man/man1/libex-core.1 +1556 -0
- libex_core-0.20.0/libex-core-data/share/zsh/site-functions/_libex-core +699 -0
- libex_core-0.20.0/libex_core/CHANGELOG.md +379 -0
- libex_core-0.20.0/libex_core/__init__.py +41 -0
- libex_core-0.20.0/libex_core/__main__.py +10 -0
- libex_core-0.20.0/libex_core/asin.py +38 -0
- libex_core-0.20.0/libex_core/audible/__init__.py +12 -0
- libex_core-0.20.0/libex_core/audible/_concurrency.py +265 -0
- libex_core-0.20.0/libex_core/audible/_retry.py +84 -0
- libex_core-0.20.0/libex_core/audible/authors/__init__.py +14 -0
- libex_core-0.20.0/libex_core/audible/authors/by_name.py +352 -0
- libex_core-0.20.0/libex_core/audible/authors/catalog.py +1211 -0
- libex_core-0.20.0/libex_core/audible/authors/profile.py +106 -0
- libex_core-0.20.0/libex_core/audible/authors/screens.py +879 -0
- libex_core-0.20.0/libex_core/audible/books.py +869 -0
- libex_core-0.20.0/libex_core/audible/chapters.py +261 -0
- libex_core-0.20.0/libex_core/audible/client.py +899 -0
- libex_core-0.20.0/libex_core/audible/extras.py +321 -0
- libex_core-0.20.0/libex_core/audible/releases.py +357 -0
- libex_core-0.20.0/libex_core/audible/search.py +139 -0
- libex_core-0.20.0/libex_core/audible/series.py +168 -0
- libex_core-0.20.0/libex_core/cli/__init__.py +7 -0
- libex_core-0.20.0/libex_core/cli/_args.py +140 -0
- libex_core-0.20.0/libex_core/cli/_db_args.py +126 -0
- libex_core-0.20.0/libex_core/cli/_render.py +618 -0
- libex_core-0.20.0/libex_core/cli/_run.py +58 -0
- libex_core-0.20.0/libex_core/cli/_shaping.py +105 -0
- libex_core-0.20.0/libex_core/cli/commands/__init__.py +4 -0
- libex_core-0.20.0/libex_core/cli/commands/abs_search.py +83 -0
- libex_core-0.20.0/libex_core/cli/commands/author.py +110 -0
- libex_core-0.20.0/libex_core/cli/commands/book.py +146 -0
- libex_core-0.20.0/libex_core/cli/commands/completion.py +40 -0
- libex_core-0.20.0/libex_core/cli/commands/config.py +34 -0
- libex_core-0.20.0/libex_core/cli/commands/db.py +572 -0
- libex_core-0.20.0/libex_core/cli/commands/narrator.py +44 -0
- libex_core-0.20.0/libex_core/cli/commands/releases.py +146 -0
- libex_core-0.20.0/libex_core/cli/commands/search.py +70 -0
- libex_core-0.20.0/libex_core/cli/commands/series.py +79 -0
- libex_core-0.20.0/libex_core/cli/environment.py +151 -0
- libex_core-0.20.0/libex_core/cli/exit_codes.py +96 -0
- libex_core-0.20.0/libex_core/cli/main.py +69 -0
- libex_core-0.20.0/libex_core/cli/output.py +86 -0
- libex_core-0.20.0/libex_core/cli/parser.py +90 -0
- libex_core-0.20.0/libex_core/cli/session.py +112 -0
- libex_core-0.20.0/libex_core/cli/store_state.py +43 -0
- libex_core-0.20.0/libex_core/exceptions.py +99 -0
- libex_core-0.20.0/libex_core/log_safety.py +111 -0
- libex_core-0.20.0/libex_core/lookup/__init__.py +58 -0
- libex_core-0.20.0/libex_core/lookup/_common.py +7 -0
- libex_core-0.20.0/libex_core/lookup/_shaping.py +76 -0
- libex_core-0.20.0/libex_core/lookup/_store.py +497 -0
- libex_core-0.20.0/libex_core/lookup/author_books.py +473 -0
- libex_core-0.20.0/libex_core/lookup/authors.py +176 -0
- libex_core-0.20.0/libex_core/lookup/books.py +571 -0
- libex_core-0.20.0/libex_core/lookup/releases.py +240 -0
- libex_core-0.20.0/libex_core/lookup/search.py +390 -0
- libex_core-0.20.0/libex_core/lookup/series.py +290 -0
- libex_core-0.20.0/libex_core/models.py +630 -0
- libex_core-0.20.0/libex_core/py.typed +0 -0
- libex_core-0.20.0/libex_core/shaping.py +173 -0
- libex_core-0.20.0/libex_core/storage/__init__.py +68 -0
- libex_core-0.20.0/libex_core/storage/base.py +14 -0
- libex_core-0.20.0/libex_core/storage/dialect.py +357 -0
- libex_core-0.20.0/libex_core/storage/filtering.py +204 -0
- libex_core-0.20.0/libex_core/storage/merge.py +266 -0
- libex_core-0.20.0/libex_core/storage/migrations/env.py +65 -0
- libex_core-0.20.0/libex_core/storage/migrations/script.py.mako +28 -0
- libex_core-0.20.0/libex_core/storage/migrations/versions/438dbe70d041_create_core_tables.py +258 -0
- libex_core-0.20.0/libex_core/storage/models.py +505 -0
- libex_core-0.20.0/libex_core/storage/read/__init__.py +11 -0
- libex_core-0.20.0/libex_core/storage/read/_compat.py +216 -0
- libex_core-0.20.0/libex_core/storage/read/books.py +541 -0
- libex_core-0.20.0/libex_core/storage/read/people.py +379 -0
- libex_core-0.20.0/libex_core/storage/read/series.py +151 -0
- libex_core-0.20.0/libex_core/storage/read/shapes.py +271 -0
- libex_core-0.20.0/libex_core/storage/read/stats.py +72 -0
- libex_core-0.20.0/libex_core/storage/sorting.py +72 -0
- libex_core-0.20.0/libex_core/storage/store.py +576 -0
- libex_core-0.20.0/libex_core/storage/types.py +55 -0
- libex_core-0.20.0/libex_core/storage/upgrade.py +147 -0
- libex_core-0.20.0/libex_core/storage/write/__init__.py +41 -0
- libex_core-0.20.0/libex_core/storage/write/books.py +202 -0
- libex_core-0.20.0/libex_core/storage/write/entities.py +437 -0
- libex_core-0.20.0/libex_core/storage/write/params.py +103 -0
- libex_core-0.20.0/libex_core/storage/write/serialize.py +75 -0
- libex_core-0.20.0/libex_core/storage/write/statements.py +481 -0
- libex_core-0.20.0/libex_core/storage/write/support.py +164 -0
- libex_core-0.20.0/libex_core/text.py +49 -0
- libex_core-0.20.0/pyproject.toml +54 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LibexHQ
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: libex-core
|
|
3
|
+
Version: 0.20.0
|
|
4
|
+
Summary: Audible metadata fetched and normalized into data shaped after AudiMeta's.
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: httpx>=0.28.1,<0.29
|
|
16
|
+
Requires-Dist: pydantic>=2.13.4,<3
|
|
17
|
+
Requires-Dist: libex-core[storage] ; extra == "postgres"
|
|
18
|
+
Requires-Dist: asyncpg>=0.31,<0.32 ; extra == "postgres"
|
|
19
|
+
Requires-Dist: sqlalchemy>=2.0.46,<2.1 ; extra == "storage"
|
|
20
|
+
Requires-Dist: alembic>=1.18.4,<1.19 ; extra == "storage"
|
|
21
|
+
Requires-Dist: aiosqlite>=0.22.1,<0.23 ; extra == "storage"
|
|
22
|
+
Project-URL: Changelog, https://github.com/LibexHQ/Libex/blob/main/libex_core/CHANGELOG.md
|
|
23
|
+
Project-URL: Homepage, https://github.com/LibexHQ/Libex
|
|
24
|
+
Project-URL: Issues, https://github.com/LibexHQ/Libex/issues
|
|
25
|
+
Project-URL: Source, https://github.com/LibexHQ/Libex
|
|
26
|
+
Provides-Extra: postgres
|
|
27
|
+
Provides-Extra: storage
|
|
28
|
+
Import-Name: libex_core
|
|
29
|
+
|
|
30
|
+
# libex-core
|
|
31
|
+
|
|
32
|
+
The Audible metadata library and `libex-core` command line behind
|
|
33
|
+
[Libex](https://github.com/LibexHQ/Libex). It runs on your own machine,
|
|
34
|
+
fetches from Audible, and returns the same JSON shapes the Libex API
|
|
35
|
+
publishes: books, chapters, series, authors, narrators, search and new
|
|
36
|
+
releases, in all eleven Audible marketplaces (`us uk ca au de fr it es jp in br`).
|
|
37
|
+
|
|
38
|
+
Libex also runs as a hosted API, and a free public instance is at
|
|
39
|
+
[libexdb.com](https://libexdb.com). You do not need it to use this package.
|
|
40
|
+
|
|
41
|
+
`pip install libex` is a different, unrelated project. This one is
|
|
42
|
+
`libex-core`.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
pip install libex-core
|
|
48
|
+
pip install "libex-core[storage]" # adds the optional local store (SQLite)
|
|
49
|
+
pip install "libex-core[postgres]" # the store, with Postgres as well
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Python 3.12 or newer. The base install depends on `httpx` and `pydantic`
|
|
53
|
+
only. The `libex-core` command is installed with the package, extras or not.
|
|
54
|
+
|
|
55
|
+
## Set up the network first
|
|
56
|
+
|
|
57
|
+
Every request goes to Audible from this machine. Audible sees the address it
|
|
58
|
+
comes from together with what you look up, which is part of your reading
|
|
59
|
+
history tied to your connection, so the package makes you decide how to
|
|
60
|
+
connect before it sends anything. The command line reads two environment
|
|
61
|
+
variables, never an option, because a proxy URL can carry credentials that
|
|
62
|
+
other processes could read from a command line:
|
|
63
|
+
|
|
64
|
+
| Variable | Meaning |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `LIBEX_CORE_PROXY_URL` | An `http://` or `https://` proxy every request goes through. May carry credentials. |
|
|
67
|
+
| `LIBEX_CORE_ALLOW_DIRECT_EGRESS` | `1`, `true`, `yes` or `on` lets requests leave from this machine's own address when no proxy is set. Anything else, or unset, refuses. |
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
export LIBEX_CORE_PROXY_URL="http://user:password@proxy.example:8080"
|
|
71
|
+
libex-core config # {"transport":{"mode":"proxy","host":"proxy.example"}}
|
|
72
|
+
|
|
73
|
+
# or, knowing what it means:
|
|
74
|
+
export LIBEX_CORE_ALLOW_DIRECT_EGRESS=1
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
With neither set, a command that needs Audible exits with status 5 and says
|
|
78
|
+
what to set. `libex-core config` makes no request and never prints the proxy
|
|
79
|
+
URL. What Audible and a proxy can see is described in the
|
|
80
|
+
[privacy notice](https://github.com/LibexHQ/Libex/blob/main/PRIVACY.md#the-embeddable-library).
|
|
81
|
+
|
|
82
|
+
## Command line
|
|
83
|
+
|
|
84
|
+
Results are JSON on standard output; logs and errors go to standard error.
|
|
85
|
+
`--region` defaults to `us`. Book ASINs belong to one marketplace, so use the
|
|
86
|
+
region the book was published in.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
libex-core book get ASIN --region us
|
|
90
|
+
libex-core book bulk ASIN1 ASIN2 --sort rating --order desc
|
|
91
|
+
libex-core book bulk --file asins.txt --language english
|
|
92
|
+
libex-core book chapters ASIN
|
|
93
|
+
|
|
94
|
+
libex-core series get SERIES_ASIN
|
|
95
|
+
libex-core series books SERIES_ASIN
|
|
96
|
+
libex-core series search "Mistborn"
|
|
97
|
+
|
|
98
|
+
libex-core author get B001IGFHW6
|
|
99
|
+
libex-core author search "Sanderson"
|
|
100
|
+
libex-core author books B001IGFHW6 --sort releaseDate
|
|
101
|
+
libex-core author books-by-name "Brandon Sanderson"
|
|
102
|
+
|
|
103
|
+
libex-core narrator books "Michael Kramer"
|
|
104
|
+
|
|
105
|
+
libex-core search --title "Hobbit" --author "Tolkien" --limit 5
|
|
106
|
+
libex-core quick-search "project hail mary"
|
|
107
|
+
libex-core abs search --title "Hobbit"
|
|
108
|
+
libex-core abs quick-search --keywords "project hail mary"
|
|
109
|
+
|
|
110
|
+
libex-core releases new --days 30
|
|
111
|
+
libex-core releases coming-soon --days 90 --category ID
|
|
112
|
+
libex-core releases categories --flat
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`libex-core COMMAND --help` lists every option, including the filters
|
|
116
|
+
(`--language`, `--genre`, `--longer-than` and others) and `--sort` / `--order`
|
|
117
|
+
that the list commands share. `libex-core completion bash` (also `zsh`,
|
|
118
|
+
`fish`) prints a completion script.
|
|
119
|
+
|
|
120
|
+
When a list of books may not be whole, it is still printed, a notice naming
|
|
121
|
+
why goes to standard error, and the status is 0.
|
|
122
|
+
|
|
123
|
+
### Exit status
|
|
124
|
+
|
|
125
|
+
| Status | Meaning |
|
|
126
|
+
|---|---|
|
|
127
|
+
| 0 | Succeeded. |
|
|
128
|
+
| 1 | Unexpected error. Rerun with `-vv` for a traceback. |
|
|
129
|
+
| 2 | The command line was not understood, or an argument was rejected. |
|
|
130
|
+
| 3 | The requested item does not exist, or nothing matched. |
|
|
131
|
+
| 4 | Audible could not be reached or did not answer. Retrying later may succeed. |
|
|
132
|
+
| 5 | Configuration is missing or invalid, or the local store is off, missing its extra, unreachable or not ready. |
|
|
133
|
+
| 130 | Interrupted. |
|
|
134
|
+
| 141 | The reader of standard output went away. |
|
|
135
|
+
|
|
136
|
+
## Local store
|
|
137
|
+
|
|
138
|
+
By default nothing is kept. Setting `LIBEX_CORE_STORAGE` turns on a local
|
|
139
|
+
database that lookups write what Audible answered into, under the same merge
|
|
140
|
+
rules as the hosted service, and that serves the stored copy when Audible is
|
|
141
|
+
unreachable. It needs the
|
|
142
|
+
`storage` extra.
|
|
143
|
+
|
|
144
|
+
| `LIBEX_CORE_STORAGE` | Store |
|
|
145
|
+
|---|---|
|
|
146
|
+
| unset, empty or `off` | none (default) |
|
|
147
|
+
| `sqlite` | `libex.db` in your user data directory: `~/.local/share/libex-core/` on Linux (or under `$XDG_DATA_HOME`), `~/Library/Application Support/libex-core/` on macOS, `%LOCALAPPDATA%\libex-core\` on Windows |
|
|
148
|
+
| an absolute path | that SQLite file |
|
|
149
|
+
| a `sqlite:///` or `postgresql://` URL | that database (`postgres` extra for Postgres) |
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
export LIBEX_CORE_STORAGE=sqlite
|
|
153
|
+
libex-core db upgrade # creates the file; the only command that changes the schema
|
|
154
|
+
libex-core db status # exits 0 only when the store is ready
|
|
155
|
+
libex-core book get ASIN # now also stored
|
|
156
|
+
libex-core db book ASIN # read back from the store, no request to Audible
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The other `db` commands read what is stored without contacting Audible:
|
|
160
|
+
`books`, `chapters`, `author`, `author-books`, `series`, `series-books`,
|
|
161
|
+
`narrators`, `narrator-books`, `genres`, `plans`, `plan`, `vvab`,
|
|
162
|
+
`new-releases`, `coming-soon` and `stats`. Each prints the shape of the hosted
|
|
163
|
+
API's `/db` route of the same name and exits 3 when it finds nothing. The
|
|
164
|
+
store is never upgraded implicitly, and a database that holds tables this
|
|
165
|
+
package did not create is refused untouched.
|
|
166
|
+
|
|
167
|
+
**The store is a plaintext record of what you have looked up.** On Linux and
|
|
168
|
+
macOS a new SQLite file is created readable by you only. The storage setting
|
|
169
|
+
can carry database credentials, so it is read from the environment, never
|
|
170
|
+
accepted as an option, and never printed. To delete a SQLite store, remove
|
|
171
|
+
the file and its `-wal` and `-shm` companions beside it.
|
|
172
|
+
|
|
173
|
+
## Use it as a library
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
import asyncio
|
|
177
|
+
|
|
178
|
+
from libex_core.audible.client import LibexClient
|
|
179
|
+
from libex_core.exceptions import LibexException
|
|
180
|
+
from libex_core.lookup import get_book, search
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
async def main() -> None:
|
|
184
|
+
# The proxy decision is explicit and has no default: pass a proxy URL,
|
|
185
|
+
# or allow_direct_egress=True to send from this machine's own address.
|
|
186
|
+
asin = "..." # the book's ASIN
|
|
187
|
+
async with LibexClient(proxy_url="http://proxy.example:8080") as client:
|
|
188
|
+
try:
|
|
189
|
+
book = await get_book(client.get, asin, region="us")
|
|
190
|
+
print(book.title)
|
|
191
|
+
for hit in await search(client.get, title="Hobbit", limit=5):
|
|
192
|
+
print(hit.asin, hit.title)
|
|
193
|
+
except LibexException as exc:
|
|
194
|
+
print(exc.code, exc.message)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
asyncio.run(main())
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The library reads no environment variable; every setting is an argument.
|
|
201
|
+
Each lookup in `libex_core.lookup` takes the request callable (`client.get`)
|
|
202
|
+
first and `region` as a keyword, and returns a model from `libex_core.models`
|
|
203
|
+
(pydantic). Pass `store=` a `libex_core.storage.LocalStore` to use the local
|
|
204
|
+
store as above; without it nothing is persisted. Importing `libex_core`
|
|
205
|
+
itself imports nothing else, and the storage libraries load only when
|
|
206
|
+
`libex_core.storage` is used.
|
|
207
|
+
|
|
208
|
+
## Links
|
|
209
|
+
|
|
210
|
+
- Source and issues: <https://github.com/LibexHQ/Libex>
|
|
211
|
+
- Changelog: <https://github.com/LibexHQ/Libex/blob/main/libex_core/CHANGELOG.md>
|
|
212
|
+
- Privacy: <https://github.com/LibexHQ/Libex/blob/main/PRIVACY.md>
|
|
213
|
+
- Hosted API: <https://libexdb.com>
|
|
214
|
+
- License: MIT
|
|
215
|
+
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# libex-core
|
|
2
|
+
|
|
3
|
+
The Audible metadata library and `libex-core` command line behind
|
|
4
|
+
[Libex](https://github.com/LibexHQ/Libex). It runs on your own machine,
|
|
5
|
+
fetches from Audible, and returns the same JSON shapes the Libex API
|
|
6
|
+
publishes: books, chapters, series, authors, narrators, search and new
|
|
7
|
+
releases, in all eleven Audible marketplaces (`us uk ca au de fr it es jp in br`).
|
|
8
|
+
|
|
9
|
+
Libex also runs as a hosted API, and a free public instance is at
|
|
10
|
+
[libexdb.com](https://libexdb.com). You do not need it to use this package.
|
|
11
|
+
|
|
12
|
+
`pip install libex` is a different, unrelated project. This one is
|
|
13
|
+
`libex-core`.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
pip install libex-core
|
|
19
|
+
pip install "libex-core[storage]" # adds the optional local store (SQLite)
|
|
20
|
+
pip install "libex-core[postgres]" # the store, with Postgres as well
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Python 3.12 or newer. The base install depends on `httpx` and `pydantic`
|
|
24
|
+
only. The `libex-core` command is installed with the package, extras or not.
|
|
25
|
+
|
|
26
|
+
## Set up the network first
|
|
27
|
+
|
|
28
|
+
Every request goes to Audible from this machine. Audible sees the address it
|
|
29
|
+
comes from together with what you look up, which is part of your reading
|
|
30
|
+
history tied to your connection, so the package makes you decide how to
|
|
31
|
+
connect before it sends anything. The command line reads two environment
|
|
32
|
+
variables, never an option, because a proxy URL can carry credentials that
|
|
33
|
+
other processes could read from a command line:
|
|
34
|
+
|
|
35
|
+
| Variable | Meaning |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `LIBEX_CORE_PROXY_URL` | An `http://` or `https://` proxy every request goes through. May carry credentials. |
|
|
38
|
+
| `LIBEX_CORE_ALLOW_DIRECT_EGRESS` | `1`, `true`, `yes` or `on` lets requests leave from this machine's own address when no proxy is set. Anything else, or unset, refuses. |
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
export LIBEX_CORE_PROXY_URL="http://user:password@proxy.example:8080"
|
|
42
|
+
libex-core config # {"transport":{"mode":"proxy","host":"proxy.example"}}
|
|
43
|
+
|
|
44
|
+
# or, knowing what it means:
|
|
45
|
+
export LIBEX_CORE_ALLOW_DIRECT_EGRESS=1
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
With neither set, a command that needs Audible exits with status 5 and says
|
|
49
|
+
what to set. `libex-core config` makes no request and never prints the proxy
|
|
50
|
+
URL. What Audible and a proxy can see is described in the
|
|
51
|
+
[privacy notice](https://github.com/LibexHQ/Libex/blob/main/PRIVACY.md#the-embeddable-library).
|
|
52
|
+
|
|
53
|
+
## Command line
|
|
54
|
+
|
|
55
|
+
Results are JSON on standard output; logs and errors go to standard error.
|
|
56
|
+
`--region` defaults to `us`. Book ASINs belong to one marketplace, so use the
|
|
57
|
+
region the book was published in.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
libex-core book get ASIN --region us
|
|
61
|
+
libex-core book bulk ASIN1 ASIN2 --sort rating --order desc
|
|
62
|
+
libex-core book bulk --file asins.txt --language english
|
|
63
|
+
libex-core book chapters ASIN
|
|
64
|
+
|
|
65
|
+
libex-core series get SERIES_ASIN
|
|
66
|
+
libex-core series books SERIES_ASIN
|
|
67
|
+
libex-core series search "Mistborn"
|
|
68
|
+
|
|
69
|
+
libex-core author get B001IGFHW6
|
|
70
|
+
libex-core author search "Sanderson"
|
|
71
|
+
libex-core author books B001IGFHW6 --sort releaseDate
|
|
72
|
+
libex-core author books-by-name "Brandon Sanderson"
|
|
73
|
+
|
|
74
|
+
libex-core narrator books "Michael Kramer"
|
|
75
|
+
|
|
76
|
+
libex-core search --title "Hobbit" --author "Tolkien" --limit 5
|
|
77
|
+
libex-core quick-search "project hail mary"
|
|
78
|
+
libex-core abs search --title "Hobbit"
|
|
79
|
+
libex-core abs quick-search --keywords "project hail mary"
|
|
80
|
+
|
|
81
|
+
libex-core releases new --days 30
|
|
82
|
+
libex-core releases coming-soon --days 90 --category ID
|
|
83
|
+
libex-core releases categories --flat
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`libex-core COMMAND --help` lists every option, including the filters
|
|
87
|
+
(`--language`, `--genre`, `--longer-than` and others) and `--sort` / `--order`
|
|
88
|
+
that the list commands share. `libex-core completion bash` (also `zsh`,
|
|
89
|
+
`fish`) prints a completion script.
|
|
90
|
+
|
|
91
|
+
When a list of books may not be whole, it is still printed, a notice naming
|
|
92
|
+
why goes to standard error, and the status is 0.
|
|
93
|
+
|
|
94
|
+
### Exit status
|
|
95
|
+
|
|
96
|
+
| Status | Meaning |
|
|
97
|
+
|---|---|
|
|
98
|
+
| 0 | Succeeded. |
|
|
99
|
+
| 1 | Unexpected error. Rerun with `-vv` for a traceback. |
|
|
100
|
+
| 2 | The command line was not understood, or an argument was rejected. |
|
|
101
|
+
| 3 | The requested item does not exist, or nothing matched. |
|
|
102
|
+
| 4 | Audible could not be reached or did not answer. Retrying later may succeed. |
|
|
103
|
+
| 5 | Configuration is missing or invalid, or the local store is off, missing its extra, unreachable or not ready. |
|
|
104
|
+
| 130 | Interrupted. |
|
|
105
|
+
| 141 | The reader of standard output went away. |
|
|
106
|
+
|
|
107
|
+
## Local store
|
|
108
|
+
|
|
109
|
+
By default nothing is kept. Setting `LIBEX_CORE_STORAGE` turns on a local
|
|
110
|
+
database that lookups write what Audible answered into, under the same merge
|
|
111
|
+
rules as the hosted service, and that serves the stored copy when Audible is
|
|
112
|
+
unreachable. It needs the
|
|
113
|
+
`storage` extra.
|
|
114
|
+
|
|
115
|
+
| `LIBEX_CORE_STORAGE` | Store |
|
|
116
|
+
|---|---|
|
|
117
|
+
| unset, empty or `off` | none (default) |
|
|
118
|
+
| `sqlite` | `libex.db` in your user data directory: `~/.local/share/libex-core/` on Linux (or under `$XDG_DATA_HOME`), `~/Library/Application Support/libex-core/` on macOS, `%LOCALAPPDATA%\libex-core\` on Windows |
|
|
119
|
+
| an absolute path | that SQLite file |
|
|
120
|
+
| a `sqlite:///` or `postgresql://` URL | that database (`postgres` extra for Postgres) |
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
export LIBEX_CORE_STORAGE=sqlite
|
|
124
|
+
libex-core db upgrade # creates the file; the only command that changes the schema
|
|
125
|
+
libex-core db status # exits 0 only when the store is ready
|
|
126
|
+
libex-core book get ASIN # now also stored
|
|
127
|
+
libex-core db book ASIN # read back from the store, no request to Audible
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The other `db` commands read what is stored without contacting Audible:
|
|
131
|
+
`books`, `chapters`, `author`, `author-books`, `series`, `series-books`,
|
|
132
|
+
`narrators`, `narrator-books`, `genres`, `plans`, `plan`, `vvab`,
|
|
133
|
+
`new-releases`, `coming-soon` and `stats`. Each prints the shape of the hosted
|
|
134
|
+
API's `/db` route of the same name and exits 3 when it finds nothing. The
|
|
135
|
+
store is never upgraded implicitly, and a database that holds tables this
|
|
136
|
+
package did not create is refused untouched.
|
|
137
|
+
|
|
138
|
+
**The store is a plaintext record of what you have looked up.** On Linux and
|
|
139
|
+
macOS a new SQLite file is created readable by you only. The storage setting
|
|
140
|
+
can carry database credentials, so it is read from the environment, never
|
|
141
|
+
accepted as an option, and never printed. To delete a SQLite store, remove
|
|
142
|
+
the file and its `-wal` and `-shm` companions beside it.
|
|
143
|
+
|
|
144
|
+
## Use it as a library
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import asyncio
|
|
148
|
+
|
|
149
|
+
from libex_core.audible.client import LibexClient
|
|
150
|
+
from libex_core.exceptions import LibexException
|
|
151
|
+
from libex_core.lookup import get_book, search
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
async def main() -> None:
|
|
155
|
+
# The proxy decision is explicit and has no default: pass a proxy URL,
|
|
156
|
+
# or allow_direct_egress=True to send from this machine's own address.
|
|
157
|
+
asin = "..." # the book's ASIN
|
|
158
|
+
async with LibexClient(proxy_url="http://proxy.example:8080") as client:
|
|
159
|
+
try:
|
|
160
|
+
book = await get_book(client.get, asin, region="us")
|
|
161
|
+
print(book.title)
|
|
162
|
+
for hit in await search(client.get, title="Hobbit", limit=5):
|
|
163
|
+
print(hit.asin, hit.title)
|
|
164
|
+
except LibexException as exc:
|
|
165
|
+
print(exc.code, exc.message)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
asyncio.run(main())
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The library reads no environment variable; every setting is an argument.
|
|
172
|
+
Each lookup in `libex_core.lookup` takes the request callable (`client.get`)
|
|
173
|
+
first and `region` as a keyword, and returns a model from `libex_core.models`
|
|
174
|
+
(pydantic). Pass `store=` a `libex_core.storage.LocalStore` to use the local
|
|
175
|
+
store as above; without it nothing is persisted. Importing `libex_core`
|
|
176
|
+
itself imports nothing else, and the storage libraries load only when
|
|
177
|
+
`libex_core.storage` is used.
|
|
178
|
+
|
|
179
|
+
## Links
|
|
180
|
+
|
|
181
|
+
- Source and issues: <https://github.com/LibexHQ/Libex>
|
|
182
|
+
- Changelog: <https://github.com/LibexHQ/Libex/blob/main/libex_core/CHANGELOG.md>
|
|
183
|
+
- Privacy: <https://github.com/LibexHQ/Libex/blob/main/PRIVACY.md>
|
|
184
|
+
- Hosted API: <https://libexdb.com>
|
|
185
|
+
- License: MIT
|