scriptconv 0.0.4a6__tar.gz → 0.0.4a7__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 (95) hide show
  1. {scriptconv-0.0.4a6/scriptconv.egg-info → scriptconv-0.0.4a7}/PKG-INFO +8 -4
  2. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/README.md +7 -3
  3. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/diacritics.py +58 -10
  4. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/version.py +1 -1
  5. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7/scriptconv.egg-info}/PKG-INFO +8 -4
  6. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_diacritics.py +106 -11
  7. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/LICENSE +0 -0
  8. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/pyproject.toml +0 -0
  9. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/requirements.txt +0 -0
  10. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/__init__.py +0 -0
  11. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/__main__.py +0 -0
  12. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/cangjie.py +0 -0
  13. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/conventions.py +0 -0
  14. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/data/__init__.py +0 -0
  15. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/data/cangjie5_tc.tsv.gz +0 -0
  16. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/graph.py +0 -0
  17. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/notation.py +0 -0
  18. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/__init__.py +0 -0
  19. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/__init__.py +0 -0
  20. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/bw2ipa.py +0 -0
  21. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/hangul2ipa.py +0 -0
  22. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/aspiration.csv +0 -0
  23. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/assimilation.csv +0 -0
  24. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/double_coda.csv +0 -0
  25. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/hanja.tsv +0 -0
  26. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/ipa.csv +0 -0
  27. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/neutralization.csv +0 -0
  28. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/tensification.csv +0 -0
  29. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/yale.csv +0 -0
  30. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/__init__.py +0 -0
  31. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/codeswitch.py +0 -0
  32. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/diacritize.py +0 -0
  33. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/dialectal.py +0 -0
  34. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/english_g2p.py +0 -0
  35. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/espeak.py +0 -0
  36. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/frontend.py +0 -0
  37. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/levantine_g2p.py +0 -0
  38. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/normalize.py +0 -0
  39. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/phoneme_inventory.py +0 -0
  40. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/zh_num.py +0 -0
  41. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/__init__.py +0 -0
  42. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/kog2p/LICENSE.md +0 -0
  43. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/kog2p/__init__.py +0 -0
  44. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/kog2p/rulebook.txt +0 -0
  45. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/LICENSE.md +0 -0
  46. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/__init__.py +0 -0
  47. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/buck/__init__.py +0 -0
  48. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/buck/phonetise_buckwalter.py +0 -0
  49. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/buck/symbols.py +0 -0
  50. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/buck/tokenization.py +0 -0
  51. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/num2words.py +0 -0
  52. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/unicode_symbol2label.py +0 -0
  53. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/ar.py +0 -0
  54. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/base.py +0 -0
  55. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/en.py +0 -0
  56. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/enums.py +0 -0
  57. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/eu.py +0 -0
  58. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/fa.py +0 -0
  59. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/gl.py +0 -0
  60. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/he.py +0 -0
  61. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/ja.py +0 -0
  62. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/ko.py +0 -0
  63. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/mul.py +0 -0
  64. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/mwl.py +0 -0
  65. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/o2ipa.py +0 -0
  66. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/pt.py +0 -0
  67. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/registry.py +0 -0
  68. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/shami.py +0 -0
  69. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/vi.py +0 -0
  70. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/phonemizers/zh.py +0 -0
  71. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/py.typed +0 -0
  72. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/readings.py +0 -0
  73. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/scripts.py +0 -0
  74. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv/translit.py +0 -0
  75. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv.egg-info/SOURCES.txt +0 -0
  76. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv.egg-info/dependency_links.txt +0 -0
  77. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv.egg-info/requires.txt +0 -0
  78. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/scriptconv.egg-info/top_level.txt +0 -0
  79. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/setup.cfg +0 -0
  80. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_arpa_stress.py +0 -0
  81. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_cangjie.py +0 -0
  82. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_cli.py +0 -0
  83. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_conventions.py +0 -0
  84. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_diacritics_graph.py +0 -0
  85. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_errors_policy.py +0 -0
  86. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_examples.py +0 -0
  87. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_graph.py +0 -0
  88. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_notation.py +0 -0
  89. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_phonemizers_base.py +0 -0
  90. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_phonemizers_cjk_ar.py +0 -0
  91. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_readings.py +0 -0
  92. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_readings_zh.py +0 -0
  93. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_scripts.py +0 -0
  94. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_scripts_stressonnx_compat.py +0 -0
  95. {scriptconv-0.0.4a6 → scriptconv-0.0.4a7}/tests/test_translit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptconv
