tarjamli 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.
tarjamli-0.1.0/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Omneity Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: tarjamli
3
+ Version: 0.1.0
4
+ Summary: Python client for Tarjamli translation through Sawalni API
5
+ Author-email: Omar Kamali <omar@omneitylabs.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://api.sawalni.com
8
+ Project-URL: Issues, https://github.com/omneity-labs/starter-translation/issues
9
+ Project-URL: Examples, https://github.com/omneity-labs/starter-translation
10
+ Keywords: translation,darija,amazigh,tarjamli,sawalni
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Text Processing :: Linguistic
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # tarjamli
21
+
22
+ A small Python client for Tarjamli translation through the [Sawalni API](https://api.sawalni.com). Translate text, convert Darija between Arabic and Latin scripts, and give the translation context about tone, gender or address forms.
23
+
24
+ Python 3.10+. No runtime dependencies.
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ pip install tarjamli
30
+ ```
31
+
32
+ Set your Sawalni API key:
33
+
34
+ ```sh
35
+ export SAWALNI_API_KEY="your-key"
36
+ ```
37
+
38
+ ## Translate
39
+
40
+ ```python
41
+ from tarjamli import Tarjamli
42
+
43
+ client = Tarjamli()
44
+ result = client.translate(
45
+ "واش تقدر تصيفط ليا الدوسي؟",
46
+ source="ary_Arab",
47
+ target="fra_Latn",
48
+ instructions="Address one person formally using vous.",
49
+ )
50
+ print(result.text)
51
+ ```
52
+
53
+ Pass your own text. Remove `instructions` for ordinary translation, or change it to supply context. The API field is `instructions`; tone and gender are not separate parameters.
54
+
55
+ ## Convert Darija scripts
56
+
57
+ ```python
58
+ result = client.translate(
59
+ "سلام، كيف داير؟",
60
+ source="ary_Arab",
61
+ target="ary_Latn",
62
+ )
63
+ print(result.text)
64
+ ```
65
+
66
+ Reverse the source and target for Latin → Arabic. Use `client.languages()` to list supported language and script codes, including `shi_Latn` for Tachelhit and `zgh_Tfng` for Tamazight in Tifinagh.
67
+
68
+ ## CLI
69
+
70
+ ```sh
71
+ tarjamli translate --text 'واش تقدر تصيفط ليا الدوسي؟' --source ary_Arab --target fra_Latn --instructions 'Address one person formally using vous.'
72
+ tarjamli languages
73
+ ```
74
+
75
+ Use `--text -` to read standard input. Add `--json` for the full API response.
76
+
77
+ ## Configuration and results
78
+
79
+ `Tarjamli(api_key=None, timeout=180)` reads `SAWALNI_API_KEY` when no key is passed. Keep keys in server-side code or your terminal environment. The package does not load `.env` files.
80
+
81
+ `translate(text, *, source, target, instructions="", model="tarjamli-mini-v4")` returns a `Translation` with `text`, `source`, `target`, `model`, and `raw` (the full response, including usage when supplied). This release supports the checked Mini model only.
82
+
83
+ Each call sends one request to `POST https://api.sawalni.com/v1/language/translate`. Live requests use your API balance. There are no automatic retries.
84
+
85
+ ## Errors
86
+
87
+ ```python
88
+ from tarjamli import AuthenticationError, RateLimitError, TranslationError
89
+
90
+ try:
91
+ result = client.translate("Hello", source="eng_Latn", target="ary_Arab")
92
+ except AuthenticationError:
93
+ # Check your key and model access.
94
+ raise
95
+ except RateLimitError:
96
+ # Decide when to retry in your application.
97
+ raise
98
+ except TranslationError:
99
+ # Includes incomplete responses, connection errors and unexpected identities.
100
+ raise
101
+ ```
102
+
103
+ Invalid local input raises `ValueError`. Incomplete output raises `IncompleteTranslationError`, a subclass of `TranslationError`. The client checks the returned model and language codes before returning a result.
104
+
105
+ [More examples](https://github.com/omneity-labs/starter-translation) · [API documentation](https://api.sawalni.com/docs) · [MIT license](LICENSE).
@@ -0,0 +1,86 @@
1
+ # tarjamli
2
+
3
+ A small Python client for Tarjamli translation through the [Sawalni API](https://api.sawalni.com). Translate text, convert Darija between Arabic and Latin scripts, and give the translation context about tone, gender or address forms.
4
+
5
+ Python 3.10+. No runtime dependencies.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ pip install tarjamli
11
+ ```
12
+
13
+ Set your Sawalni API key:
14
+
15
+ ```sh
16
+ export SAWALNI_API_KEY="your-key"
17
+ ```
18
+
19
+ ## Translate
20
+
21
+ ```python
22
+ from tarjamli import Tarjamli
23
+
24
+ client = Tarjamli()
25
+ result = client.translate(
26
+ "واش تقدر تصيفط ليا الدوسي؟",
27
+ source="ary_Arab",
28
+ target="fra_Latn",
29
+ instructions="Address one person formally using vous.",
30
+ )
31
+ print(result.text)
32
+ ```
33
+
34
+ Pass your own text. Remove `instructions` for ordinary translation, or change it to supply context. The API field is `instructions`; tone and gender are not separate parameters.
35
+
36
+ ## Convert Darija scripts
37
+
38
+ ```python
39
+ result = client.translate(
40
+ "سلام، كيف داير؟",
41
+ source="ary_Arab",
42
+ target="ary_Latn",
43
+ )
44
+ print(result.text)
45
+ ```
46
+
47
+ Reverse the source and target for Latin → Arabic. Use `client.languages()` to list supported language and script codes, including `shi_Latn` for Tachelhit and `zgh_Tfng` for Tamazight in Tifinagh.
48
+
49
+ ## CLI
50
+
51
+ ```sh
52
+ tarjamli translate --text 'واش تقدر تصيفط ليا الدوسي؟' --source ary_Arab --target fra_Latn --instructions 'Address one person formally using vous.'
53
+ tarjamli languages
54
+ ```
55
+
56
+ Use `--text -` to read standard input. Add `--json` for the full API response.
57
+
58
+ ## Configuration and results
59
+
60
+ `Tarjamli(api_key=None, timeout=180)` reads `SAWALNI_API_KEY` when no key is passed. Keep keys in server-side code or your terminal environment. The package does not load `.env` files.
61
+
62
+ `translate(text, *, source, target, instructions="", model="tarjamli-mini-v4")` returns a `Translation` with `text`, `source`, `target`, `model`, and `raw` (the full response, including usage when supplied). This release supports the checked Mini model only.
63
+
64
+ Each call sends one request to `POST https://api.sawalni.com/v1/language/translate`. Live requests use your API balance. There are no automatic retries.
65
+
66
+ ## Errors
67
+
68
+ ```python
69
+ from tarjamli import AuthenticationError, RateLimitError, TranslationError
70
+
71
+ try:
72
+ result = client.translate("Hello", source="eng_Latn", target="ary_Arab")
73
+ except AuthenticationError:
74
+ # Check your key and model access.
75
+ raise
76
+ except RateLimitError:
77
+ # Decide when to retry in your application.
78
+ raise
79
+ except TranslationError:
80
+ # Includes incomplete responses, connection errors and unexpected identities.
81
+ raise
82
+ ```
83
+
84
+ Invalid local input raises `ValueError`. Incomplete output raises `IncompleteTranslationError`, a subclass of `TranslationError`. The client checks the returned model and language codes before returning a result.
85
+
86
+ [More examples](https://github.com/omneity-labs/starter-translation) · [API documentation](https://api.sawalni.com/docs) · [MIT license](LICENSE).
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tarjamli"
7
+ version = "0.1.0"
8
+ description = "Python client for Tarjamli translation through Sawalni API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [{name = "Omar Kamali", email = "omar@omneitylabs.com"}]
13
+ keywords = ["translation", "darija", "amazigh", "tarjamli", "sawalni"]
14
+ classifiers = ["Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Topic :: Text Processing :: Linguistic"]
15
+ dependencies = []
16
+
17
+ [project.scripts]
18
+ tarjamli = "tarjamli.cli:main"
19
+
20
+ [tool.setuptools.packages.find]
21
+ where = ["src"]
22
+
23
+ [tool.setuptools.package-data]
24
+ tarjamli = ["languages.json", "py.typed"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://api.sawalni.com"
28
+ Issues = "https://github.com/omneity-labs/starter-translation/issues"
29
+ Examples = "https://github.com/omneity-labs/starter-translation"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """Tarjamli translation client for Sawalni API."""
2
+ from .client import (Tarjamli, Translation, TranslationError, AuthenticationError,
3
+ RateLimitError, IncompleteTranslationError)
4
+ __all__ = ["Tarjamli", "Translation", "TranslationError", "AuthenticationError",
5
+ "RateLimitError", "IncompleteTranslationError"]
@@ -0,0 +1,2 @@
1
+ from .cli import main
2
+ main()
@@ -0,0 +1,26 @@
1
+ import argparse
2
+ import json
3
+ import sys
4
+ from . import Tarjamli, TranslationError
5
+
6
+ def main():
7
+ parser = argparse.ArgumentParser(prog="tarjamli")
8
+ commands = parser.add_subparsers(dest="command", required=True)
9
+ commands.add_parser("languages", help="List language and script codes")
10
+ command = commands.add_parser("translate", help="Translate text; use --text - to read stdin")
11
+ command.add_argument("--text", required=True)
12
+ command.add_argument("--source", required=True)
13
+ command.add_argument("--target", required=True)
14
+ command.add_argument("--instructions", default="")
15
+ command.add_argument("--json", action="store_true", help="Print the full API response")
16
+ args = parser.parse_args()
17
+ if args.command == "languages":
18
+ print(json.dumps(Tarjamli.languages(), ensure_ascii=False, indent=2))
19
+ return
20
+ try:
21
+ text = sys.stdin.read() if args.text == "-" else args.text
22
+ result = Tarjamli().translate(text, source=args.source, target=args.target, instructions=args.instructions)
23
+ print(json.dumps(result.raw, ensure_ascii=False, indent=2) if args.json else result.text)
24
+ except (TranslationError, ValueError) as error:
25
+ print(str(error), file=sys.stderr)
26
+ raise SystemExit(1)
@@ -0,0 +1,95 @@
1
+ """Small standard-library client for Tarjamli Mini v4. Python 3.10+."""
2
+ import json
3
+ import os
4
+ import urllib.error
5
+ import urllib.request
6
+ from importlib.resources import files
7
+
8
+ ENDPOINT = "https://api.sawalni.com/v1/language/translate"
9
+ LANGUAGES = json.loads(files("tarjamli").joinpath("languages.json").read_text())
10
+
11
+ class TranslationError(RuntimeError):
12
+ """A request failed or returned an unusable translation."""
13
+
14
+ class AuthenticationError(TranslationError):
15
+ pass
16
+
17
+ class RateLimitError(TranslationError):
18
+ pass
19
+
20
+ class IncompleteTranslationError(TranslationError):
21
+ pass
22
+
23
+ def make_request(text, source, target, instructions="", *, model="tarjamli-mini-v4"):
24
+ if model not in ("tarjamli-mini-v4",):
25
+ raise ValueError("Unsupported model")
26
+ if not isinstance(text, str) or not text.strip():
27
+ raise ValueError("text must be nonempty")
28
+ if len(text.encode("utf-8")) > 262144:
29
+ raise ValueError("text exceeds 262144 UTF-8 bytes")
30
+ if source not in LANGUAGES or target not in LANGUAGES:
31
+ raise ValueError("Use explicit supported source and target codes from languages.json")
32
+ if not isinstance(instructions, str) or len(instructions.encode("utf-8")) > 32768:
33
+ raise ValueError("instructions must be a string of at most 32768 UTF-8 bytes")
34
+ payload = dict(model=model, text=text, source=source, target=target)
35
+ if instructions.strip():
36
+ payload["instructions"] = instructions
37
+ return payload
38
+
39
+ def translate(text, source, target, instructions="", *, api_key=None, timeout=180, model="tarjamli-mini-v4"):
40
+ payload = make_request(text, source, target, instructions, model=model)
41
+ key = api_key or os.environ.get("SAWALNI_API_KEY")
42
+ if not key:
43
+ raise AuthenticationError("Set SAWALNI_API_KEY in your shell or pass api_key")
44
+ request = urllib.request.Request(ENDPOINT, data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
45
+ headers={"Authorization": "Bearer " + key, "Content-Type": "application/json", "User-Agent": "Sawalni-Tarjamli-Starter/1.0"})
46
+ try:
47
+ with urllib.request.urlopen(request, timeout=timeout) as response:
48
+ body = json.load(response)
49
+ except urllib.error.HTTPError as error:
50
+ error_type = AuthenticationError if error.code in (401, 403) else RateLimitError if error.code == 429 else TranslationError
51
+ raise error_type(f"Translation HTTP {error.code}; check access, input and API status. No automatic retry.") from None
52
+ except (urllib.error.URLError, TimeoutError):
53
+ raise TranslationError("Translation connection failed or timed out. Check before retrying; the request may have run.") from None
54
+ except (ValueError, UnicodeError):
55
+ raise TranslationError("API returned invalid JSON") from None
56
+ data = body.get("data") if isinstance(body, dict) else None
57
+ if not isinstance(data, dict) or not isinstance(data.get("translated"), str) or not data["translated"].strip():
58
+ raise TranslationError("API returned no translation")
59
+ if data.get("complete") is not True:
60
+ raise IncompleteTranslationError("Translation is incomplete; shorten the input before trying again")
61
+ if data.get("source") != source or data.get("target") != target or data.get("model") != payload["model"]:
62
+ raise TranslationError("Unexpected response model or language identity")
63
+ return body
64
+
65
+
66
+ from dataclasses import dataclass
67
+ from typing import Any
68
+
69
+ @dataclass(frozen=True)
70
+ class Translation:
71
+ """Translation text and the full API response, including usage when provided."""
72
+ text: str
73
+ source: str
74
+ target: str
75
+ model: str
76
+ raw: dict[str, Any]
77
+
78
+ class Tarjamli:
79
+ """Synchronous client. Requests are never retried automatically."""
80
+ def __init__(self, api_key: str | None = None, *, timeout: float = 180) -> None:
81
+ if timeout <= 0:
82
+ raise ValueError("timeout must be positive")
83
+ self._api_key = api_key
84
+ self.timeout = timeout
85
+
86
+ @staticmethod
87
+ def languages() -> dict[str, str]:
88
+ """Return a copy of the supported language-code mapping."""
89
+ return dict(LANGUAGES)
90
+
91
+ def translate(self, text: str, *, source: str, target: str, instructions: str = "", model: str = "tarjamli-mini-v4") -> Translation:
92
+ body = translate(text, source, target, instructions, api_key=self._api_key,
93
+ timeout=self.timeout, model=model)
94
+ data = body["data"]
95
+ return Translation(data["translated"], data["source"], data["target"], data["model"], body)
@@ -0,0 +1,56 @@
1
+ {
2
+ "ary_Arab": "Moroccan Arabic",
3
+ "ary_Latn": "Moroccan Arabic written in Latin script",
4
+ "shi_Latn": "Tachelhit written in Latin script",
5
+ "zgh_Tfng": "Standard Moroccan Tamazight written in Tifinagh script",
6
+ "amh_Ethi": "Amharic",
7
+ "arb_Arab": "Modern Standard Arabic",
8
+ "ben_Beng": "Bengali",
9
+ "ces_Latn": "Czech",
10
+ "cmn_Hans": "Simplified Chinese",
11
+ "dan_Latn": "Danish",
12
+ "deu_Latn": "German",
13
+ "ell_Grek": "Greek",
14
+ "eng_Latn": "English",
15
+ "fin_Latn": "Finnish",
16
+ "fra_Latn": "French",
17
+ "guj_Gujr": "Gujarati",
18
+ "hau_Latn": "Hausa",
19
+ "heb_Hebr": "Hebrew",
20
+ "hin_Deva": "Hindi",
21
+ "hun_Latn": "Hungarian",
22
+ "ind_Latn": "Indonesian",
23
+ "ita_Latn": "Italian",
24
+ "jpn_Jpan": "Japanese",
25
+ "kan_Knda": "Kannada",
26
+ "khm_Khmr": "Khmer",
27
+ "kor_Hang": "Korean",
28
+ "lao_Laoo": "Lao",
29
+ "mal_Mlym": "Malayalam",
30
+ "mar_Deva": "Marathi",
31
+ "mya_Mymr": "Burmese",
32
+ "nld_Latn": "Dutch",
33
+ "nob_Latn": "Norwegian Bokmål",
34
+ "npi_Deva": "Nepali",
35
+ "pan_Guru": "Punjabi written in Gurmukhi",
36
+ "pes_Arab": "Persian",
37
+ "pol_Latn": "Polish",
38
+ "por_Latn": "Portuguese",
39
+ "ron_Latn": "Romanian",
40
+ "rus_Cyrl": "Russian",
41
+ "sin_Sinh": "Sinhala",
42
+ "som_Latn": "Somali",
43
+ "spa_Latn": "Spanish",
44
+ "swe_Latn": "Swedish",
45
+ "swh_Latn": "Swahili",
46
+ "tam_Taml": "Tamil",
47
+ "tel_Telu": "Telugu",
48
+ "tha_Thai": "Thai",
49
+ "tur_Latn": "Turkish",
50
+ "ukr_Cyrl": "Ukrainian",
51
+ "urd_Arab": "Urdu",
52
+ "vie_Latn": "Vietnamese",
53
+ "yor_Latn": "Yoruba",
54
+ "zsm_Latn": "Malay",
55
+ "zul_Latn": "Zulu"
56
+ }
File without changes
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: tarjamli
3
+ Version: 0.1.0
4
+ Summary: Python client for Tarjamli translation through Sawalni API
5
+ Author-email: Omar Kamali <omar@omneitylabs.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://api.sawalni.com
8
+ Project-URL: Issues, https://github.com/omneity-labs/starter-translation/issues
9
+ Project-URL: Examples, https://github.com/omneity-labs/starter-translation
10
+ Keywords: translation,darija,amazigh,tarjamli,sawalni
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Text Processing :: Linguistic
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # tarjamli
21
+
22
+ A small Python client for Tarjamli translation through the [Sawalni API](https://api.sawalni.com). Translate text, convert Darija between Arabic and Latin scripts, and give the translation context about tone, gender or address forms.
23
+
24
+ Python 3.10+. No runtime dependencies.
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ pip install tarjamli
30
+ ```
31
+
32
+ Set your Sawalni API key:
33
+
34
+ ```sh
35
+ export SAWALNI_API_KEY="your-key"
36
+ ```
37
+
38
+ ## Translate
39
+
40
+ ```python
41
+ from tarjamli import Tarjamli
42
+
43
+ client = Tarjamli()
44
+ result = client.translate(
45
+ "واش تقدر تصيفط ليا الدوسي؟",
46
+ source="ary_Arab",
47
+ target="fra_Latn",
48
+ instructions="Address one person formally using vous.",
49
+ )
50
+ print(result.text)
51
+ ```
52
+
53
+ Pass your own text. Remove `instructions` for ordinary translation, or change it to supply context. The API field is `instructions`; tone and gender are not separate parameters.
54
+
55
+ ## Convert Darija scripts
56
+
57
+ ```python
58
+ result = client.translate(
59
+ "سلام، كيف داير؟",
60
+ source="ary_Arab",
61
+ target="ary_Latn",
62
+ )
63
+ print(result.text)
64
+ ```
65
+
66
+ Reverse the source and target for Latin → Arabic. Use `client.languages()` to list supported language and script codes, including `shi_Latn` for Tachelhit and `zgh_Tfng` for Tamazight in Tifinagh.
67
+
68
+ ## CLI
69
+
70
+ ```sh
71
+ tarjamli translate --text 'واش تقدر تصيفط ليا الدوسي؟' --source ary_Arab --target fra_Latn --instructions 'Address one person formally using vous.'
72
+ tarjamli languages
73
+ ```
74
+
75
+ Use `--text -` to read standard input. Add `--json` for the full API response.
76
+
77
+ ## Configuration and results
78
+
79
+ `Tarjamli(api_key=None, timeout=180)` reads `SAWALNI_API_KEY` when no key is passed. Keep keys in server-side code or your terminal environment. The package does not load `.env` files.
80
+
81
+ `translate(text, *, source, target, instructions="", model="tarjamli-mini-v4")` returns a `Translation` with `text`, `source`, `target`, `model`, and `raw` (the full response, including usage when supplied). This release supports the checked Mini model only.
82
+
83
+ Each call sends one request to `POST https://api.sawalni.com/v1/language/translate`. Live requests use your API balance. There are no automatic retries.
84
+
85
+ ## Errors
86
+
87
+ ```python
88
+ from tarjamli import AuthenticationError, RateLimitError, TranslationError
89
+
90
+ try:
91
+ result = client.translate("Hello", source="eng_Latn", target="ary_Arab")
92
+ except AuthenticationError:
93
+ # Check your key and model access.
94
+ raise
95
+ except RateLimitError:
96
+ # Decide when to retry in your application.
97
+ raise
98
+ except TranslationError:
99
+ # Includes incomplete responses, connection errors and unexpected identities.
100
+ raise
101
+ ```
102
+
103
+ Invalid local input raises `ValueError`. Incomplete output raises `IncompleteTranslationError`, a subclass of `TranslationError`. The client checks the returned model and language codes before returning a result.
104
+
105
+ [More examples](https://github.com/omneity-labs/starter-translation) · [API documentation](https://api.sawalni.com/docs) · [MIT license](LICENSE).
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/tarjamli/__init__.py
5
+ src/tarjamli/__main__.py
6
+ src/tarjamli/cli.py
7
+ src/tarjamli/client.py
8
+ src/tarjamli/languages.json
9
+ src/tarjamli/py.typed
10
+ src/tarjamli.egg-info/PKG-INFO
11
+ src/tarjamli.egg-info/SOURCES.txt
12
+ src/tarjamli.egg-info/dependency_links.txt
13
+ src/tarjamli.egg-info/entry_points.txt
14
+ src/tarjamli.egg-info/top_level.txt
15
+ tests/test_client.py
16
+ tests/test_public_api.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tarjamli = tarjamli.cli:main
@@ -0,0 +1 @@
1
+ tarjamli
@@ -0,0 +1,40 @@
1
+ import io, json, sys, unittest, urllib.error
2
+ from pathlib import Path
3
+ from unittest.mock import patch
4
+
5
+ from tarjamli.client import translate, make_request, TranslationError
6
+
7
+ def body(**change):
8
+ data = dict(translated="Pouvez-vous m'envoyer le document ?", complete=True, source="eng_Latn", target="fra_Latn", model="tarjamli-mini-v4")
9
+ data.update(change)
10
+ return io.BytesIO(json.dumps(dict(data=data)).encode())
11
+
12
+ class ClientTests(unittest.TestCase):
13
+ def test_wire_request_and_auth(self):
14
+ with patch("urllib.request.urlopen", return_value=body()) as call:
15
+ result = translate("واش؟", "eng_Latn", "fra_Latn", "Use vous.", api_key="test-only")
16
+ request = call.call_args.args[0]
17
+ self.assertEqual(request.full_url, "https://api.sawalni.com/v1/language/translate")
18
+ self.assertEqual(request.get_header("Authorization"), "Bearer test-only")
19
+ payload = json.loads(request.data)
20
+ self.assertEqual(payload["text"], "واش؟")
21
+ self.assertEqual(payload["instructions"], "Use vous.")
22
+ self.assertNotIn("test-only", request.data.decode())
23
+ self.assertTrue(result["data"]["complete"])
24
+ def test_incomplete_and_identity_mismatch(self):
25
+ for changes in [dict(complete=False),dict(target="deu_Latn"),dict(model="other"),dict(translated="")]:
26
+ with self.subTest(changes=changes), patch("urllib.request.urlopen", return_value=body(**changes)):
27
+ with self.assertRaises(TranslationError): translate("hello", "eng_Latn", "fra_Latn", api_key="test-only")
28
+ def test_utf8_limit_and_explicit_language(self):
29
+ for args in [("x", "auto", "fra_Latn"),("é"*131073,"eng_Latn","fra_Latn")]:
30
+ with self.assertRaises(ValueError): make_request(*args)
31
+ with self.assertRaises(ValueError): make_request("x","eng_Latn","fra_Latn","é"*16385)
32
+ def test_no_silent_retry_or_secret_in_error(self):
33
+ error = urllib.error.HTTPError("https://api.sawalni.com",429,"busy",{},None)
34
+ with patch("urllib.request.urlopen", side_effect=error) as call:
35
+ with self.assertRaisesRegex(TranslationError,"HTTP 429") as caught:
36
+ translate("hello","eng_Latn","fra_Latn",api_key="test-only")
37
+ self.assertEqual(call.call_count,1)
38
+ self.assertNotIn("test-only",str(caught.exception))
39
+
40
+ if __name__ == "__main__": unittest.main()
@@ -0,0 +1,44 @@
1
+ import io
2
+ import json
3
+ import unittest
4
+ import urllib.error
5
+ from unittest.mock import patch
6
+ from tarjamli import Tarjamli, TranslationError, AuthenticationError, RateLimitError, IncompleteTranslationError
7
+
8
+ class PublicAPI(unittest.TestCase):
9
+ def response(self, **changes):
10
+ data = dict(translated='Bonjour', source='eng_Latn', target='fra_Latn', model='tarjamli-mini-v4', complete=True)
11
+ data.update(changes)
12
+ return io.BytesIO(json.dumps({'data': data, 'usage': {'tokens': 4}}).encode())
13
+
14
+ def test_public_result_and_environment_key(self):
15
+ with patch.dict('os.environ', {'SAWALNI_API_KEY':'private-test-key'}), patch('urllib.request.urlopen', return_value=self.response()) as call:
16
+ result = Tarjamli(timeout=10).translate('Hello', source='eng_Latn', target='fra_Latn')
17
+ self.assertEqual(result.text, 'Bonjour')
18
+ self.assertEqual(result.raw['usage']['tokens'], 4)
19
+ self.assertEqual(call.call_args.kwargs['timeout'], 10)
20
+
21
+ def test_typed_errors_without_retries(self):
22
+ for status, expected in [(401, AuthenticationError), (403, AuthenticationError), (429, RateLimitError), (500, TranslationError)]:
23
+ with patch('urllib.request.urlopen', side_effect=urllib.error.HTTPError('https://api.sawalni.com', status, '', {}, None)) as call:
24
+ with self.assertRaises(expected):
25
+ Tarjamli('secret').translate('Hello', source='eng_Latn', target='fra_Latn')
26
+ self.assertEqual(call.call_count, 1)
27
+ with patch('urllib.request.urlopen', return_value=self.response(complete=False)):
28
+ with self.assertRaises(IncompleteTranslationError):
29
+ Tarjamli('secret').translate('Hello', source='eng_Latn', target='fra_Latn')
30
+
31
+ def test_language_copy_and_model_guard(self):
32
+ languages = Tarjamli.languages()
33
+ languages.clear()
34
+ self.assertIn('zgh_Tfng', Tarjamli.languages())
35
+ with patch('urllib.request.urlopen') as call:
36
+ with self.assertRaises(ValueError):
37
+ Tarjamli('secret').translate('Hello', source='eng_Latn', target='fra_Latn', model='unknown')
38
+ call.assert_not_called()
39
+
40
+ def test_cli_stdin(self):
41
+ from tarjamli.cli import main
42
+ with patch('sys.argv', ['tarjamli','translate','--text','-','--source','eng_Latn','--target','fra_Latn']), patch('sys.stdin', io.StringIO('Hello')), patch('sys.stdout', new_callable=io.StringIO) as stdout, patch('urllib.request.urlopen', return_value=self.response()), patch.dict('os.environ', {'SAWALNI_API_KEY':'test'}):
43
+ main()
44
+ self.assertEqual(stdout.getvalue(), 'Bonjour\n')