Skip to content

Commit 18f518a

Browse files
Merge branch 'master' into dev
2 parents 2d8f261 + 00445d4 commit 18f518a

File tree

20 files changed

+1889
-408
lines changed

20 files changed

+1889
-408
lines changed

.devcontainer/devcontainer.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "Python dev environment",
3+
"image": "ghcr.io/opencyphal/toxic:tx22.4.2",
4+
"workspaceFolder": "/workspace",
5+
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated",
6+
"mounts": [
7+
"source=root-vscode-server,target=/root/.vscode-server/extensions,type=volume",
8+
"source=pydsdl-tox,target=/workspace/.nox,type=volume"
9+
],
10+
"customizations": {
11+
"vscode": {
12+
"extensions": [
13+
"uavcan.dsdl",
14+
"wholroyd.jinja",
15+
"streetsidesoftware.code-spell-checker",
16+
"ms-python.python",
17+
"ms-python.mypy-type-checker",
18+
"ms-python.black-formatter",
19+
"ms-python.pylint"
20+
]
21+
}
22+
},
23+
"postCreateCommand": "git clone --depth 1 [email protected]:OpenCyphal/public_regulated_data_types.git .dsdl-test && nox -e test-3.12"
24+
}

.github/workflows/test-and-release.yml

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: 'Test and Release PyDSDL'
2-
on: push
2+
on: [ push, pull_request ]
33

44
# Ensures that only one workflow is running at a time
55
concurrency:
@@ -9,11 +9,14 @@ concurrency:
99
jobs:
1010
pydsdl-test:
1111
name: Test PyDSDL
12+
# Run on push OR on 3rd-party PR.
13+
# https://docs.github.com/en/webhooks/webhook-events-and-payloads?actionType=edited#pull_request
14+
if: (github.event_name == 'push') || github.event.pull_request.head.repo.fork
1215
strategy:
1316
fail-fast: false
1417
matrix:
1518
os: [ ubuntu-latest, macos-latest ]
16-
python: [ '3.7', '3.8', '3.9', '3.10', '3.11' ]
19+
python: [ '3.8', '3.9', '3.10', '3.11', '3.12' ]
1720
include:
1821
- os: windows-2019
1922
python: '3.10'
@@ -22,10 +25,10 @@ jobs:
2225
runs-on: ${{ matrix.os }}
2326
steps:
2427
- name: Check out pydsdl
25-
uses: actions/checkout@v3
28+
uses: actions/checkout@v4
2629

2730
- name: Check out public_regulated_data_types
28-
uses: actions/checkout@v3
31+
uses: actions/checkout@v4
2932
with:
3033
repository: OpenCyphal/public_regulated_data_types
3134
path: .dsdl-test
@@ -50,27 +53,28 @@ jobs:
5053
- name: Run build and test
5154
run: |
5255
if [ "$RUNNER_OS" == "Linux" ]; then
53-
nox --non-interactive --error-on-missing-interpreters --session test test_eol pristine lint --python ${{ matrix.python }}
56+
nox --non-interactive --error-on-missing-interpreters --session test pristine lint --python ${{ matrix.python }}
5457
nox --non-interactive --session docs
5558
elif [ "$RUNNER_OS" == "Windows" ]; then
56-
nox --forcecolor --non-interactive --error-on-missing-interpreters --session test test_eol pristine lint
59+
nox --forcecolor --non-interactive --error-on-missing-interpreters --session test pristine lint
5760
elif [ "$RUNNER_OS" == "macOS" ]; then
58-
nox --non-interactive --error-on-missing-interpreters --session test test_eol pristine lint --python ${{ matrix.python }}
61+
nox --non-interactive --error-on-missing-interpreters --session test pristine lint --python ${{ matrix.python }}
5962
else
6063
echo "${{ runner.os }} not supported"
6164
exit 1
6265
fi
63-
python -c "import pydsdl; pydsdl.read_namespace('.dsdl-test/uavcan', [])"
6466
shell: bash
6567

6668
pydsdl-release:
6769
name: Release PyDSDL
68-
if: contains(github.event.head_commit.message, '#release') || contains(github.ref, '/master')
70+
if: >
71+
(github.event_name == 'push') &&
72+
(contains(github.event.head_commit.message, '#release') || contains(github.ref, '/master'))
6973
needs: pydsdl-test
7074
runs-on: ubuntu-latest
7175
steps:
7276
- name: Check out
73-
uses: actions/checkout@v3
77+
uses: actions/checkout@v4
7478

7579
- name: Build distribution
7680
run: |

.readthedocs.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
version: 2
44

5+
build:
6+
os: ubuntu-22.04
7+
tools:
8+
python: "3.12"
9+
apt_packages:
10+
- graphviz
11+
512
sphinx:
613
configuration: docs/conf.py
714
fail_on_warning: true