3
- Version: 0.0.4a6
3
+ Version: 0.0.4a7
4
4
  Summary: Zero-dependency script & phoneme-notation core — ISO-15924 detection & metadata, IPA↔ARPABET/X-SAMPA/Lexique/Kirshenbaum/Cotovía/RFE, Buckwalter↔Arabic, Hangul→jamo, kana
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/TigreGotico/scriptconv
@@ -373,9 +373,13 @@ Two design points worth knowing:
373
373
  phonemizing; that needs language resources scriptconv doesn't ship. Pass
374
374
  `normalizer=` (a `(text, lang) -> str` callable) to run yours inside the
375
375
  pipeline; without it, text is phonemized as-is.
376
- - **Model-backed engines never download.** ByT5/Charsiu require an explicit
377
- local `model=` path; resolving and caching model files is the caller's
378
- concern.
376
+ - **Large/licensed model-backed engines never download.** ByT5/Charsiu
377
+ require an explicit local `model=` path; resolving and caching those model
378
+ files is the caller's concern. Small, known-good, unencumbered models are
379
+ the exception: the Hebrew phonikud diacritizer auto-provisions its ONNX
380
+ model to a cache dir on first use (`phonikud_model=` still overrides it
381
+ with a path or callable; cache location via `SCRIPTCONV_CACHE`/
382
+ `XDG_CACHE_HOME`).
379
383
 
380
384
  **Pre-G2P disambiguation.** `add_diacritics(text, lang, model=None)` restores
381
385
  information ordinary orthography omits but a G2P needs, before phonemization:
@@ -246,9 +246,13 @@ Two design points worth knowing:
246
246
  phonemizing; that needs language resources scriptconv doesn't ship. Pass
247
247
  `normalizer=` (a `(text, lang) -> str` callable) to run yours inside the
248
248
  pipeline; without it, text is phonemized as-is.
249
- - **Model-backed engines never download.** ByT5/Charsiu require an explicit
250
- local `model=` path; resolving and caching model files is the caller's
251
- concern.
249
+ - **Large/licensed model-backed engines never download.** ByT5/Charsiu
250
+ require an explicit local `model=` path; resolving and caching those model
251
+ files is the caller's concern. Small, known-good, unencumbered models are
252
+ the exception: the Hebrew phonikud diacritizer auto-provisions its ONNX
253
+ model to a cache dir on first use (`phonikud_model=` still overrides it
254
+ with a path or callable; cache location via `SCRIPTCONV_CACHE`/
255
+ `XDG_CACHE_HOME`).
252
256
 
253
257
  **Pre-G2P disambiguation.** `add_diacritics(text, lang, model=None)` restores
254
258
  information ordinary orthography omits but a G2P needs, before phonemization:
@@ -51,6 +51,10 @@ tracks which backend/model produced the marks: overlay backends (phonikud,
51
51
  tashkeel, stressonnx) are strippable; spelling-integral backends (bifonia) are
52
52
  not.
