diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97834abe..de6861c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: sed -i '' 's/\[tool.hatch.build.hooks.\(.*\)\]/\[_tool.hatch.build.hooks.\1\]/' pyproject.toml - uses: astral-sh/setup-uv@v3 with: - version: "0.4.16" + version: "0.4.x" - run: | uv run ruff check uv run ruff format @@ -39,7 +39,7 @@ jobs: sed -i '' 's/\[tool.hatch.build.hooks.\(.*\)\]/\[_tool.hatch.build.hooks.\1\]/' pyproject.toml - uses: astral-sh/setup-uv@v3 with: - version: "0.4.16" + version: "0.4.x" - run: uv run mypy TestPython: @@ -55,17 +55,16 @@ jobs: - "3.12" steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version-file: ".python-version" - uses: pnpm/action-setup@v4 with: run_install: true - uses: astral-sh/setup-uv@v3 with: - version: "0.4.16" + version: "0.4.x" - name: Run tests run: uv run --with pytest-cov pytest ./tests --color=yes --cov anywidget --cov-report xml + env: + UV_PYTHON: ${{ matrix.python-version }} - uses: codecov/codecov-action@v4 LintJavaScript: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ab9668c..88df64cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: run_install: true - uses: astral-sh/setup-uv@v3 with: - version: "0.4.16" + version: "0.4.x" - name: Create Release Pull Request or Publish id: changesets uses: changesets/action@v1 diff --git a/.python-version b/.python-version index 902b2c90..e4fba218 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.11 \ No newline at end of file +3.12 diff --git a/pyproject.toml b/pyproject.toml index 77cbff4b..07b046af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,14 +5,12 @@ build-backend = "hatchling.build" [project] name = "anywidget" description = "custom jupyter widgets made easy" -authors = [ - { name = "Trevor Manz", email = "trevor.j.manz@gmail.com" } -] +authors = [{ name = "Trevor Manz", email = "trevor.j.manz@gmail.com" }] license = { text = "MIT" } dynamic = ["version"] readme = "README.md" requires-python = ">=3.8" -dependencies = [ +dependencies = [ "ipywidgets>=7.6.0", "typing-extensions>=4.2.0", "psygnal>=0.8.1", @@ -69,9 +67,7 @@ skip-if-exists = [ "anywidget/nbextension/index.js", "anywidget/labextension/package.json", ] -dependencies = [ - "hatch-jupyter-builder>=0.5.0", -] +dependencies = ["hatch-jupyter-builder>=0.5.0"] [tool.hatch.build.hooks.jupyter-builder.build-kwargs] npm = "pnpm" @@ -91,20 +87,24 @@ exclude = ["packages", "docs"] pydocstyle = { convention = "numpy" } select = ["ALL"] ignore = [ - "D401", # First line should be in imperative mood (remove to opt in) + "D401", # First line should be in imperative mood (remove to opt in) "COM812", # Missing trailing comma (conflicts with ruff format) "ISC001", # Import sorting (conflicts with ruff format) "FIX002", # Fixable issue "DOC201", # TODO(manzt) enable in follow-up PR; no doc for return type. - "FBT", # TODO(manzt): enable in follow-up PR; require bool options to be keyword-only. + "FBT", # TODO(manzt): enable in follow-up PR; require bool options to be keyword-only. ] [tool.ruff.lint.per-file-ignores] "tests/*.py" = [ - "D", # No docstrings in tests - "S101", # Use of assert - "B018", # "useless expression", for accessing the Foo._repr_mimbundle_ descriptor + "D", # No docstrings in tests + "S101", # Use of assert + "B018", # "useless expression", for accessing the Foo._repr_mimbundle_ descriptor "SLF001", # Access private member + "PLC2701" # Private imports +] +"tests/test_descriptor.py" = [ + "FA100" # Don't add 'from __future__ import annotations' because it messes with Pydantic and ClassVar ] "docs/*.py" = ["D"] @@ -131,7 +131,7 @@ exclude_lines = [ "@overload", "except ImportError", "\\.\\.\\.", - "raise NotImplementedError()" + "raise NotImplementedError()", ] # https://mypy.readthedocs.io/en/stable/config_file.html @@ -144,11 +144,11 @@ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] warn_unreachable = true [[tool.mypy.overrides]] -module = "anywidget.widget" # makes heavy use of traitlets, which is not typed +module = "anywidget.widget" # makes heavy use of traitlets, which is not typed disallow_untyped_calls = false [[tool.mypy.overrides]] -module = "anywidget._cellmagic" # makes heavy use of IPython, which is not typed +module = "anywidget._cellmagic" # makes heavy use of IPython, which is not typed disallow_untyped_calls = false [[tool.mypy.overrides]] diff --git a/tests/test_descriptor.py b/tests/test_descriptor.py index 1fdb52fb..b30683ff 100644 --- a/tests/test_descriptor.py +++ b/tests/test_descriptor.py @@ -1,9 +1,8 @@ -from __future__ import annotations - +import pathlib import time import weakref from dataclasses import dataclass -from typing import TYPE_CHECKING, Callable, ClassVar, Generator +from typing import TYPE_CHECKING, Callable, ClassVar, Generator, Set, Union from unittest.mock import MagicMock, patch import anywidget._descriptor @@ -15,15 +14,11 @@ ReprMimeBundle, ) from anywidget._file_contents import FileContents +from anywidget._protocols import AnywidgetProtocol from anywidget._util import _WIDGET_MIME_TYPE +from ipykernel.comm import Comm from watchfiles import Change -if TYPE_CHECKING: - import pathlib - - from anywidget._protocols import AnywidgetProtocol - from ipykernel.comm import Comm - class MockComm(MagicMock): # The only thing we need to do is to be able to relay messages back to @@ -82,7 +77,7 @@ class Foo: _repr_mimebundle_ = MimeBundleDescriptor(autodetect_observer=False) value: int = val - def _get_anywidget_state(self, include: set[str] | None): # noqa: ANN202, ARG002 + def _get_anywidget_state(self, include: Union[Set[str], None]): # noqa: ANN202, ARG002 return {"value": self.value} def __repr__(self) -> str: @@ -122,7 +117,7 @@ def test_state_setter(mock_comm: MagicMock) -> None: class Foo: _repr_mimebundle_ = MimeBundleDescriptor(autodetect_observer=False) - def _get_anywidget_state(self, include: set[str] | None): # noqa: ANN202, ARG002 + def _get_anywidget_state(self, include: Union[Set[str], None]): # noqa: ANN202, ARG002 return {} def _set_anywidget_state(self, state) -> None: # noqa: ANN001 @@ -142,7 +137,7 @@ def test_state_setter_binary(mock_comm: MagicMock) -> None: class Foo: _repr_mimebundle_ = MimeBundleDescriptor(autodetect_observer=False) - def _get_anywidget_state(self, include: set[str] | None): # noqa: ANN202, ARG002 + def _get_anywidget_state(self, include: Union[Set[str], None]): # noqa: ANN202, ARG002 return {} def _set_anywidget_state(self, state: dict) -> None: @@ -168,7 +163,7 @@ def test_comm_cleanup() -> None: class Foo: _repr_mimebundle_ = MimeBundleDescriptor(autodetect_observer=False) - def _get_anywidget_state(self, include: set[str] | None): # noqa: ANN202, ARG002 + def _get_anywidget_state(self, include: Union[Set[str], None]): # noqa: ANN202, ARG002 return {} foo = Foo() @@ -192,7 +187,7 @@ def test_detect_observer() -> None: class Foo: _repr_mimebundle_ = MimeBundleDescriptor() - def _get_anywidget_state(self, include: set[str] | None): # noqa: ANN202, ARG002 + def _get_anywidget_state(self, include: Union[Set[str], None]): # noqa: ANN202, ARG002 return {} with pytest.warns(UserWarning, match="Could not find a notifier"): @@ -208,7 +203,7 @@ class Foo: _repr_mimebundle_ = MimeBundleDescriptor(autodetect_observer=False) value: int = 1 - def _get_anywidget_state(self, include: set[str] | None): # noqa: ANN202, ARG002 + def _get_anywidget_state(self, include: Union[Set[str], None]): # noqa: ANN202, ARG002 return {"value": self.value} with pytest.warns(UserWarning, match=".*is not weakrefable"): @@ -340,7 +335,7 @@ class Foo: _repr_mimebundle_ = MimeBundleDescriptor(_esm=esm, autodetect_observer=False) value: int = 1 - def _get_anywidget_state(self, include: set[str] | None): # noqa: ANN202, ARG002 + def _get_anywidget_state(self, include: Union[Set[str], None]): # noqa: ANN202, ARG002 return {"value": self.value} file_contents = Foo._repr_mimebundle_._extra_state["_esm"] @@ -388,7 +383,7 @@ class Foo: _repr_mimebundle_ = MimeBundleDescriptor(bar=bar, autodetect_observer=False) value: int = 1 - def _get_anywidget_state(self, include: set[str] | None): # noqa: ANN202, ARG002 + def _get_anywidget_state(self, include: Union[Set[str], None]): # noqa: ANN202, ARG002 return {"value": self.value} file_contents = Foo._repr_mimebundle_._extra_state["bar"] @@ -415,7 +410,7 @@ class Foo: autodetect_observer=False, ) - def _get_anywidget_state(self, include: set[str] | None): # noqa: ANN202, ARG002 + def _get_anywidget_state(self, include: Union[Set[str], None]): # noqa: ANN202, ARG002 return {} foo = Foo()