lexiafrik 2.0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 XEL SOFT AI
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,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: lexiafrik
3
+ Version: 2.0.0
4
+ Summary: Python SDK and command line for LexiAfrik: speech-to-text for African languages
5
+ Home-page: https://lexiafrik.com
6
+ Author: XEL SOFT AI
7
+ Author-email: contact@xelsoftai.com
8
+ License: MIT
9
+ Project-URL: Documentation, https://docs.lexiafrik.com
10
+ Project-URL: Source, https://github.com/xelsoft-ai/lexiafrik-python
11
+ Project-URL: Contact, https://lexiafrik.com/contact
12
+ Keywords: wolof,african-languages,speech-to-text,transcription,lexiafrik,asr
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
17
+ Classifier: Intended Audience :: Developers
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: httpx>=0.24.0
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: keywords
29
+ Dynamic: license
30
+ Dynamic: license-file
31
+ Dynamic: project-url
32
+ Dynamic: requires-dist
33
+ Dynamic: requires-python
34
+ Dynamic: summary
35
+
36
+ # LexiAfrik Python SDK
37
+
38
+ Official Python SDK and command line for the [LexiAfrik API](https://docs.lexiafrik.com): speech-to-text for
39
+ Wolof and 30+ other African languages. Full documentation: **https://docs.lexiafrik.com**
40
+
41
+ 🇫🇷 [Version française](https://github.com/xelsoft-ai/lexiafrik-python/blob/main/README.fr.md)
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ pip install lexiafrik
47
+ ```
48
+
49
+ Python 3.8 or newer.
50
+
51
+ ## Sign in from the terminal
52
+
53
+ ```bash
54
+ lexiafrik login # prints a code, opens your browser, saves a key
55
+ lexiafrik whoami # the saved key and its plan
56
+ lexiafrik logout # deletes the credentials and revokes this terminal's key
57
+ ```
58
+
59
+ `lexiafrik login` needs no password and no key to copy: you sign in on lexiafrik.com, check the code shown
60
+ in the terminal and approve. An API key dedicated to this terminal is created on your account and saved in
61
+ `~/.lexiafrik/credentials.json` (readable by you only). You can revoke it at any time from
62
+ **lexiafrik.com > API keys**.
63
+
64
+ Key lookup order: the `api_key=` argument, the `LEXIAFRIK_API_KEY` environment variable, then the file saved
65
+ by `lexiafrik login`. On a server or in CI, create a key on the website and use the environment variable.
66
+
67
+ ## Usage
68
+
69
+ ```python
70
+ from lexiafrik import LexiAfrikClient
71
+
72
+ client = LexiAfrikClient()
73
+
74
+ # Short file (50 MB maximum): immediate response
75
+ result = client.transcribe("speech.wav", language="wo", include_segments=True)
76
+ print(result.text)
77
+ for seg in result.segments:
78
+ print(seg) # [0.4s → 6.1s] First segment.
79
+
80
+ # Large file or video: direct upload, asynchronous job, wait for the result
81
+ result = client.transcribe_large("conference.mp4", language="wo",
82
+ on_progress=lambda n: print(n, "segments"))
83
+
84
+ # Direct link to an audio or video file
85
+ result = client.transcribe_url("https://example.com/recording.mp3")
86
+
87
+ # Languages, account, history
88
+ print([l.code for l in client.list_languages()])
89
+ print(client.get_account().available_minutes, "minutes available")
90
+ for t in client.list_transcriptions(page_size=5).items:
91
+ print(t.request_id, t.status)
92
+ ```
93
+
94
+ Links to streaming platforms (YouTube, TikTok…) are not accepted: provide a direct link or upload the file.
95
+
96
+ ## Error handling
97
+
98
+ ```python
99
+ from lexiafrik import LexiAfrikClient, AuthenticationError, QuotaExceededError, LexiAfrikError
100
+
101
+ try:
102
+ result = client.transcribe("audio.wav")
103
+ except AuthenticationError:
104
+ print("Invalid key: run `lexiafrik login` again")
105
+ except QuotaExceededError:
106
+ print("Monthly quota and credit exhausted")
107
+ except LexiAfrikError as e:
108
+ print(e) # English message; e.detail holds the server's own text, if any
109
+ ```
110
+
111
+ Exceptions: `AuthenticationError` (401/403), `QuotaExceededError` (402), `NotFoundError` (404),
112
+ `AudioTooLargeError` (413), `UnsupportedFormatError` (415), `TranscriptionError` (422),
113
+ `RateLimitError` (429), `APIError` (5xx).
114
+
115
+ ## Environment variables
116
+
117
+ | Variable | Purpose |
118
+ |---|---|
119
+ | `LEXIAFRIK_API_KEY` | API key (takes priority over the saved file). |
120
+ | `LEXIAFRIK_BASE_URL` | Another API address (tests). Default: `https://api.lexiafrik.com`. |
121
+ | `LEXIAFRIK_CREDENTIALS` | Another location for the credentials file. |
122
+ | `LEXIAFRIK_LANG` | Preferred language of server messages, sent as `Accept-Language` (default: `en`). |
123
+
124
+ ## Migrating from version 1
125
+
126
+ SDK 2.0 follows the current API: a single result type (`TranscriptionResult`) for every mode, `get_usage()`
127
+ returns the account (plan, monthly quota, credit) rather than daily limits, and the `model`, `n_tokens`
128
+ and `rtf` fields of the result no longer exist.
129
+
130
+ ## Tests
131
+
132
+ ```bash
133
+ python -m unittest discover -s tests -v
134
+ ```
135
+
136
+ No external network and no real key needed.
137
+
138
+ ## Get an API key
139
+
140
+ Create an account on [lexiafrik.com](https://lexiafrik.com), or run `lexiafrik login`.
141
+
142
+ ## License
143
+
144
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,109 @@
1
+ # LexiAfrik Python SDK
2
+
3
+ Official Python SDK and command line for the [LexiAfrik API](https://docs.lexiafrik.com): speech-to-text for
4
+ Wolof and 30+ other African languages. Full documentation: **https://docs.lexiafrik.com**
5
+
6
+ 🇫🇷 [Version française](https://github.com/xelsoft-ai/lexiafrik-python/blob/main/README.fr.md)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install lexiafrik
12
+ ```
13
+
14
+ Python 3.8 or newer.
15
+
16
+ ## Sign in from the terminal
17
+
18
+ ```bash
19
+ lexiafrik login # prints a code, opens your browser, saves a key
20
+ lexiafrik whoami # the saved key and its plan
21
+ lexiafrik logout # deletes the credentials and revokes this terminal's key
22
+ ```
23
+
24
+ `lexiafrik login` needs no password and no key to copy: you sign in on lexiafrik.com, check the code shown
25
+ in the terminal and approve. An API key dedicated to this terminal is created on your account and saved in
26
+ `~/.lexiafrik/credentials.json` (readable by you only). You can revoke it at any time from
27
+ **lexiafrik.com > API keys**.
28
+
29
+ Key lookup order: the `api_key=` argument, the `LEXIAFRIK_API_KEY` environment variable, then the file saved
30
+ by `lexiafrik login`. On a server or in CI, create a key on the website and use the environment variable.
31
+
32
+ ## Usage
33
+
34
+ ```python
35
+ from lexiafrik import LexiAfrikClient
36
+
37
+ client = LexiAfrikClient()
38
+
39
+ # Short file (50 MB maximum): immediate response
40
+ result = client.transcribe("speech.wav", language="wo", include_segments=True)
41
+ print(result.text)
42
+ for seg in result.segments:
43
+ print(seg) # [0.4s → 6.1s] First segment.
44
+
45
+ # Large file or video: direct upload, asynchronous job, wait for the result
46
+ result = client.transcribe_large("conference.mp4", language="wo",
47
+ on_progress=lambda n: print(n, "segments"))
48
+
49
+ # Direct link to an audio or video file
50
+ result = client.transcribe_url("https://example.com/recording.mp3")
51
+
52
+ # Languages, account, history
53
+ print([l.code for l in client.list_languages()])
54
+ print(client.get_account().available_minutes, "minutes available")
55
+ for t in client.list_transcriptions(page_size=5).items:
56
+ print(t.request_id, t.status)
57
+ ```
58
+
59
+ Links to streaming platforms (YouTube, TikTok…) are not accepted: provide a direct link or upload the file.
60
+
61
+ ## Error handling
62
+
63
+ ```python
64
+ from lexiafrik import LexiAfrikClient, AuthenticationError, QuotaExceededError, LexiAfrikError
65
+
66
+ try:
67
+ result = client.transcribe("audio.wav")
68
+ except AuthenticationError:
69
+ print("Invalid key: run `lexiafrik login` again")
70
+ except QuotaExceededError:
71
+ print("Monthly quota and credit exhausted")
72
+ except LexiAfrikError as e:
73
+ print(e) # English message; e.detail holds the server's own text, if any
74
+ ```
75
+
76
+ Exceptions: `AuthenticationError` (401/403), `QuotaExceededError` (402), `NotFoundError` (404),
77
+ `AudioTooLargeError` (413), `UnsupportedFormatError` (415), `TranscriptionError` (422),
78
+ `RateLimitError` (429), `APIError` (5xx).
79
+
80
+ ## Environment variables
81
+
82
+ | Variable | Purpose |
83
+ |---|---|
84
+ | `LEXIAFRIK_API_KEY` | API key (takes priority over the saved file). |
85
+ | `LEXIAFRIK_BASE_URL` | Another API address (tests). Default: `https://api.lexiafrik.com`. |
86
+ | `LEXIAFRIK_CREDENTIALS` | Another location for the credentials file. |
87
+ | `LEXIAFRIK_LANG` | Preferred language of server messages, sent as `Accept-Language` (default: `en`). |
88
+
89
+ ## Migrating from version 1
90
+
91
+ SDK 2.0 follows the current API: a single result type (`TranscriptionResult`) for every mode, `get_usage()`
92
+ returns the account (plan, monthly quota, credit) rather than daily limits, and the `model`, `n_tokens`
93
+ and `rtf` fields of the result no longer exist.
94
+
95
+ ## Tests
96
+
97
+ ```bash
98
+ python -m unittest discover -s tests -v
99
+ ```
100
+
101
+ No external network and no real key needed.
102
+
103
+ ## Get an API key
104
+
105
+ Create an account on [lexiafrik.com](https://lexiafrik.com), or run `lexiafrik login`.
106
+
107
+ ## License
108
+
109
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,54 @@
1
+ """
2
+ LexiAfrik SDK — speech-to-text for Wolof and other African languages
3
+
4
+ Quick start:
5
+ from lexiafrik import LexiAfrikClient
6
+
7
+ client = LexiAfrikClient() # key: api_key=, LEXIAFRIK_API_KEY or `lexiafrik login`
8
+ result = client.transcribe("audio.wav", language="wo")
9
+ print(result.text)
10
+ """
11
+
12
+ from .client import LexiAfrikClient
13
+ from .models import (
14
+ AccountInfo,
15
+ Language,
16
+ SegmentResult,
17
+ TranscriptionPage,
18
+ TranscriptionResult,
19
+ TranscriptionSummary,
20
+ URLTranscriptionResult,
21
+ )
22
+ from .exceptions import (
23
+ LexiAfrikError,
24
+ AuthenticationError,
25
+ QuotaExceededError,
26
+ RateLimitError,
27
+ NotFoundError,
28
+ AudioTooLargeError,
29
+ UnsupportedFormatError,
30
+ TranscriptionError,
31
+ APIError,
32
+ )
33
+
34
+ __version__ = "2.0.0"
35
+ __author__ = "XEL SOFT AI"
36
+ __all__ = [
37
+ "LexiAfrikClient",
38
+ "TranscriptionResult",
39
+ "URLTranscriptionResult",
40
+ "SegmentResult",
41
+ "AccountInfo",
42
+ "Language",
43
+ "TranscriptionPage",
44
+ "TranscriptionSummary",
45
+ "LexiAfrikError",
46
+ "AuthenticationError",
47
+ "QuotaExceededError",
48
+ "RateLimitError",
49
+ "NotFoundError",
50
+ "AudioTooLargeError",
51
+ "UnsupportedFormatError",
52
+ "TranscriptionError",
53
+ "APIError",
54
+ ]
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())
@@ -0,0 +1,143 @@
1
+ """
2
+ LexiAfrik SDK — Sign in a terminal without a password (device-code flow, RFC 8628).
3
+
4
+ from lexiafrik.auth import login
5
+ login() # prints a code, opens the browser, saves the key
6
+
7
+ The API key is created on your account when you approve, then saved in
8
+ ``~/.lexiafrik/credentials.json`` (readable by you only). ``LexiAfrikClient()`` finds it
9
+ afterwards without any parameter. It stays revocable from lexiafrik.com > API keys.
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import platform
15
+ import socket
16
+ import time
17
+ import webbrowser
18
+ from pathlib import Path
19
+ from typing import Callable, Optional
20
+
21
+ import httpx
22
+
23
+ from .exceptions import AuthenticationError, LexiAfrikError
24
+
25
+ DEFAULT_BASE_URL = "https://api.lexiafrik.com"
26
+
27
+
28
+ def credentials_path() -> Path:
29
+ """Credentials file (set LEXIAFRIK_CREDENTIALS to move it)."""
30
+ override = os.environ.get("LEXIAFRIK_CREDENTIALS")
31
+ return Path(override) if override else Path.home() / ".lexiafrik" / "credentials.json"
32
+
33
+
34
+ def load_credentials() -> Optional[dict]:
35
+ try:
36
+ data = json.loads(credentials_path().read_text(encoding="utf-8"))
37
+ except (OSError, ValueError):
38
+ return None
39
+ return data if isinstance(data, dict) and data.get("api_key") else None
40
+
41
+
42
+ def save_credentials(data: dict) -> Path:
43
+ path = credentials_path()
44
+ path.parent.mkdir(parents=True, exist_ok=True)
45
+ try:
46
+ os.chmod(path.parent, 0o700)
47
+ except OSError:
48
+ pass
49
+ # Created directly as 0600: the key is never readable by other users.
50
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
51
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
52
+ json.dump(data, f, indent=2)
53
+ return path
54
+
55
+
56
+ def clear_credentials() -> bool:
57
+ try:
58
+ credentials_path().unlink()
59
+ return True
60
+ except FileNotFoundError:
61
+ return False
62
+
63
+
64
+ def default_client_name() -> str:
65
+ try:
66
+ host = socket.gethostname()
67
+ except OSError:
68
+ host = "terminal"
69
+ return f"{host} ({platform.system() or 'CLI'})"[:60]
70
+
71
+
72
+ def login(
73
+ base_url: Optional[str] = None,
74
+ client_name: Optional[str] = None,
75
+ open_browser: bool = True,
76
+ echo: Callable[[str], None] = print,
77
+ save: bool = True,
78
+ timeout: float = 30.0,
79
+ ) -> dict:
80
+ """
81
+ Connect this terminal to your LexiAfrik account.
82
+
83
+ Returns:
84
+ ``{"api_key", "key_hash", "prefix", "name", "base_url"}`` (saved to disk when ``save``).
85
+
86
+ Raises:
87
+ AuthenticationError: sign-in denied in the browser.
88
+ LexiAfrikError: code expired or API unreachable.
89
+ """
90
+ base = (base_url or os.environ.get("LEXIAFRIK_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
91
+ with httpx.Client(base_url=base, timeout=timeout) as http:
92
+ try:
93
+ r = http.post("/v1/auth/device", json={"client_name": client_name or default_client_name()})
94
+ except httpx.HTTPError as e:
95
+ raise LexiAfrikError(f"API unreachable ({base}): {e}") from e
96
+ if r.status_code == 429:
97
+ raise LexiAfrikError("Too many attempts. Try again in a few minutes.")
98
+ if r.status_code != 200:
99
+ raise LexiAfrikError(f"Could not start the sign-in (HTTP {r.status_code}).")
100
+ d = r.json()
101
+
102
+ url = d["verification_uri_complete"]
103
+ echo("")
104
+ echo("To connect this terminal, open:")
105
+ echo(f" {url}")
106
+ echo("")
107
+ echo(f"Confirmation code: {d['user_code']}")
108
+ echo("(only approve if you just ran this command)")
109
+ echo("")
110
+ if open_browser:
111
+ try:
112
+ webbrowser.open(url)
113
+ except Exception: # no browser (server, SSH): the URL stays displayed
114
+ pass
115
+ echo("Waiting for your approval…")
116
+
117
+ interval = max(1, int(d.get("interval", 5)))
118
+ deadline = time.monotonic() + int(d.get("expires_in", 600))
119
+ while time.monotonic() < deadline:
120
+ time.sleep(interval)
121
+ try:
122
+ r = http.post("/v1/auth/device/token", json={"device_code": d["device_code"]})
123
+ except httpx.HTTPError:
124
+ continue # transient network failure
125
+ if r.status_code == 200:
126
+ creds = {**r.json(), "base_url": base}
127
+ if save:
128
+ path = save_credentials(creds)
129
+ echo(f"Signed in. Key '{creds['name']}' saved to {path}")
130
+ return creds
131
+ is_json = r.headers.get("content-type", "").startswith("application/json")
132
+ err = (r.json() if is_json else {}).get("error")
133
+ if err == "authorization_pending":
134
+ continue
135
+ if err == "slow_down":
136
+ interval += 5
137
+ continue
138
+ if err == "access_denied":
139
+ raise AuthenticationError("Sign-in denied in the browser.")
140
+ if err == "expired_token":
141
+ raise LexiAfrikError("The code has expired. Run `lexiafrik login` again.")
142
+ raise LexiAfrikError(f"Sign-in failed ({err or r.status_code}).")
143
+ raise LexiAfrikError("The code has expired. Run `lexiafrik login` again.")
@@ -0,0 +1,82 @@
1
+ """LexiAfrik SDK — command line: ``lexiafrik login | logout | whoami``."""
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ import httpx
7
+
8
+ from . import __version__
9
+ from .auth import DEFAULT_BASE_URL, clear_credentials, credentials_path, load_credentials, login
10
+ from .exceptions import LexiAfrikError
11
+
12
+
13
+ def _login(args) -> int:
14
+ login(base_url=args.base_url, client_name=args.name, open_browser=not args.no_browser)
15
+ return 0
16
+
17
+
18
+ def _logout(args) -> int:
19
+ creds = load_credentials()
20
+ if not creds:
21
+ print("No saved credentials.")
22
+ return 0
23
+ if not args.keep_key and creds.get("key_hash"):
24
+ # Revoke this terminal's key on the LexiAfrik side (best effort: the file is deleted regardless).
25
+ try:
26
+ r = httpx.delete(f"{creds.get('base_url', DEFAULT_BASE_URL)}/v1/keys/{creds['key_hash']}",
27
+ headers={"X-API-Key": creds["api_key"]}, timeout=15)
28
+ print("Key revoked." if r.status_code < 300 else
29
+ "The key could not be revoked; revoke it from lexiafrik.com > API keys.")
30
+ except httpx.HTTPError:
31
+ print("API unreachable: revoke the key from lexiafrik.com > API keys.")
32
+ clear_credentials()
33
+ print(f"Credentials deleted ({credentials_path()}).")
34
+ return 0
35
+
36
+
37
+ def _whoami(_args) -> int:
38
+ creds = load_credentials()
39
+ if not creds:
40
+ print("Not signed in. Run `lexiafrik login`.")
41
+ return 1
42
+ r = httpx.get(f"{creds.get('base_url', DEFAULT_BASE_URL)}/v1/whoami",
43
+ headers={"X-API-Key": creds["api_key"]}, timeout=15)
44
+ if r.status_code != 200:
45
+ print("Invalid or revoked key. Run `lexiafrik login` again.")
46
+ return 1
47
+ who = r.json()
48
+ print(f"Key: {creds.get('name', '')} ({creds.get('prefix', '')}…) Plan: {who.get('tier', '?')}")
49
+ return 0
50
+
51
+
52
+ def main(argv=None) -> int:
53
+ parser = argparse.ArgumentParser(prog="lexiafrik", description="LexiAfrik - speech-to-text for African languages")
54
+ parser.add_argument("--version", action="version", version=f"lexiafrik {__version__}")
55
+ sub = parser.add_subparsers(dest="command", required=True)
56
+
57
+ p = sub.add_parser("login", help="connect this terminal to your account (no password)")
58
+ p.add_argument("--no-browser", action="store_true", help="print the link without opening the browser")
59
+ p.add_argument("--name", help="name of this terminal (shown when you approve it)")
60
+ p.add_argument("--base-url", help=f"API URL (default: {DEFAULT_BASE_URL})")
61
+ p.set_defaults(func=_login)
62
+
63
+ p = sub.add_parser("logout", help="delete saved credentials and revoke this terminal's key")
64
+ p.add_argument("--keep-key", action="store_true", help="do not revoke the key on the LexiAfrik side")
65
+ p.set_defaults(func=_logout)
66
+
67
+ p = sub.add_parser("whoami", help="show the saved key and its plan")
68
+ p.set_defaults(func=_whoami)
69
+
70
+ args = parser.parse_args(argv)
71
+ try:
72
+ return args.func(args)
73
+ except LexiAfrikError as e:
74
+ print(f"Error: {e}", file=sys.stderr)
75
+ return 1
76
+ except KeyboardInterrupt:
77
+ print("\nInterrupted.", file=sys.stderr)
78
+ return 130
79
+
80
+
81
+ if __name__ == "__main__":
82
+ sys.exit(main())