From 74a1d12b1a6f92bef211edc6b7250f9095f26255 Mon Sep 17 00:00:00 2001 From: Clive Date: Thu, 14 May 2026 16:36:05 +0100 Subject: [PATCH 1/3] docs: document open()/ASCII default gotcha in Indigo's Python MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IndigoPluginHost3 runs Python with a locale where open() defaults to ASCII encoding, not UTF-8. Any code that does open(path).read() on a file containing common UTF-8 characters (em-dash, arrows, currency symbols, accented letters) crashes with: UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 ... This bites scripts that exec() another script to reuse functions, plugins reading bundled JSON/text resources, and any code loading UTF-8 user content. Add a subsection under "Python 3 Issues" in troubleshooting/common-issues.md covering symptom, cause, common byte sequences seen, and the fix: always pass encoding="utf-8" to open() and Path.read_text(). Confirmed 14-May-2026 on Indigo 2025.2 / Python 3.13.9 — four trigger scripts using the exec(open(controller_path).read()) pattern all failed at byte 134 (em-dash in the controller's Description header). Co-Authored-By: Claude Opus 4.7 --- .../troubleshooting/common-issues.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/docs/plugin-dev/troubleshooting/common-issues.md b/docs/plugin-dev/troubleshooting/common-issues.md index c1c77fa..95570f4 100644 --- a/docs/plugin-dev/troubleshooting/common-issues.md +++ b/docs/plugin-dev/troubleshooting/common-issues.md @@ -447,6 +447,55 @@ See [Python 3 Migration Guide](../../reference/Python3-Migration-Guide.md) for c indigo.kStateImageSel.NoImage ``` +### `UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2` + +**Symptom:** +``` +UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position N: ordinal not in range(128) +``` +Triggered by code like: +```python +exec(open(SOME_PATH).read()) +# or +text = open(some_path).read() +``` + +**Cause:** `IndigoPluginHost3` runs Python with a locale that makes the built-in `open()` default to **ASCII** encoding rather than UTF-8. Any source file or text file containing common non-ASCII characters fails to decode: + +| Byte sequence | Character | Where it appears | +|---------------|-----------|------------------| +| `0xe2 0x80 0x94` | `—` (em-dash) | comments, docstrings, log messages | +| `0xe2 0x86 0x92` | `→` (arrow) | log messages (`Hall Lamp → blue`) | +| `0xc2 0xb0` | `°` | temperature strings | +| `0xc2 0xa3` | `£` | currency in error messages | + +This bites: +- Scripts that `exec()` another script to reuse its functions (the "core-only" / shared-module pattern used by many automation setups) +- Plugins reading bundled `.json`, `.txt`, or `.py` resource files with bare `open()` +- Any code that loads UTF-8 user content (MQTT payloads written to disk, scraped web data, etc.) + +**Fix:** always pass `encoding="utf-8"` explicitly to `open()` when reading text: +```python +# WRONG — relies on Indigo's locale default (ASCII) +with open(path) as f: + contents = f.read() + +# RIGHT — explicit UTF-8 +with open(path, encoding="utf-8") as f: + contents = f.read() +``` + +For the `exec()` pattern (e.g. when one script reuses functions from another): +```python +CONTROLLER_PATH = "/Library/Application Support/Perceptive Automation/Python Scripts/Some_Library.py" +with open(CONTROLLER_PATH, encoding="utf-8") as _f: + exec(_f.read()) +``` + +Indigo's own logs, plugin sources, MQTT payloads, and JSON configs are all UTF-8 in practice. Defaulting to `encoding="utf-8"` everywhere is the safe rule. + +**Note:** `Path.read_text()` has the same default-encoding issue — pass `encoding="utf-8"` there too if you're using `pathlib`. + ## HTTP Responder Issues ### 404 Errors From e012ed198c02f0a37638a4b11657960fe72d15bf Mon Sep 17 00:00:00 2001 From: Clive Date: Thu, 14 May 2026 16:42:10 +0100 Subject: [PATCH 2/3] docs: add companion exec()/globals() gotcha in same section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "exec a shared library" pattern hits a second non-obvious failure mode under Indigo, separate from the UTF-8 default: NameError: name 'log' is not defined after a successful exec(). Cause: Indigo runs embedded trigger/action scripts inside a function wrapper, so globals() != locals() at the exec() call site. Per Python's documented semantics, exec() in that case puts definitions into locals() only — they never reach the module global namespace where subsequent code looks for them. Fix: pass globals() explicitly to exec(): exec(_f.read(), globals()) The two gotchas (ASCII default open() + locals-bound exec()) are companions — the exec-a-shared-library pattern needs both to work reliably under Indigo. Documented together in the same Python 3 Issues subsection so anyone hitting one finds the other. Confirmed 14-May-2026 on Indigo 2025.2 / Python 3.13.9 — same garage door controller refactor that exposed the UTF-8 issue then hit NameError on every function call once encoding was fixed. Co-Authored-By: Claude Opus 4.7 --- .../troubleshooting/common-issues.md | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/docs/plugin-dev/troubleshooting/common-issues.md b/docs/plugin-dev/troubleshooting/common-issues.md index 95570f4..e48b568 100644 --- a/docs/plugin-dev/troubleshooting/common-issues.md +++ b/docs/plugin-dev/troubleshooting/common-issues.md @@ -485,17 +485,38 @@ with open(path, encoding="utf-8") as f: contents = f.read() ``` -For the `exec()` pattern (e.g. when one script reuses functions from another): -```python -CONTROLLER_PATH = "/Library/Application Support/Perceptive Automation/Python Scripts/Some_Library.py" -with open(CONTROLLER_PATH, encoding="utf-8") as _f: - exec(_f.read()) -``` +For the `exec()` pattern (e.g. when one script reuses functions from another), pass `globals()` explicitly as well — see the next section. Indigo's own logs, plugin sources, MQTT payloads, and JSON configs are all UTF-8 in practice. Defaulting to `encoding="utf-8"` everywhere is the safe rule. **Note:** `Path.read_text()` has the same default-encoding issue — pass `encoding="utf-8"` there too if you're using `pathlib`. +### `NameError: name 'log' is not defined` after `exec()`-ing a shared library + +**Symptom:** A trigger or action script `exec()`s another script to reuse its functions (the "core-only" / shared-library pattern), but every call to a function from that library raises: +``` +NameError: name '' is not defined +``` +The exec call itself succeeds with no error. + +**Cause:** Indigo runs embedded trigger/action scripts **inside a function wrapper**, not at true module level. At the point of your `exec()` call, `globals()` and `locals()` are *different* dicts. Python's `exec()` semantics in that case put any new definitions into `locals()` only — i.e. the wrapper function's local scope. The library's functions are defined, but they never reach the global namespace where the subsequent code looks them up. + +**Fix:** pass `globals()` to `exec()` explicitly so definitions land in the module global namespace: +```python +LIBRARY_PATH = "/Library/Application Support/Perceptive Automation/Python Scripts/Some_Library.py" + +with open(LIBRARY_PATH, encoding="utf-8") as _f: + exec(_f.read(), globals()) # ← globals() makes the functions visible below + +# Now functions defined in Some_Library.py resolve correctly: +log("Library loaded") +do_something() +``` + +Without `globals()`, `exec()` runs as if inside a class body (per Python's documented behaviour when globals and locals are different) and `def` statements bind names locally. + +**Why this is Indigo-specific:** a normal Python script run from the command line has `globals() is locals()` at module level, so bare `exec(code)` works as expected. Indigo's plugin host wraps embedded scripts, breaking that assumption. The pairing of this with the UTF-8 default (above) means the "exec a shared library" pattern needs *both* `encoding="utf-8"` AND `globals()` to work reliably under Indigo. + ## HTTP Responder Issues ### 404 Errors From ed91854f3c33938e975a500343cf9cb5dfbbd509 Mon Sep 17 00:00:00 2001 From: Clive Date: Thu, 14 May 2026 16:45:26 +0100 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20address=20CodeRabbit=20MD040=20?= =?UTF-8?q?=E2=80=94=20add=20language=20tags=20to=20error=20fences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit flagged two bare ``` fences (the UnicodeDecodeError and NameError symptom blocks) as triggering markdownlint MD040. Add `text` language tags to both so they're treated as plain text and the linter warning clears. Co-Authored-By: Claude Opus 4.7 --- docs/plugin-dev/troubleshooting/common-issues.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugin-dev/troubleshooting/common-issues.md b/docs/plugin-dev/troubleshooting/common-issues.md index e48b568..bad0ecf 100644 --- a/docs/plugin-dev/troubleshooting/common-issues.md +++ b/docs/plugin-dev/troubleshooting/common-issues.md @@ -450,7 +450,7 @@ See [Python 3 Migration Guide](../../reference/Python3-Migration-Guide.md) for c ### `UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2` **Symptom:** -``` +```text UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position N: ordinal not in range(128) ``` Triggered by code like: @@ -494,7 +494,7 @@ Indigo's own logs, plugin sources, MQTT payloads, and JSON configs are all UTF-8 ### `NameError: name 'log' is not defined` after `exec()`-ing a shared library **Symptom:** A trigger or action script `exec()`s another script to reuse its functions (the "core-only" / shared-library pattern), but every call to a function from that library raises: -``` +```text NameError: name '' is not defined ``` The exec call itself succeeds with no error.