Skip to content

Commit a062fb4

Browse files
committed
Replace urllib.request.url2pathname with local shim for Python 3.14 (#7240)
In Python 3.14, `url2pathname` was rewritten to be a full file-URL parser per RFC 8089 (python/cpython#126838). The new implementation prepends `file:` to the input and re-parses it with `urlsplit`, then validates that the URL authority resolves to localhost. This breaks our usage because we pass `urlparse(uri).path` — a pre-parsed path component — not a full URL. The new `url2pathname` misinterprets this input, and raises: URLError: file:// scheme is supported only on localhost Since we already validate that the scheme is `file://` before extracting the path, we replace the stdlib import with a local `url2pathname` shim that preserves the simple Python <= 3.13 behavior.
1 parent f3097c6 commit a062fb4

4 files changed

Lines changed: 29 additions & 2 deletions

File tree

src/aiida/common/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from .links import *
2323
from .log import *
2424
from .progress_reporter import *
25+
from .utils import *
2526

2627
__all__ = (
2728
'AIIDA_LOGGER',
@@ -87,6 +88,7 @@
8788
'override_log_level',
8889
'set_progress_bar_tqdm',
8990
'set_progress_reporter',
91+
'url2pathname',
9092
'validate_link_label',
9193
)
9294

src/aiida/common/utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
from __future__ import annotations
1212

13+
__all__ = ('url2pathname',)
14+
1315
import filecmp
1416
import inspect
1517
import io
@@ -659,3 +661,25 @@ def batch_iter(
659661
# balance between memory efficiency and database round-trip overhead. Setting it too low increases
660662
# the number of database queries needed, while setting it too high increases memory consumption.
661663
DEFAULT_BATCH_SIZE: int = 1000
664+
665+
# To obtain pre py3.14 behavior of url2pathname we copied the code to here
666+
# https://github.com/python/cpython/blob/1a2b0fb3e5eac4e767e6bbb0b2c3cedaedafc07b/Lib/urllib/request.py#L1664-L1679
667+
# Only minor changes were applied to conform with mypy
668+
if os.name == 'nt':
669+
from nturl2path import url2pathname
670+
else:
671+
from urllib.parse import unquote
672+
673+
def url2pathname(url: str) -> str:
674+
"""OS-specific conversion from a relative URL of the 'file' scheme
675+
to a file system path; not recommended for general use."""
676+
if url[:3] == '///':
677+
# URL has an empty authority section, so the path begins on the
678+
# third character.
679+
url = url[2:]
680+
elif url[:12] == '//localhost/':
681+
# Skip past 'localhost' authority.
682+
url = url[11:]
683+
encoding = sys.getfilesystemencoding()
684+
errors = sys.getfilesystemencodeerrors()
685+
return unquote(url, encoding=encoding, errors=errors)

src/aiida/manage/configuration/profile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,8 @@ def repository_path(self) -> pathlib.Path:
214214
:return: absolute filepath of the profile's file repository
215215
"""
216216
from urllib.parse import urlparse
217-
from urllib.request import url2pathname
218217

218+
from aiida.common.utils import url2pathname
219219
from aiida.common.warnings import warn_deprecation
220220

221221
warn_deprecation('This method has been deprecated', version=3)

src/aiida/storage/psql_dos/backend.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
def get_filepath_container(profile: Profile) -> pathlib.Path:
5050
"""Return the filepath of the disk-object store container."""
5151
from urllib.parse import urlparse
52-
from urllib.request import url2pathname
52+
53+
from aiida.common.utils import url2pathname
5354

5455
try:
5556
parts = urlparse(profile.storage_config['repository_uri'])

0 commit comments

Comments
 (0)