Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
- run: poetry install --only=main --only=test
- run: poetry run pytest
if: matrix.python-version != '3.10'
env:
PYTHONDEVMODE: 1
PYTHONWARNDEFAULTENCODING: 1
- run: poetry run coverage run
if: matrix.python-version == '3.10'
- run: poetry run coverage xml
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed
- Fixed calling `PybricksHub.write()` methods.
- Fixed using default text encoding when opening text files.

### Changed
- Downloading programs without starting them can now be done by
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018-2023 The Pybricks Authors
Copyright (c) 2018-2025 The Pybricks Authors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
8 changes: 4 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@

# -- Project information -----------------------------------------------------

project = _pyproject["tool"]["poetry"]["name"]
copyright = "2021, The Pybricks Authors"
author = _pyproject["tool"]["poetry"]["authors"][0]
release = f"v{_pyproject['tool']['poetry']['version']}"
project = _pyproject["project"]["name"]
copyright = "2021-2025, The Pybricks Authors"
author = _pyproject["project"]["authors"][0]["name"]
release = f"v{_pyproject['project']['version']}"
version = re.match(r"(v\d+\.\d+)", release)[0]


Expand Down
156 changes: 125 additions & 31 deletions poetry.lock

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions pybricksdev/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ def _get_script_path(file: TextIO) -> ContextManager[PathLike]:
@contextlib.contextmanager
def temp_context():
try:
with NamedTemporaryFile(suffix=".py", delete=False) as temp:
with NamedTemporaryFile(
suffix=".py", delete=False, encoding="utf-8"
) as temp:
temp.write(file.buffer.read())

yield temp.name
Expand Down Expand Up @@ -98,7 +100,7 @@ def add_parser(self, subparsers: argparse._SubParsersAction):
"file",
metavar="<file>",
help="path to a MicroPython script or `-` for stdin",
type=argparse.FileType(),
type=argparse.FileType(encoding="utf-8"),
)
parser.add_argument(
"--abi",
Expand Down Expand Up @@ -135,7 +137,7 @@ def add_parser(self, subparsers: argparse._SubParsersAction):
"file",
metavar="<file>",
help="path to a MicroPython script or `-` for stdin",
type=argparse.FileType(),
type=argparse.FileType(encoding="utf-8"),
)
parser.add_argument(
"-n",
Expand Down
2 changes: 1 addition & 1 deletion pybricksdev/cli/flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async def download_and_run(client: BleakClient, script: str, abi: int) -> None:
script: The script to be compiled and run.
abi: The MPY ABI version.
"""
with NamedTemporaryFile("w", suffix=".py") as temp:
with NamedTemporaryFile("w", suffix=".py", encoding="utf-8") as temp:
temp.write(script)

# file has to be closed so mpy-cross can open it
Expand Down
4 changes: 2 additions & 2 deletions pybricksdev/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async def compile_file(
"""

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

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

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

Expand Down
2 changes: 1 addition & 1 deletion pybricksdev/connections/pybricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def _line_handler(self, line: bytes) -> None:
os.makedirs(dir_path)

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

# The line tells us to close a log file, so do it.
Expand Down
52 changes: 31 additions & 21 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,48 +1,53 @@
[tool.poetry]
[project]
name = "pybricksdev"
version = "1.2.0"
description = "Pybricks developer tools"
authors = ["The Pybricks Authors <[email protected]>"]
maintainers = ["Laurens Valk <[email protected]>", "David Lechner <[email protected]>" ]
license = "MIT"
authors = [{ name = "The Pybricks Authors", email = "[email protected]" }]
maintainers = [
{ name = "Laurens Valk", email = "[email protected]" },
{ name = "David Lechner", email = "[email protected]" }
]
license = { text = "MIT" }
readme = "README.md"
homepage = "https://pybricks.com"
repository = "https://github.com/pybricks/pybricksdev"
documentation = "https://docs.pybricks.com/projects/pybricksdev"
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Natural Language :: English",
"Operating System :: OS Independent",
]
include = [
"AUTHORS.md"
]
requires-python = ">=3.10"
dynamic = ["dependencies"]

[tool.poetry.urls]
[project.urls]
homepage = "https://pybricks.com"
repository = "https://github.com/pybricks/pybricksdev"
documentation = "https://docs.pybricks.com/projects/pybricksdev"
"Changelog" = "https://github.com/pybricks/pybricksdev/blob/master/CHANGELOG.md"
"Support" = "https://github.com/pybricks/support/discussions"
"Issues" = "https://github.com/pybricks/support/issues"

[tool.poetry.scripts]
[project.scripts]
pybricksdev = 'pybricksdev.cli:main'

[tool.poetry]
include = [
"AUTHORS.md"
]

[tool.poetry.dependencies]
argcomplete = ">=1.11.1"
argcomplete = ">=3.6.2"
bleak = ">=0.22.0"
mpy-cross-v5 = ">=1.0.0"
python = ">=3.10,<3.14"
mpy-cross-v5 = ">=1.1.0"
tqdm = ">=4.62.3"
pyusb = ">=1.0.2"
semver = ">=2.13.0"
appdirs = ">=1.4.4"
prompt-toolkit = ">=3.0.18"
mpy-cross-v6 = ">=1.0.0"
mpy-cross-v6 = ">=1.1.0"
packaging = ">=22"
typing-extensions = ">=4.3.0"
reactivex = ">=4.0.4"
reactivex = {version = ">=4.0.4", python = "<4"}
hidapi = ">=0.14.0"
pybricks = {version = ">=3", allow-prereleases = true}
pybricks = {version = ">=3", allow-prereleases = true, python = "<4"}