conftest.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#
2+
# Copyright (C) OpenCyphal Development Team <opencyphal.org>
3+
# Copyright Amazon.com Inc. or its affiliates.
4+
# SPDX-License-Identifier: MIT
5+
#
6+
"""
7+
Configuration for pytest tests including fixtures and hooks.
8+
"""
9+
10+
import tempfile
11+
from pathlib import Path
12+
from typing import Any, Optional
13+
14+
import pytest
15+
16+
17+
# +-------------------------------------------------------------------------------------------------------------------+
18+
# | TEST FIXTURES
19+
# +-------------------------------------------------------------------------------------------------------------------+
20+
class TemporaryDsdlContext:
21+
"""
22+
Powers the temp_dsdl_factory test fixture.
23+
"""
24+
def __init__(self) -> None:
25+
self._base_dir: Optional[Any] = None
26+
27+
def new_file(self, file_path: Path, text: Optional[str] = None) -> Path:
28+
if file_path.is_absolute():
29+
raise ValueError(f"{file_path} is an absolute path. The test fixture requires relative paths to work.")
30+
file = self.base_dir / file_path
31+
file.parent.mkdir(parents=True, exist_ok=True)
32+
if text is not None:
33+
file.write_text(text)
34+
return file
35+
36+
@property
37+
def base_dir(self) -> Path:
38+
if self._base_dir is None:
39+
self._base_dir = tempfile.TemporaryDirectory()
40+
return Path(self._base_dir.name).resolve()
41+
42+
def _test_path_finalizer(self) -> None:
43+
"""
44+
Finalizer to clean up any temporary directories created during the test.
45+
"""
46+
if self._base_dir is not None:
47+
self._base_dir.cleanup()
48+
del self._base_dir
49+
self._base_dir = None
50+
51+
@pytest.fixture(scope="function")
52+
def temp_dsdl_factory(request: pytest.FixtureRequest) -> Any: # pylint: disable=unused-argument
53+
"""
54+
Fixture for pydsdl tests that have to create files as part of the test. This object stays in-scope for a given
55+
test method and does not requires a context manager in the test itself.
56+
57+
Call `new_file(path)` to create a new file path in the fixture's temporary directory. This will create all
58+
uncreated parent directories but will _not_ create the file unless text is provided: `new_file(path, "hello")`
59+
"""
60+
f = TemporaryDsdlContext()
61+
request.addfinalizer(f._test_path_finalizer) # pylint: disable=protected-access
62+
return f
63+

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Contents
1515
--------
1616

1717
.. toctree::
18+
:maxdepth: 2
1819

1920
pages/installation
2021
pages/pydsdl

docs/pages/pydsdl.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ You can find a practical usage example in the Nunavut code generation library th
1212
:local:
1313

1414

15-
The main function
16-
+++++++++++++++++
15+
The main functions
16+
++++++++++++++++++
1717

1818
.. autofunction:: pydsdl.read_namespace
19+
.. autofunction:: pydsdl.read_files
1920

2021

2122
Type model

docs/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
sphinx == 4.4.0
2-
sphinx_rtd_theme == 1.0.0
1+
sphinx == 7.1.2 # this is the last version that supports Python 3.8
2+
sphinx_rtd_theme == 2.0.0
33
sphinx-computron >= 0.2, < 2.0

noxfile.py

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import nox
1111

1212

13-
PYTHONS = ["3.8", "3.9", "3.10", "3.11"]
13+
PYTHONS = ["3.8", "3.9", "3.10", "3.11", "3.12"]
1414
"""The newest supported Python shall be listed LAST."""
1515

1616
nox.options.error_on_external_run = True
@@ -33,7 +33,6 @@ def clean(session):
3333
"*.log",
3434
"*.tmp",
3535
".nox",
36-
".dsdl-test",
3736
]
3837
for w in wildcards:
3938
for f in Path.cwd().glob(w):
@@ -49,9 +48,9 @@ def test(session):
4948
session.log("Using the newest supported Python: %s", is_latest_python(session))
5049
session.install("-e", ".")
5150
session.install(
52-
"pytest ~= 7.3",
53-
"pytest-randomly ~= 3.12",
54-
"coverage ~= 7.2",
51+
"pytest ~= 8.1",
52+
"pytest-randomly ~= 3.15",
53+
"coverage ~= 7.5",
5554
)
5655
session.run("coverage", "run", "-m", "pytest")
5756
session.run("coverage", "report", "--fail-under=95")
@@ -61,14 +60,6 @@ def test(session):
6160
session.log(f"OPEN IN WEB BROWSER: file://{report_file}")
6261

6362