53
53
  """
54
+ import os
55
+ import tempfile
56
+ import urllib.request
57
+ from pathlib import Path
54
58
  from typing import Optional
55
59
 
56
60
  from scriptconv.graph import Edge
@@ -119,20 +123,61 @@ _DEFAULT_DIACRITIZER_MODEL = "rawi-ensemble"
119
123
  _PHONIKUD_CACHE: dict = {} # resolved model path/string -> Phonikud instance
120
124
  _TASHKEEL_CACHE: dict = {} # model name -> text2tashkeel Diacritizer
121
125
 
126
+ # phonikud is a small (~100MB), public, unlicensed-restriction ONNX model, so
127
+ # — unlike the large/licensed models behind mul.py's ByT5/Charsiu backends —
128
+ # scriptconv auto-provisions it: no local path is required unless the caller
129
+ # wants to override the cache (e.g. an air-gapped host).
130
+ _PHONIKUD_URL = "https://huggingface.co/thewh1teagle/phonikud-onnx/resolve/main/phonikud-1.0.int8.onnx"
122
131
 
123
- def _phonikud(phonikud_model):
132
+
133
+ def _default_phonikud_model() -> str:
134
+ """Resolve (downloading and caching on first use if needed) the path to
135
+ the default phonikud ONNX model.
136
+
137
+ Cache directory: ``<base>/scriptconv/phonikud`` where ``<base>`` is
138
+ ``$SCRIPTCONV_CACHE`` if set, else ``$XDG_CACHE_HOME`` (default
139
+ ``~/.cache``) — i.e. ``~/.cache/scriptconv/phonikud`` by default. The
140
+ download is written to a temp file
141
+ in the same directory and atomically moved into place via
142
+ :func:`os.replace`, so a failed or interrupted download never leaves a
143
+ partial file at the destination path.
144
+ """
145
+ base = os.environ.get("SCRIPTCONV_CACHE") or os.environ.get(
146
+ "XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
147
+ cache_dir = Path(base) / "scriptconv" / "phonikud"
148
+ dest = cache_dir / "phonikud-1.0.int8.onnx"
149
+ if not dest.is_file():
150
+ cache_dir.mkdir(parents=True, exist_ok=True)
151
+ fd, tmp_path = tempfile.mkstemp(dir=str(cache_dir), prefix=".phonikud-", suffix=".tmp")
152
+ try:
153
+ with os.fdopen(fd, "wb") as tmp_f, urllib.request.urlopen(_PHONIKUD_URL) as resp:
154
+ while True:
155
+ chunk = resp.read(1024 * 1024)
156
+ if not chunk:
157
+ break
158
+ tmp_f.write(chunk)
159
+ os.replace(tmp_path, dest)
160
+ except BaseException:
161
+ if os.path.exists(tmp_path):
162
+ os.remove(tmp_path)
163
+ raise
164
+ return str(dest)
165
+
166
+
167
+ def _phonikud(phonikud_model=None):
124
168
  """Lazily build (and cache) the phonikud Phonikud instance used for Hebrew.
125
169
 
126
- ``phonikud_model`` is a local path to a phonikud ONNX model, or a
127
- zero-arg callable resolving one lazily; scriptconv never downloads —
128
- the consumer resolves the file. Install with ``pip install
129
- scriptconv[he]`` (or ``pip install phonikud-onnx``)."""
170
+ ``phonikud_model`` is optional: a local path to a phonikud ONNX model, or
171
+ a zero-arg callable resolving one lazily. When omitted, scriptconv
172
+ auto-provisions the small public phonikud model, downloading it once into
173
+ a cache dir (see :func:`_default_phonikud_model`) and reusing it on
174
+ subsequent calls. Pass an explicit path/callable to override — e.g. to
175
+ point at a model already on disk, or on an air-gapped host. Install
176
+ phonikud-onnx with ``pip install scriptconv[he]`` (or ``pip install
177
+ phonikud-onnx``)."""
130
178
  model = phonikud_model() if callable(phonikud_model) else phonikud_model
131
179
  if not model:
132
- raise ValueError(
133
- "Hebrew diacritization needs a local phonikud ONNX model: "
134
- "pass phonikud_model=<path> (scriptconv never downloads "
135
- "models; obtain one from the phonikud-onnx release)")
180
+ model = _default_phonikud_model()
136
181
  if model not in _PHONIKUD_CACHE:
137
182
  try:
138
183
  from phonikud_onnx import Phonikud
