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.
Files changed (94) hide show
  1. libex_core-0.20.0/LICENSE +21 -0
  2. libex_core-0.20.0/PKG-INFO +215 -0
  3. libex_core-0.20.0/PYPI.md +185 -0
  4. libex_core-0.20.0/libex-core-data/share/bash-completion/completions/libex-core +385 -0
  5. libex_core-0.20.0/libex-core-data/share/fish/vendor_completions.d/libex-core.fish +447 -0
  6. libex_core-0.20.0/libex-core-data/share/man/man1/libex-core.1 +1556 -0
  7. libex_core-0.20.0/libex-core-data/share/zsh/site-functions/_libex-core +699 -0
  8. libex_core-0.20.0/libex_core/CHANGELOG.md +379 -0
  9. libex_core-0.20.0/libex_core/__init__.py +41 -0
  10. libex_core-0.20.0/libex_core/__main__.py +10 -0
  11. libex_core-0.20.0/libex_core/asin.py +38 -0
  12. libex_core-0.20.0/libex_core/audible/__init__.py +12 -0
  13. libex_core-0.20.0/libex_core/audible/_concurrency.py +265 -0
  14. libex_core-0.20.0/libex_core/audible/_retry.py +84 -0
  15. libex_core-0.20.0/libex_core/audible/authors/__init__.py +14 -0
  16. libex_core-0.20.0/libex_core/audible/authors/by_name.py +352 -0
  17. libex_core-0.20.0/libex_core/audible/authors/catalog.py +1211 -0
  18. libex_core-0.20.0/libex_core/audible/authors/profile.py +106 -0
  19. libex_core-0.20.0/libex_core/audible/authors/screens.py +879 -0
  20. libex_core-0.20.0/libex_core/audible/books.py +869 -0
  21. libex_core-0.20.0/libex_core/audible/chapters.py +261 -0
  22. libex_core-0.20.0/libex_core/audible/client.py +899 -0
  23. libex_core-0.20.0/libex_core/audible/extras.py +321 -0
  24. libex_core-0.20.0/libex_core/audible/releases.py +357 -0
  25. libex_core-0.20.0/libex_core/audible/search.py +139 -0
  26. libex_core-0.20.0/libex_core/audible/series.py +168 -0
  27. libex_core-0.20.0/libex_core/cli/__init__.py +7 -0
  28. libex_core-0.20.0/libex_core/cli/_args.py +140 -0
  29. libex_core-0.20.0/libex_core/cli/_db_args.py +126 -0
  30. libex_core-0.20.0/libex_core/cli/_render.py +618 -0
  31. libex_core-0.20.0/libex_core/cli/_run.py +58 -0
  32. libex_core-0.20.0/libex_core/cli/_shaping.py +105 -0
  33. libex_core-0.20.0/libex_core/cli/commands/__init__.py +4 -0
  34. libex_core-0.20.0/libex_core/cli/commands/abs_search.py +83 -0
  35. libex_core-0.20.0/libex_core/cli/commands/author.py +110 -0
  36. libex_core-0.20.0/libex_core/cli/commands/book.py +146 -0
  37. libex_core-0.20.0/libex_core/cli/commands/completion.py +40 -0
  38. libex_core-0.20.0/libex_core/cli/commands/config.py +34 -0
  39. libex_core-0.20.0/libex_core/cli/commands/db.py +572 -0
  40. libex_core-0.20.0/libex_core/cli/commands/narrator.py +44 -0
  41. libex_core-0.20.0/libex_core/cli/commands/releases.py +146 -0
  42. libex_core-0.20.0/libex_core/cli/commands/search.py +70 -0
  43. libex_core-0.20.0/libex_core/cli/commands/series.py +79 -0
  44. libex_core-0.20.0/libex_core/cli/environment.py +151 -0
  45. libex_core-0.20.0/libex_core/cli/exit_codes.py +96 -0
  46. libex_core-0.20.0/libex_core/cli/main.py +69 -0
  47. libex_core-0.20.0/libex_core/cli/output.py +86 -0
  48. libex_core-0.20.0/libex_core/cli/parser.py +90 -0
  49. libex_core-0.20.0/libex_core/cli/session.py +112 -0
  50. libex_core-0.20.0/libex_core/cli/store_state.py +43 -0
  51. libex_core-0.20.0/libex_core/exceptions.py +99 -0
  52. libex_core-0.20.0/libex_core/log_safety.py +111 -0
  53. libex_core-0.20.0/libex_core/lookup/__init__.py +58 -0
  54. libex_core-0.20.0/libex_core/lookup/_common.py +7 -0
  55. libex_core-0.20.0/libex_core/lookup/_shaping.py +76 -0
  56. libex_core-0.20.0/libex_core/lookup/_store.py +497 -0
  57. libex_core-0.20.0/libex_core/lookup/author_books.py +473 -0
  58. libex_core-0.20.0/libex_core/lookup/authors.py +176 -0
  59. libex_core-0.20.0/libex_core/lookup/books.py +571 -0
  60. libex_core-0.20.0/libex_core/lookup/releases.py +240 -0
  61. libex_core-0.20.0/libex_core/lookup/search.py +390 -0
  62. libex_core-0.20.0/libex_core/lookup/series.py +290 -0
  63. libex_core-0.20.0/libex_core/models.py +630 -0
  64. libex_core-0.20.0/libex_core/py.typed +0 -0
  65. libex_core-0.20.0/libex_core/shaping.py +173 -0
  66. libex_core-0.20.0/libex_core/storage/__init__.py +68 -0
  67. libex_core-0.20.0/libex_core/storage/base.py +14 -0
  68. libex_core-0.20.0/libex_core/storage/dialect.py +357 -0
  69. libex_core-0.20.0/libex_core/storage/filtering.py +204 -0
  70. libex_core-0.20.0/libex_core/storage/merge.py +266 -0
  71. libex_core-0.20.0/libex_core/storage/migrations/env.py +65 -0
  72. libex_core-0.20.0/libex_core/storage/migrations/script.py.mako +28 -0
  73. libex_core-0.20.0/libex_core/storage/migrations/versions/438dbe70d041_create_core_tables.py +258 -0
  74. libex_core-0.20.0/libex_core/storage/models.py +505 -0
  75. libex_core-0.20.0/libex_core/storage/read/__init__.py +11 -0
  76. libex_core-0.20.0/libex_core/storage/read/_compat.py +216 -0
  77. libex_core-0.20.0/libex_core/storage/read/books.py +541 -0
  78. libex_core-0.20.0/libex_core/storage/read/people.py +379 -0
  79. libex_core-0.20.0/libex_core/storage/read/series.py +151 -0
  80. libex_core-0.20.0/libex_core/storage/read/shapes.py +271 -0
  81. libex_core-0.20.0/libex_core/storage/read/stats.py +72 -0
  82. libex_core-0.20.0/libex_core/storage/sorting.py +72 -0
  83. libex_core-0.20.0/libex_core/storage/store.py +576 -0
  84. libex_core-0.20.0/libex_core/storage/types.py +55 -0
  85. libex_core-0.20.0/libex_core/storage/upgrade.py +147 -0
  86. libex_core-0.20.0/libex_core/storage/write/__init__.py +41 -0
  87. libex_core-0.20.0/libex_core/storage/write/books.py +202 -0
  88. libex_core-0.20.0/libex_core/storage/write/entities.py +437 -0
  89. libex_core-0.20.0/libex_core/storage/write/params.py +103 -0
  90. libex_core-0.20.0/libex_core/storage/write/serialize.py +75 -0
  91. libex_core-0.20.0/libex_core/storage/write/statements.py +481 -0
  92. libex_core-0.20.0/libex_core/storage/write/support.py +164 -0
  93. libex_core-0.20.0/libex_core/text.py +49 -0
  94. 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