64-
@nox.session(python=["3.7"])
65-
def test_eol(session):
66-
"""This is a minimal test session for those old Pythons that have EOLed."""
67-
session.install("-e", ".")
68-
session.install("pytest")
69-
session.run("pytest")
70-
71-
7263
@nox.session(python=PYTHONS)
7364
def pristine(session):
7465
"""
@@ -85,8 +76,9 @@ def pristine(session):
8576
def lint(session):
8677
session.log("Using the newest supported Python: %s", is_latest_python(session))
8778
session.install(
88-
"mypy ~= 1.2.0",
89-
"pylint ~= 2.17.2",
79+
"mypy ~= 1.10",
80+
"types-parsimonious",
81+
"pylint ~= 3.2",
9082
)
9183
session.run(
9284
"mypy",
@@ -105,7 +97,8 @@ def lint(session):
10597
},
10698
)
10799
if is_latest_python(session):
108-
session.install("black ~= 23.3")
100+
# we run black only on the newest Python version to ensure that the code is formatted with the latest version
101+
session.install("black ~= 24.4")
109102
session.run("black", "--check", ".")
110103

111104

pydsdl/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import sys as _sys
88
from pathlib import Path as _Path
99

10-
__version__ = "1.21.0"
10+
__version__ = "1.23.0"
1111
__version_info__ = tuple(map(int, __version__.split(".")[:3]))
1212
__license__ = "MIT"
1313
__author__ = "OpenCyphal"
@@ -25,8 +25,9 @@
2525
_sys.path = [str(_Path(__file__).parent / "third_party")] + _sys.path
2626

2727
# Never import anything that is not available here - API stability guarantees are only provided for the exposed items.
28+
from ._dsdl import PrintOutputHandler as PrintOutputHandler
2829
from ._namespace import read_namespace as read_namespace
29-
from ._namespace import PrintOutputHandler as PrintOutputHandler
30+
from ._namespace import read_files as read_files
3031

3132
# Error model.
3233
from ._error import FrontendError as FrontendError

pydsdl/_bit_length_set/_symbolic_test.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# This software is distributed under the terms of the MIT License.
33
# Author: Pavel Kirienko <[email protected]>
44

5-
import typing
65
import random
76
import itertools
87
from ._symbolic import NullaryOperator, validate_numerically
@@ -140,7 +139,7 @@ def _unittest_repetition() -> None:
140139
)
141140
assert op.min == 7 * 3
142141
assert op.max == 17 * 3
143-
assert set(op.expand()) == set(map(sum, itertools.combinations_with_replacement([7, 11, 17], 3))) # type: ignore
142+
assert set(op.expand()) == set(map(sum, itertools.combinations_with_replacement([7, 11, 17], 3)))
144143
assert set(op.expand()) == {21, 25, 29, 31, 33, 35, 39, 41, 45, 51}
145144
assert set(op.modulo(7)) == {0, 1, 2, 3, 4, 5, 6}
146145
assert set(op.modulo(8)) == {1, 3, 5, 7}
@@ -149,15 +148,15 @@ def _unittest_repetition() -> None:
149148
for _ in range(1):
150149
child = NullaryOperator(random.randint(0, 100) for _ in range(random.randint(1, 10)))
151150
k = random.randint(0, 10)
152-
ref = set(map(sum, itertools.combinations_with_replacement(child.expand(), k))) # type: ignore
151+
ref = set(map(sum, itertools.combinations_with_replacement(child.expand(), k)))
153152
op = RepetitionOperator(child, k)
154153
assert set(op.expand()) == ref
155154

156155
assert op.min == min(child.expand()) * k
157156
assert op.max == max(child.expand()) * k
158157

159158
div = random.randint(1, 64)
160-
assert set(op.modulo(div)) == {typing.cast(int, x) % div for x in ref}
159+
assert set(op.modulo(div)) == {x % div for x in ref}
161160

162161
validate_numerically(op)
163162

@@ -173,9 +172,9 @@ def _unittest_range_repetition() -> None:
173172
assert op.max == 17 * 3
174173
assert set(op.expand()) == (
175174
{0}
176-
| set(map(sum, itertools.combinations_with_replacement([7, 11, 17], 1))) # type: ignore
177-
| set(map(sum, itertools.combinations_with_replacement([7, 11, 17], 2))) # type: ignore
178-
| set(map(sum, itertools.combinations_with_replacement([7, 11, 17], 3))) # type: ignore
175+
| set(map(sum, itertools.combinations_with_replacement([7, 11, 17], 1)))
176+
| set(map(sum, itertools.combinations_with_replacement([7, 11, 17], 2)))
177+
| set(map(sum, itertools.combinations_with_replacement([7, 11, 17], 3)))
179178
)
180179
assert set(op.expand()) == {0, 7, 11, 14, 17, 18, 21, 22, 24, 25, 28, 29, 31, 33, 34, 35, 39, 41, 45, 51}
181180
assert set(op.modulo(7)) == {0, 1, 2, 3, 4, 5, 6}
@@ -197,10 +196,7 @@ def _unittest_range_repetition() -> None:
197196
k_max = random.randint(0, 10)
198197
ref = set(
199198
itertools.chain(
200-
*(
201-
map(sum, itertools.combinations_with_replacement(child.expand(), k)) # type: ignore
202-
for k in range(k_max + 1)
203-
)
199+
*(map(sum, itertools.combinations_with_replacement(child.expand(), k)) for k in range(k_max + 1))
204200
)
205201
)
206202
op = RangeRepetitionOperator(child, k_max)
@@ -210,7 +206,7 @@ def _unittest_range_repetition() -> None:
210206
assert op.max == max(child.expand()) * k_max
211207

212208
div = random.randint(1, 64)
213-
assert set(op.modulo(div)) == {typing.cast(int, x) % div for x in ref}
209+
assert set(op.modulo(div)) == {x % div for x in ref}
214210

215211
validate_numerically(op)
216212

0 commit comments

Comments
 (0)