@@ -207,7 +252,10 @@ def diacritize(text: str, lang: str = "und", model=None,
207
252
  Four backends, each restoring information ordinary orthography omits but
208
253
  downstream G2P needs:
209
254
 
210
- - Hebrew (``he``) — niqqud via phonikud (``phonikud_model=``).
255
+ - Hebrew (``he``) — niqqud via phonikud. ``phonikud_model=`` is optional:
256
+ omitted, the small public phonikud ONNX model is auto-downloaded and
257
+ cached (``$SCRIPTCONV_CACHE``/``$XDG_CACHE_HOME``); pass a path or
258
+ zero-arg callable to override.
211
259
  - Arabic (``ar``) — tashkeel via text2tashkeel (``[tashkeel]``,
212
260
  ``model=``/``diacritizer_model=``).
213
261
  - East Slavic, Bulgarian/Macedonian/Slovene, Latvian, Armenian, Georgian,
@@ -2,7 +2,7 @@
2
2
  VERSION_MAJOR = 0
3
3
  VERSION_MINOR = 0
4
4
  VERSION_BUILD = 4
5
- VERSION_ALPHA = 6
5
+ VERSION_ALPHA = 7
6
6
  # END_VERSION_BLOCK
7
7
 
8
8
  VERSION_STR = f"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_BUILD}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptconv
3
- Version: 0.0.4a6
3
+ Version: 0.0.4a7
4
4
  Summary: Zero-dependency script & phoneme-notation core — ISO-15924 detection & metadata, IPA↔ARPABET/X-SAMPA/Lexique/Kirshenbaum/Cotovía/RFE, Buckwalter↔Arabic, Hangul→jamo, kana
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/TigreGotico/scriptconv
@@ -373,9 +373,13 @@ Two design points worth knowing:
373
373
  phonemizing; that needs language resources scriptconv doesn't ship. Pass
374
374
  `normalizer=` (a `(text, lang) -> str` callable) to run yours inside the
375
375
  pipeline; without it, text is phonemized as-is.
376
- - **Model-backed engines never download.** ByT5/Charsiu require an explicit
377
- local `model=` path; resolving and caching model files is the caller's
378
- concern.
376
+ - **Large/licensed model-backed engines never download.** ByT5/Charsiu
377
+ require an explicit local `model=` path; resolving and caching those model
378
+ files is the caller's concern. Small, known-good, unencumbered models are
379
+ the exception: the Hebrew phonikud diacritizer auto-provisions its ONNX
380
+ model to a cache dir on first use (`phonikud_model=` still overrides it
381
+ with a path or callable; cache location via `SCRIPTCONV_CACHE`/
382
+ `XDG_CACHE_HOME`).
379
383
 
380
384
  **Pre-G2P disambiguation.** `add_diacritics(text, lang, model=None)` restores
381
385
  information ordinary orthography omits but a G2P needs, before phonemization:
@@ -3,6 +3,8 @@
3
3
  No optional backend is required — strip_diacritics/_overlay_marks/_supports_strip
4
4
  never load a phonemizer model.
5
5
  """
6
+ import os
7
+
6
8
  import pytest
7
9
 
8
10
  from scriptconv.diacritics import (
@@ -90,27 +92,120 @@ def test_overlay_marks_none_for_native_orthography():
90
92
  assert _overlay_marks("en") is None
91
93
 
92
94
 
93
- class TestHebrewDiacritizerRequiresLocalModel:
94
- def test_hebrew_diacritizer_requires_local_model(self):
95
- from scriptconv.diacritics import diacritize
96
- with pytest.raises(ValueError) as ctx:
97
- diacritize("שלום", "he")
98
- assert "phonikud_model" in str(ctx.value)
95
+ class TestHebrewDiacritizerAutoProvisions:
96
+ def test_no_model_given_auto_provisions(self, monkeypatch):
97
+ from scriptconv import diacritics
98
+
99
+ monkeypatch.setitem(diacritics._PHONIKUD_CACHE, "/sentinel/path.onnx", object())
100
+ monkeypatch.setattr(diacritics, "_default_phonikud_model",
101
+ lambda: "/sentinel/path.onnx")
102
+ result = diacritics._phonikud(None)
103
+ assert result is diacritics._PHONIKUD_CACHE["/sentinel/path.onnx"]
104
+
105
+ def test_explicit_path_bypasses_provisioner(self, monkeypatch):
106
+ from scriptconv import diacritics
107
+
108
+ calls = []
109
+ monkeypatch.setattr(diacritics, "_default_phonikud_model",
110
+ lambda: calls.append(1) or "/should/not/be/used.onnx")
111
+
112
+ class FakePhonikud:
113
+ def __init__(self, model):
114
+ self.model = model
115
+
116
+ monkeypatch.setattr(diacritics, "_PHONIKUD_CACHE", {})
117
+ import sys
118
+ import types
119
+ fake_mod = types.ModuleType("phonikud_onnx")
120
+ fake_mod.Phonikud = FakePhonikud
121
+ monkeypatch.setitem(sys.modules, "phonikud_onnx", fake_mod)
122
+
123
+ result = diacritics._phonikud("/explicit/path.onnx")
124
+ assert result.model == "/explicit/path.onnx"
125
+ assert calls == [] # provisioner never invoked
99
126
 
100
127
 
101
128
  class TestPhonikudModelResolver:
102
- def test_callable_resolver_invoked_lazily(self):
103
- from scriptconv.diacritics import diacritize
129
+ def test_callable_resolver_invoked_lazily(self, monkeypatch):
130
+ from scriptconv import diacritics
131
+
104
132
  calls = []
105
133
 
106
134
  def resolver():
107
135
  calls.append(1)
108
- return "" # resolves to nothing -> still the explicit ValueError
136
+ return "/from/callable.onnx"
137
+
138
+ class FakePhonikud:
139
+ def __init__(self, model):
140
+ self.model = model
141
+
142
+ monkeypatch.setattr(diacritics, "_PHONIKUD_CACHE", {})
143
+ import sys
144
+ import types
145
+ fake_mod = types.ModuleType("phonikud_onnx")
146
+ fake_mod.Phonikud = FakePhonikud
147
+ monkeypatch.setitem(sys.modules, "phonikud_onnx", fake_mod)
109
148
 
110
149
  assert calls == [] # not resolved at construction
111
- with pytest.raises(ValueError):
112
- diacritize("שלום", "he", phonikud_model=resolver)
150
+ result = diacritics._phonikud(resolver)
113
151
  assert calls == [1]
152
+ assert result.model == "/from/callable.onnx"
153
+
154
+
155
+ class TestDefaultPhonikudModelProvisioning:
156
+ def test_downloads_to_cache_dir_and_returns_path(self, monkeypatch, tmp_path):
157
+ from scriptconv import diacritics
158
+
159
+ monkeypatch.setenv("SCRIPTCONV_CACHE", str(tmp_path))
160
+ calls = []
161
+
162
+ class FakeResponse:
163
+ def __enter__(self):
164
+ return self
165
+
166
+ def __exit__(self, *a):
167
+ return False
168
+
169
+ def read(self, n):
170
+ if calls and calls[-1] == "read-done":
171
+ return b""
172
+ calls.append("read-done")
173
+ return b"dummy-onnx-bytes"
174
+
175
+ def fake_urlopen(url):
176
+ calls.append(("urlopen", url))
177
+ return FakeResponse()
178
+
179
+ monkeypatch.setattr(diacritics.urllib.request, "urlopen", fake_urlopen)
180
+
181
+ path = diacritics._default_phonikud_model()
182
+ assert path == str(tmp_path / "scriptconv" / "phonikud" / "phonikud-1.0.int8.onnx")
183
+ assert os.path.isfile(path)
184
+
185
+ # idempotent: second call must not re-download
186
+ urlopen_calls_before = sum(1 for c in calls if isinstance(c, tuple))
187
+ diacritics._default_phonikud_model()
188
+ urlopen_calls_after = sum(1 for c in calls if isinstance(c, tuple))
189
+ assert urlopen_calls_before == urlopen_calls_after == 1
190
+
191
+ def test_failed_download_leaves_no_partial_file(self, monkeypatch, tmp_path):
192
+ from scriptconv import diacritics
193
+
194
+ monkeypatch.setenv("SCRIPTCONV_CACHE", str(tmp_path))
195
+
196
+ def failing_urlopen(url):
197
+ raise OSError("network unavailable")
198
+
199
+ monkeypatch.setattr(diacritics.urllib.request, "urlopen", failing_urlopen)
200
+
201
+ with pytest.raises(OSError):
202
+ diacritics._default_phonikud_model()
203
+
204
+ dest = tmp_path / "scriptconv" / "phonikud" / "phonikud-1.0.int8.onnx"
205
+ assert not dest.exists()
206
+ cache_dir = tmp_path / "scriptconv" / "phonikud"
207
+ if cache_dir.exists():
208
+ assert list(cache_dir.iterdir()) == []
114
209
 
115
210
 
116
211
  class TestEuropeanPortugueseSenseDiacritics:
File without changes
File without changes