Skip to content

Commit 6c5d53b

Browse files
committed
pybricksdev: use explicit text encoding for text files
Ensure that text files are opened with UTF-8 encoding by default to avoid issues with different default encodings. This also requires updating some dependencies that also had issues with text encoding. PYTHONWARNDEFAULTENCODING is set for tests to try to catch any future issues with text encoding. And PYTHONDEVMODE is set for good measure.
1 parent 0142fb1 commit 6c5d53b

File tree

11 files changed

+172
-53
lines changed

11 files changed

+172
-53
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ jobs:
3838
- run: poetry install --only=main --only=test
3939
- run: poetry run pytest
4040
if: matrix.python-version != '3.10'
41+
env:
42+
PYTHONDEVMODE: 1
43+
PYTHONWARNDEFAULTENCODING: 1
4144
- run: poetry run coverage run
4245
if: matrix.python-version == '3.10'
4346
- run: poetry run coverage xml

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
### Fixed
1313
- Fixed calling `PybricksHub.write()` methods.
14+
- Fixed using default text encoding when opening text files.
1415

1516
### Changed
1617
- Downloading programs without starting them can now be done by

poetry.lock

Lines changed: 122 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pybricksdev/cli/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ def _get_script_path(file: TextIO) -> ContextManager[PathLike]:
6868
@contextlib.contextmanager
6969
def temp_context():
7070
try:
71-
with NamedTemporaryFile(suffix=".py", delete=False) as temp:
71+
with NamedTemporaryFile(
72+
suffix=".py", delete=False, encoding="utf-8"
73+
) as temp:
7274
temp.write(file.buffer.read())
7375

