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
12 changes: 6 additions & 6 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
defaults:
run:
shell: bash
Expand All @@ -35,18 +35,18 @@ jobs:
cache: "poetry"
- name: Install dependencies
run: poetry install --no-interaction --extras syntax
if: ${{ matrix.python-version != '3.8' && matrix.python-version != '3.9' }}
- name: Install dependencies for 3.8 and 3.9
if: ${{ matrix.python-version != '3.9' }}
- name: Install dependencies for 3.9
run: poetry install --no-interaction
if: ${{ matrix.python-version == '3.8' || matrix.python-version == '3.9' }}
if: ${{ matrix.python-version == '3.9' }}
- name: Test with pytest (Py310+ - with syntax highlighting)
run: |
poetry run pytest tests -v --cov=./src/textual --cov-report=xml:./coverage.xml --cov-report term-missing
if: ${{ matrix.python-version != '3.8' && matrix.python-version != '3.9' }}
if: ${{ matrix.python-version != '3.9' }}
- name: Test with pytest (Py39 - without syntax highlighting)
run: |
poetry run pytest tests -v --cov=./src/textual --cov-report=xml:./coverage.xml --cov-report term-missing -m 'not syntax'
if: ${{ matrix.python-version == '3.8' || matrix.python-version == '3.9' }}
if: ${{ matrix.python-version == '3.9' }}
- name: Upload snapshot report
if: always()
uses: actions/upload-artifact@v4
Expand Down
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased
## [6.3.0] - 2025-10-11

### Added

Expand All @@ -15,6 +15,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Fixed highlight not auto-detecting lexer https://github.com/Textualize/textual/pull/6167

### Changed

- Dropped support for Python3.8 https://github.com/Textualize/textual/pull/6121/
- Added support for Python3.14 https://github.com/Textualize/textual/pull/6121/

## [6.2.1] - 2025-10-01

- Fix inability to copy text outside of an input/textarea when it was focused https://github.com/Textualize/textual/pull/6148
Expand Down Expand Up @@ -3144,6 +3149,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040
- New handler system for messages that doesn't require inheritance
- Improved traceback handling

[6.3.0]: https://github.com/Textualize/textual/compare/v6.2.1...v6.3.0
[6.2.1]: https://github.com/Textualize/textual/compare/v6.2.0...v6.2.1
[6.2.0]: https://github.com/Textualize/textual/compare/v6.1.0...v6.2.0
[6.1.0]: https://github.com/Textualize/textual/compare/v6.0.0...v6.1.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@


