Typing-related TypeError with Python 3.7, but runs with Python 3.10

The following program test_widget.py

from textual.app import App, ComposeResult
from textual.widget import Widget

class TestWidget(Widget):

    def __init__(
        self,
        *,
        name: str | None = None,
        id = None,
        classes = None
    ):
        super().__init__(name=name, id=id, classes=classes)

class TestApp(App):
    def compose(self) -> ComposeResult:
        yield TestWidget()

app = TestApp()
app.run()

runs fine on Python 3.10, but not on Python 3.7 (which is the stated minimum Python version in pyproject.toml). The error I get is

$ venv37/bin/python test_widget.py 
Traceback (most recent call last):
  File "test_widget.py", line 4, in <module>
    class TestWidget(Widget):
  File "test_widget.py", line 11, in TestWidget
    classes = None
TypeError: unsupported operand type(s) for |: 'type' and 'NoneType'

It seems to relate to this line:

        name: str | None = None,

in which, if I change the type to None rather than str | None, the above works with 3.7 too.

This is what I did to set up the venvs for testing: set up a requirements file

$ more test-reqts.txt 
rich
textual[dev]

then

$ venv37/bin/pip install -r test-reqts.txt
[intermediate output elided]
Successfully installed aiohttp-3.8.3 aiosignal-1.3.1 async-timeout-4.0.2 asynctest-0.13.0 attrs-22.1.0 charset-normalizer-2.1.1 click-8.1.3 commonmark-0.9.1 frozenlist-1.3.3 idna-3.4 importlib-metadata-4.13.0 msgpack-1.0.4 multidict-6.0.2 nanoid-2.0.0 pygments-2.13.0 rich-12.6.0 textual-0.5.0 typing-extensions-4.4.0 yarl-1.8.1 zipp-3.10.0
$ venv310/bin/pip install -r test-reqts.txt
[intermediate output elided]
Successfully installed aiohttp-3.8.3 aiosignal-1.3.1 async-timeout-4.0.2 attrs-22.1.0 charset-normalizer-2.1.1 click-8.1.3 commonmark-0.9.1 frozenlist-1.3.3 idna-3.4 importlib-metadata-4.13.0 msgpack-1.0.4 multidict-6.0.2 nanoid-2.0.0 pygments-2.13.0 rich-12.6.0 textual-0.5.0 yarl-1.8.1 zipp-3.10.0

Then,
$ venv37/bin/python test_widget.py gives the error described above, but $ venv310/bin/python test_widget.py shows no error.

I notice that 0.5.0 has a lot of instances of the str | None type in the standard widgets - so what have I got wrong in my test script?

The union type (X | Y) does seem to be a new feature of python 3.10. But looking at the github actions on the github repo, it seems like the tests should be run against 3.7 to 3.11 so I’m surprised it’s passing those test suites. Looks like the tests were running on 3.7.15 if you want to try that.

I tried running your script with 3.7.15, no dice. But I can run the textual demo app no problem and as you said the current textual code is full of the str | None syntax.

Oh, I see. The new typing stuff is backported to (recent, probably) versions of 3.7+. I noticed this import in various textual source files:

from __future__ import annotations

add that to your script and it works fine in 3.7.15 (I didn’t try older versions, it’ll have to be a version of 3.7 that was updated after the changes were added to 3.10 though)

1 Like

Nice catch. Thank you! For anyone else running into this, 3.10.0 was released on 2021-10-04. Newer releases of older Python versions are:

3.7.13 released on 2022-03-16
3.8.13 released on 2022-03-16
3.9.8  released on 2021-11-05