Skip to content

Fixes for Python 3.14 and fixes for deprecations #1539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 19, 2025
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
5 changes: 3 additions & 2 deletions .github/workflows/tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
fail-fast: false
max-parallel: 4
matrix:
tox-env: [py39, py310, py311, py312, py313, pypy39, pypy310, pygments]
tox-env: [py39, py310, py311, py312, py313, py314, pypy39, pypy310, pygments]
include:
- tox-env: py39
python-version: '3.9'
Expand All @@ -32,13 +32,14 @@ jobs:
python-version: '3.12'
- tox-env: py313
python-version: '3.13'
- tox-env: py314
python-version: '3.14'
- tox-env: pypy39
python-version: pypy-3.9
- tox-env: pypy310
python-version: pypy-3.10
- tox-env: pygments
python-version: '3.11'

env:
TOXENV: ${{ matrix.tox-env }}

Expand Down
9 changes: 8 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ and this project adheres to the
[Python Version Specification]: https://packaging.python.org/en/latest/specifications/version-specifiers/.
See the [Contributing Guide](contributing.md) for details.

## [unreleased]
## [Unreleased]

### Fixed

* Fix `codecs` deprecation in Python 3.14.
* Fix issue with unclosed comment parsing in Python 3.14.
* Fix issue with unclosed declarations in Python 3.14.
* Fix issue with unclosed HTML tag `<foo` and Python 3.14.

## [3.8.1] - 2025-06-18

Expand Down
3 changes: 1 addition & 2 deletions markdown/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

import sys
import optparse
import codecs
import warnings
import markdown
try:
Expand Down Expand Up @@ -100,7 +99,7 @@ def parse_options(args=None, values=None):

extension_configs = {}
if options.configfile:
with codecs.open(
with open(
options.configfile, mode="r", encoding=options.encoding
) as fp:
try:
Expand Down
2 changes: 1 addition & 1 deletion markdown/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ def convertFile(
# Read the source
if input:
if isinstance(input, str):
input_file = codecs.open(input, mode="r", encoding=encoding)
input_file = open(input, mode="r", encoding=encoding)
else:
input_file = codecs.getreader(encoding)(input)
text = input_file.read()
Expand Down
6 changes: 5 additions & 1 deletion markdown/extensions/md_in_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,11 @@ def parse_html_declaration(self, i: int) -> int:
if self.rawdata[i:i+3] == '<![' and not self.rawdata[i:i+9] == '<![CDATA[':
# We have encountered the bug in #1534 (Python bug `gh-77057`).
# Provide an override until we drop support for Python < 3.13.
return self.parse_bogus_comment(i)
result = self.parse_bogus_comment(i)
if result == -1:
self.handle_data(self.rawdata[i:i + 1])
return i + 1
return result
# The same override exists in `HTMLExtractor` without the check
# for `mdstack`. Therefore, use parent of `HTMLExtractor` instead.
return super(HTMLExtractor, self).parse_html_declaration(i)
Expand Down
24 changes: 22 additions & 2 deletions markdown/htmlparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ def __init__(self, md: Markdown, *args, **kwargs):

self.lineno_start_cache = [0]

self.override_comment_update = False

# This calls self.reset
super().__init__(*args, **kwargs)
self.md = md
Expand Down Expand Up @@ -253,8 +255,21 @@ def handle_entityref(self, name: str):
self.handle_empty_tag('&{};'.format(name), is_block=False)

def handle_comment(self, data: str):
# Check if the comment is unclosed, if so, we need to override position
i = self.line_offset + self.offset + len(data) + 4
if self.rawdata[i:i + 3] != '-->':
self.handle_data('<')
self.override_comment_update = True
return
self.handle_empty_tag('<!--{}-->'.format(data), is_block=True)

def updatepos(self, i: int, j: int) -> int:
if self.override_comment_update:
self.override_comment_update = False
i = 0
j = 1
return super().updatepos(i, j)

def handle_decl(self, data: str):
self.handle_empty_tag('<!{}>'.format(data), is_block=True)

Expand All @@ -278,7 +293,11 @@ def parse_html_declaration(self, i: int) -> int:
if self.rawdata[i:i+3] == '<![' and not self.rawdata[i:i+9] == '<![CDATA[':
# We have encountered the bug in #1534 (Python bug `gh-77057`).
# Provide an override until we drop support for Python < 3.13.
return self.parse_bogus_comment(i)
result = self.parse_bogus_comment(i)
if result == -1:
self.handle_data(self.rawdata[i:i + 1])
return i + 1
return result
return super().parse_html_declaration(i)
# This is not the beginning of a raw block so treat as plain data
# and avoid consuming any tags which may follow (see #1066).
Expand Down Expand Up @@ -313,7 +332,8 @@ def parse_starttag(self, i: int) -> int: # pragma: no cover
self.__starttag_text = None
endpos = self.check_for_whole_start_tag(i)
if endpos < 0:
return endpos
self.handle_data(self.rawdata[i:i + 1])
return i + 1
rawdata = self.rawdata
self.__starttag_text = rawdata[i:endpos]

Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py{39, 310, 311, 312, 313}, pypy{39, 310}, pygments, flake8, checkspelling, pep517check, checklinks
envlist = py{39, 310, 311, 312, 313, py314}, pypy{39, 310}, pygments, flake8, checkspelling, pep517check, checklinks
isolated_build = True

[testenv]
Expand Down