7476
yield temp.name
@@ -98,7 +100,7 @@ def add_parser(self, subparsers: argparse._SubParsersAction):
98100
"file",
99101
metavar="<file>",
100102
help="path to a MicroPython script or `-` for stdin",
101-
type=argparse.FileType(),
103+
type=argparse.FileType(encoding="utf-8"),
102104
)
103105
parser.add_argument(
104106
"--abi",
@@ -135,7 +137,7 @@ def add_parser(self, subparsers: argparse._SubParsersAction):
135137
"file",
136138
metavar="<file>",
137139
help="path to a MicroPython script or `-` for stdin",
138-
type=argparse.FileType(),
140+
type=argparse.FileType(encoding="utf-8"),
139141
)
140142
parser.add_argument(
141143
"-n",

pybricksdev/cli/flash.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ async def download_and_run(client: BleakClient, script: str, abi: int) -> None:
108108
script: The script to be compiled and run.
109109
abi: The MPY ABI version.
110110
"""
111-
with NamedTemporaryFile("w", suffix=".py") as temp:
111+
with NamedTemporaryFile("w", suffix=".py", encoding="utf-8") as temp:
112112
temp.write(script)
113113

114114
# file has to be closed so mpy-cross can open it

pybricksdev/compile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ async def compile_file(
5555
"""
5656

5757
# Get version info
58-
with open(os.path.join(proj_dir, proj_path), "r") as f:
58+
with open(os.path.join(proj_dir, proj_path), "r", encoding="utf-8") as f:
5959
loop = asyncio.get_running_loop()
6060
script = f.read()
6161

@@ -188,7 +188,7 @@ def save_script(py_string):
188188
py_path = os.path.join(BUILD_DIR, TMP_PY_SCRIPT)
189189

190190
# Write Python command to a file.
191-
with open(py_path, "w") as f:
191+
with open(py_path, "w", encoding="utf-8") as f:
192192
f.write(py_string)
193193
f.write("\n")
194194

pybricksdev/connections/pybricks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def _line_handler(self, line: bytes) -> None:
194194
os.makedirs(dir_path)
195195

196196
logger.info("Saving log to {0}.".format(full_path))
197-
self.log_file = open(full_path, "w")
197+
self.log_file = open(full_path, "w", encoding="utf-8")
198198
return
199199

200200
# The line tells us to close a log file, so do it.

pyproject.toml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,16 @@ include = [
2828
pybricksdev = 'pybricksdev.cli:main'
2929

3030
[tool.poetry.dependencies]
31-
argcomplete = ">=1.11.1"
31+
argcomplete = ">=3.6.2"
3232
bleak = ">=0.22.0"
33-
mpy-cross-v5 = ">=1.0.0"
33+
mpy-cross-v5 = ">=1.1.0"
3434
python = ">=3.10,<3.14"
3535
tqdm = ">=4.62.3"
3636
pyusb = ">=1.0.2"
3737
semver = ">=2.13.0"
3838
appdirs = ">=1.4.4"
3939
prompt-toolkit = ">=3.0.18"
40-
mpy-cross-v6 = ">=1.0.0"
40+
mpy-cross-v6 = ">=1.1.0"
4141
packaging = ">=22"
4242
typing-extensions = ">=4.3.0"
4343
reactivex = ">=4.0.4"
@@ -74,6 +74,11 @@ known_third_party = ["usb"]
7474

7575
[tool.pytest.ini_options]
7676
asyncio_mode = "strict"
77+
filterwarnings = [
78+
"error",
79+
# Known issue with reactivex package
80+
"ignore::DeprecationWarning:reactivex.internal",
81+
]
7782

7883
[tool.coverage.run]
7984
branch = true

tests/connections/test_pybricks.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ async def test_download_modern_protocol(self):
3737
with contextlib.ExitStack() as stack:
3838
# Create and manage temporary file
3939
temp = stack.enter_context(
40-
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
40+
tempfile.NamedTemporaryFile(
41+
suffix=".py", mode="w+", delete=False, encoding="utf-8"
42+
)
4143
)
4244
temp.write("print('test')")
4345
temp_path = temp.name
@@ -62,7 +64,9 @@ async def test_download_legacy_firmware(self):
6264
with contextlib.ExitStack() as stack:
6365
# Create and manage temporary file
6466
temp = stack.enter_context(
65-
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
67+
tempfile.NamedTemporaryFile(
68+
suffix=".py", mode="w+", delete=False, encoding="utf-8"
69+
)
6670
)
6771
temp.write("print('test')")
6872
temp_path = temp.name
@@ -86,7 +90,9 @@ async def test_download_unsupported_capabilities(self):
8690
with contextlib.ExitStack() as stack:
8791
# Create and manage temporary file
8892
temp = stack.enter_context(
89-
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
93+
tempfile.NamedTemporaryFile(
94+
suffix=".py", mode="w+", delete=False, encoding="utf-8"
95+
)
9096
)
9197
temp.write("print('test')")
9298
temp_path = temp.name
@@ -114,7 +120,9 @@ async def test_download_compile_error(self):
114120
with contextlib.ExitStack() as stack:
115121
# Create and manage temporary file
116122
temp = stack.enter_context(
117-
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
123+
tempfile.NamedTemporaryFile(
124+
suffix=".py", mode="w+", delete=False, encoding="utf-8"
125+
)
118126
)
119127
temp.write("print('test' # Missing closing parenthesis")
120128
temp_path = temp.name
@@ -157,7 +165,9 @@ async def test_run_modern_protocol(self):
157165
with contextlib.ExitStack() as stack:
158166
# Create and manage temporary file
159167
temp = stack.enter_context(
160-
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
168+
tempfile.NamedTemporaryFile(
169+
suffix=".py", mode="w+", delete=False, encoding="utf-8"
170+
)
161171
)
162172
temp.write("print('test')")
163173
temp_path = temp.name

tests/test_cli.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ async def test_download_ble(self):
7171
with contextlib.ExitStack() as stack:
7272
# Create and manage temporary file
7373
temp = stack.enter_context(
74-
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
74+
tempfile.NamedTemporaryFile(
75+
suffix=".py", mode="w+", delete=False, encoding="utf-8"
76+
)
7577
)
7678
temp.write("print('test')")
7779
temp_path = temp.name
@@ -80,7 +82,7 @@ async def test_download_ble(self):
8082
# Create args
8183
args = argparse.Namespace(
8284
conntype="ble",
83-
file=open(temp_path, "r"),
85+
file=open(temp_path, "r", encoding="utf-8"),
8486
name="MyHub",
8587
start=False,
8688
wait=False,
@@ -118,7 +120,9 @@ async def test_download_usb(self):
118120
with contextlib.ExitStack() as stack:
119121
# Create and manage temporary file
120122
temp = stack.enter_context(
121-
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
123+
tempfile.NamedTemporaryFile(
124+
suffix=".py", mode="w+", delete=False, encoding="utf-8"
125+
)
122126
)
123127
temp.write("print('test')")
124128
temp_path = temp.name
@@ -127,7 +131,7 @@ async def test_download_usb(self):
127131
# Create args
128132
args = argparse.Namespace(
129133
conntype="usb",
130-
file=open(temp_path, "r"),
134+
file=open(temp_path, "r", encoding="utf-8"),
131135
name=None,
132136
start=False,
133137
wait=False,
@@ -208,7 +212,9 @@ async def test_download_connection_error(self):
208212
with contextlib.ExitStack() as stack:
209213
# Create and manage temporary file
210214
temp = stack.enter_context(
211-
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
215+
tempfile.NamedTemporaryFile(
216+
suffix=".py", mode="w+", delete=False, encoding="utf-8"
217+
)
212218
)
213219
temp.write("print('test')")
214220
temp_path = temp.name
@@ -217,7 +223,7 @@ async def test_download_connection_error(self):
217223
# Create args
218224
args = argparse.Namespace(
219225
conntype="ble",
220-
file=open(temp_path, "r"),
226+
file=open(temp_path, "r", encoding="utf-8"),
221227
name="MyHub",
222228
start=False,
223229
wait=False,

0 commit comments

Comments
 (0)