[tool.poetry.group.lint.dependencies]
black = ">=23,<25"
Expand All @@ -61,8 +66,8 @@ pytest = ">=7.3.1"
pytest-asyncio = ">=0.21.0"

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.black]
target-version = ['py38']
Expand All @@ -74,6 +79,11 @@ known_third_party = ["usb"]

[tool.pytest.ini_options]
asyncio_mode = "strict"
filterwarnings = [
"error",
# Known issue with reactivex package
"ignore::DeprecationWarning:reactivex.internal",
]

[tool.coverage.run]
branch = true
Expand Down
20 changes: 15 additions & 5 deletions tests/connections/test_pybricks.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ async def test_download_modern_protocol(self):
with contextlib.ExitStack() as stack:
# Create and manage temporary file
temp = stack.enter_context(
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
tempfile.NamedTemporaryFile(
suffix=".py", mode="w+", delete=False, encoding="utf-8"
)
)
temp.write("print('test')")
temp_path = temp.name
Expand All @@ -62,7 +64,9 @@ async def test_download_legacy_firmware(self):
with contextlib.ExitStack() as stack:
# Create and manage temporary file
temp = stack.enter_context(
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
tempfile.NamedTemporaryFile(
suffix=".py", mode="w+", delete=False, encoding="utf-8"
)
)
temp.write("print('test')")
temp_path = temp.name
Expand All @@ -86,7 +90,9 @@ async def test_download_unsupported_capabilities(self):
with contextlib.ExitStack() as stack:
# Create and manage temporary file
temp = stack.enter_context(
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
tempfile.NamedTemporaryFile(
suffix=".py", mode="w+", delete=False, encoding="utf-8"
)
)
temp.write("print('test')")
temp_path = temp.name
Expand Down Expand Up @@ -114,7 +120,9 @@ async def test_download_compile_error(self):
with contextlib.ExitStack() as stack:
# Create and manage temporary file
temp = stack.enter_context(
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
tempfile.NamedTemporaryFile(
suffix=".py", mode="w+", delete=False, encoding="utf-8"
)
)
temp.write("print('test' # Missing closing parenthesis")
temp_path = temp.name
Expand Down Expand Up @@ -157,7 +165,9 @@ async def test_run_modern_protocol(self):
with contextlib.ExitStack() as stack:
# Create and manage temporary file
temp = stack.enter_context(
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
tempfile.NamedTemporaryFile(
suffix=".py", mode="w+", delete=False, encoding="utf-8"
)
)
temp.write("print('test')")
temp_path = temp.name
Expand Down
18 changes: 12 additions & 6 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ async def test_download_ble(self):
with contextlib.ExitStack() as stack:
# Create and manage temporary file
temp = stack.enter_context(
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
tempfile.NamedTemporaryFile(
suffix=".py", mode="w+", delete=False, encoding="utf-8"
)
)
temp.write("print('test')")
temp_path = temp.name
Expand All @@ -80,7 +82,7 @@ async def test_download_ble(self):
# Create args
args = argparse.Namespace(
conntype="ble",
file=open(temp_path, "r"),
file=stack.enter_context(open(temp_path, "r", encoding="utf-8")),
name="MyHub",
start=False,
wait=False,
Expand Down Expand Up @@ -118,7 +120,9 @@ async def test_download_usb(self):
with contextlib.ExitStack() as stack:
# Create and manage temporary file
temp = stack.enter_context(
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
tempfile.NamedTemporaryFile(
suffix=".py", mode="w+", delete=False, encoding="utf-8"
)
)
temp.write("print('test')")
temp_path = temp.name
Expand All @@ -127,7 +131,7 @@ async def test_download_usb(self):
# Create args
args = argparse.Namespace(
conntype="usb",
file=open(temp_path, "r"),
file=stack.enter_context(open(temp_path, "r", encoding="utf-8")),
name=None,
start=False,
wait=False,
Expand Down Expand Up @@ -208,7 +212,9 @@ async def test_download_connection_error(self):
with contextlib.ExitStack() as stack:
# Create and manage temporary file
temp = stack.enter_context(
tempfile.NamedTemporaryFile(suffix=".py", mode="w+", delete=False)
tempfile.NamedTemporaryFile(
suffix=".py", mode="w+", delete=False, encoding="utf-8"
)
)
temp.write("print('test')")
temp_path = temp.name
Expand All @@ -217,7 +223,7 @@ async def test_download_connection_error(self):
# Create args
args = argparse.Namespace(
conntype="ble",
file=open(temp_path, "r"),
file=stack.enter_context(open(temp_path, "r", encoding="utf-8")),
name="MyHub",
start=False,
wait=False,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
@pytest.mark.parametrize("abi", [5, 6])
@pytest.mark.asyncio
async def test_compile_file(abi: int):
with NamedTemporaryFile("w", delete=False) as f:
with NamedTemporaryFile("w", delete=False, encoding="utf-8") as f:
try:
f.write("print('test')")
f.close()
Expand All @@ -35,7 +35,7 @@ async def test_compile_file(abi: int):

@pytest.mark.asyncio
async def test_compile_file_invalid_abi():
with NamedTemporaryFile("w", delete=False) as f:
with NamedTemporaryFile("w", delete=False, encoding="utf-8") as f:
try:
f.write("print('test')")
f.close()
Expand Down