[![Discord](https://img.shields.io/discord/1026214085173461072)](https://discord.gg/Enf6Z3qhVr)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/textual/1.0.0)](https://pypi.org/project/textual/)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/textual)](https://pypi.org/project/textual/)
[![PyPI version](https://badge.fury.io/py/textual.svg?)](https://badge.fury.io/py/textual)
![OS support](https://img.shields.io/badge/OS-macOS%20Linux%20Windows-red)

Expand Down
2 changes: 1 addition & 1 deletion docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ All you need to get started building Textual apps.

## Requirements

Textual requires Python 3.8 or later (if you have a choice, pick the most recent Python). Textual runs on Linux, macOS, Windows and probably any OS where Python also runs.
Textual requires Python 3.9 or later (if you have a choice, pick the most recent Python). Textual runs on Linux, macOS, Windows and probably any OS where Python also runs.

!!! info "Your platform"

Expand Down
2,891 changes: 1,643 additions & 1,248 deletions poetry.lock

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
version = "6.2.1"
version = "6.3.0"
homepage = "https://github.com/Textualize/textual"
repository = "https://github.com/Textualize/textual"
documentation = "https://textual.textualize.io/"
Expand All @@ -17,12 +17,12 @@ classifiers = [
"Operating System :: Microsoft :: Windows :: Windows 11",
"Operating System :: MacOS",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Typing :: Typed",
]
include = [
Expand All @@ -41,12 +41,12 @@ include = [
"Bug Tracker" = "https://github.com/Textualize/textual/issues"

[tool.ruff]
target-version = "py38"
target-version = "py39"

[tool.poetry.dependencies]
python = "^3.8.1"
python = "^3.9"
markdown-it-py = { extras = ["plugins", "linkify"], version = ">=2.1.0" }
rich = ">=13.3.3"
rich = ">=14.2.0"
#rich = {path="../rich", develop=true}
typing-extensions = "^4.4.0"
platformdirs = ">=3.6.0,<5"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions tests/test_arrange.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
from textual.widget import Widget


def test_arrange_empty():
async def test_arrange_empty():
container = Widget(id="container")

result = arrange(container, [], Size(80, 24), Size(80, 24))
assert result.placements == []
assert result.widgets == set()


def test_arrange_dock_top():
async def test_arrange_dock_top():
container = Widget(id="container")
container._parent = App()
child = Widget(id="child")
Expand All @@ -36,7 +36,7 @@ def test_arrange_dock_top():
assert result.widgets == {child, header}


def test_arrange_dock_left():
async def test_arrange_dock_left():
container = Widget(id="container")
container._parent = App()
child = Widget(id="child")
Expand All @@ -61,7 +61,7 @@ def test_arrange_dock_left():
assert result.widgets == {child, header}


def test_arrange_dock_right():
async def test_arrange_dock_right():
container = Widget(id="container")
container._parent = App()
child = Widget(id="child")
Expand All @@ -86,7 +86,7 @@ def test_arrange_dock_right():
assert result.widgets == {child, header}


def test_arrange_dock_bottom():
async def test_arrange_dock_bottom():
container = Widget(id="container")
container._parent = App()
child = Widget(id="child")
Expand All @@ -111,7 +111,7 @@ def test_arrange_dock_bottom():
assert result.widgets == {child, header}


def test_arrange_dock_badly():
async def test_arrange_dock_badly():
child = Widget(id="child")
child.styles.dock = "nowhere"
with pytest.raises(AssertionError):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_border.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def test_border_render_row():
]


def test_border_title_single_line():
async def test_border_title_single_line():
"""The border_title gets set to a single line even when multiple lines are provided."""
widget = Widget()

Expand Down Expand Up @@ -59,7 +59,7 @@ def test_border_title_single_line():
assert widget.border_title == "[bold]Hello World[/bold]"


def test_border_subtitle_single_line():
async def test_border_subtitle_single_line():
"""The border_subtitle gets set to a single line even when multiple lines are provided."""
widget = Widget()

Expand Down
10 changes: 5 additions & 5 deletions tests/test_box_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from textual.widget import Widget


def test_content_box():
async def test_content_box():
one = Fraction(1)

class TestWidget(Widget):
Expand Down Expand Up @@ -44,7 +44,7 @@ def get_content_height(self, container: Size, parent: Size) -> int:
assert box_model == BoxModel(Fraction(14), Fraction(12), Spacing(0, 0, 0, 0))


def test_width():
async def test_width():
"""Test width settings."""

one = Fraction(1)
Expand Down Expand Up @@ -93,7 +93,7 @@ def get_content_height(self, container: Size, parent: Size, width: int) -> int:
assert box_model == BoxModel(Fraction(27), Fraction(16), Spacing(1, 2, 3, 4))


def test_height():
async def test_height():
"""Test height settings."""

one = Fraction(1)
Expand Down Expand Up @@ -143,7 +143,7 @@ def get_content_height(self, container: Size, parent: Size, width: int) -> int:
assert box_model == BoxModel(Fraction(54), Fraction(8), Spacing(1, 2, 3, 4))


def test_max():
async def test_max():
"""Check that max_width and max_height are respected."""
one = Fraction(1)

Expand All @@ -166,7 +166,7 @@ def get_content_height(self, container: Size, parent: Size, width: int) -> int:
assert box_model == BoxModel(Fraction(40), Fraction(30), Spacing(0, 0, 0, 0))


def test_min():
async def test_min():
"""Check that min_width and min_height are respected."""

one = Fraction(1)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def test_first_line():
assert first_line.spans == [Span(0, 3, "red")]


def test_split_and_tabs():
async def test_split_and_tabs():
spans = [
Span(0, 49, style="$text"),
]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from textual.app import App


def test_textual_env_var(monkeypatch):
async def test_textual_env_var(monkeypatch):
monkeypatch.setenv("TEXTUAL", "")
app = App()
assert app.features == set()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from textual.widgets import Log


def test_process_line():
async def test_process_line():
log = Log()
assert log._process_line("foo") == "foo"
assert log._process_line("foo\t") == "foo "
Expand Down
20 changes: 10 additions & 10 deletions tests/test_node_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ def test_empty_list():
assert len(NodeList()) == 0


def test_add_one():
async def test_add_one():
"""Does adding a node to the node list report as having one item?"""
nodes = NodeList()
nodes._append(Widget())
assert len(nodes) == 1


def test_length_hint():
async def test_length_hint():
"""Check length hint dunder method."""
nodes = NodeList()
assert nodes.__length_hint__() == 0
Expand All @@ -26,7 +26,7 @@ def test_length_hint():
assert nodes.__length_hint__() == 3


def test_repeat_add_one():
async def test_repeat_add_one():
"""Does adding the same item to the node list ignore the additional adds?"""
nodes = NodeList()
widget = Widget()
Expand All @@ -35,7 +35,7 @@ def test_repeat_add_one():
assert len(nodes) == 1


def test_insert():
async def test_insert():
nodes = NodeList()
widget1 = Widget()
widget2 = Widget()
Expand All @@ -46,15 +46,15 @@ def test_insert():
assert list(nodes) == [widget1, widget2, widget3]


def test_truthy():
async def test_truthy():
"""Does a node list act as a truthy object?"""
nodes = NodeList()
assert not bool(nodes)
nodes._append(Widget())
assert bool(nodes)


def test_contains():
async def test_contains():
"""Can we check if a widget is (not) within the list?"""
widget = Widget()
nodes = NodeList()
Expand All @@ -64,7 +64,7 @@ def test_contains():
assert Widget() not in nodes


def test_index():
async def test_index():
"""Can we get the index of a widget in the list?"""
widget = Widget()
nodes = NodeList()
Expand All @@ -74,7 +74,7 @@ def test_index():
assert nodes.index(widget) == 0


def test_remove():
async def test_remove():
"""Can we remove a widget we've added?"""
widget = Widget()
nodes = NodeList()
Expand All @@ -84,7 +84,7 @@ def test_remove():
assert widget not in nodes


def test_clear():
async def test_clear():
"""Can we clear the list?"""
nodes = NodeList()
assert len(nodes) == 0
Expand All @@ -100,7 +100,7 @@ def test_clear():
assert widget not in nodes


def test_listy():
async def test_listy():
nodes = NodeList()
widget1 = Widget()
widget2 = Widget()
Expand Down
4 changes: 2 additions & 2 deletions tests/test_placeholder.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
from textual.widgets._placeholder import InvalidPlaceholderVariant


def test_invalid_placeholder_variant():
async def test_invalid_placeholder_variant():
with pytest.raises(InvalidPlaceholderVariant):
Placeholder(variant="this is clearly not a valid variant!")


def test_invalid_reactive_variant_change():
async def test_invalid_reactive_variant_change():
p = Placeholder()
with pytest.raises(InvalidPlaceholderVariant):
p.variant = "this is clearly not a valid variant!"
Loading
Loading