Skip to content

Commit 82a5115

Browse files
authored
Add a test of .pth handling. (#118)
1 parent 76c16e6 commit 82a5115

8 files changed

+119
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
__pycache__/
22
.idea
33
*.dist-info/
4+
*.egg-info/
45
build/
56
logs/
67
.DS_Store

pth-tester/README.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# pth-tester
2+
3+
This is a package that tests whether `.pth` files are correctly processed on
4+
import. It is not designed to be published; it is only useful in the context of
5+
the testbed app.
6+
7+
When installed, it includes a `.pth` file that invokes the `pth_tester.init()` method.
8+
This sets the `initialized` attribute of the module to `True`. In this way, it is
9+
possible to tell if `.pth` handling has occurred on app startup.
10+
11+
This project has been compiled into a wheel, stored in the `wheels` directory
12+
of the top-level directory. The wheel can be rebuilt using:
13+
14+
$ pip install build
15+
$ python -m build --wheel --outdir ../wheels
16+
17+
If you make any modifications to the code for this project, you will need to
18+
rebuild the wheel.

pth-tester/pth_tester.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
initialized = False
2+
has_socket = False
3+
4+
5+
# The pth_tester module should be initalized by processing the `.pth` file
6+
# created on installation.
7+
def init():
8+
global initialized
9+
global has_socket
10+
11+
initialized = True
12+
13+
# At the time that the module is initialized, it *should* have access
14+
# to all of the standard library. This might not be true, depending on
15+
# the initialization order of the site module and sys.path.
16+
try:
17+
import socket # NOQA: F401
18+
19+
has_socket = True
20+
except ImportError:
21+
pass

pth-tester/pyproject.toml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[build-system]
2+
requires = ["setuptools==78.0.2", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "x-pth-tester"
7+
version = "2025.3.26"
8+
classifiers = ["Private :: Do Not Upload"]

pth-tester/setup.py

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
3+
import setuptools
4+
from setuptools.command.install import install
5+
6+
7+
# Copied from setuptools:
8+
# (https://github.com/pypa/setuptools/blob/7c859e017368360ba66c8cc591279d8964c031bc/setup.py#L40C6-L82)
9+
class install_with_pth(install):
10+
"""
11+
Custom install command to install a .pth file.
12+
13+
This hack is necessary because there's no standard way to install behavior
14+
on startup (and it's debatable if there should be one). This hack (ab)uses
15+
the `extra_path` behavior in Setuptools to install a `.pth` file with
16+
implicit behavior on startup.
17+
18+
The original source strongly recommends against using this behavior.
19+
"""
20+
21+
_pth_name = "_pth_tester"
22+
_pth_contents = "import pth_tester; pth_tester.init()"
23+
24+
def initialize_options(self):
25+
install.initialize_options(self)
26+
self.extra_path = self._pth_name, self._pth_contents
27+
28+
def finalize_options(self):
29+
install.finalize_options(self)
30+
self._restore_install_lib()
31+
32+
def _restore_install_lib(self):
33+
"""
34+
Undo secondary effect of `extra_path` adding to `install_lib`
35+
"""
36+
suffix = os.path.relpath(self.install_lib, self.install_libbase)
37+
38+
if suffix.strip() == self._pth_contents.strip():
39+
self.install_lib = self.install_libbase
40+
41+
42+
setuptools.setup(
43+
cmdclass={"install": install_with_pth},
44+
)

pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ sources = ["src/testbed"]
1919
test_sources = ["tests"]
2020

2121
requires = [
22+
"x-pth-tester",
23+
2224
# Cryptography provides an ABI3 wheel for all desktop platforms, but requires cffi which doesn't.
2325
"""cryptography; \
2426
(platform_system != 'iOS' and platform_system != 'Android' and python_version < '3.14') \

tests/test_common.py

+25
Original file line numberDiff line numberDiff line change
@@ -444,3 +444,28 @@ def test_zoneinfo():
444444

445445
dt = datetime(2022, 5, 4, 13, 40, 42, tzinfo=ZoneInfo("Australia/Perth"))
446446
assert str(dt) == "2022-05-04 13:40:42+08:00"
447+
448+
449+
def test_pth_handling():
450+
".pth files installed by a package are processed"
451+
import pth_tester
452+
453+
# The pth_tester module should be "initialized" as a result of
454+
# processing the .pth file created when the package is installed.
455+
assert pth_tester.initialized
456+
457+
# When the .pth file is processed, the full standard library should be
458+
# available. Check if the initialization process could import socket.
459+
if sys.platform == "android" or hasattr(sys, "getandroidapilevel"):
460+
# Android is known to have an issue with .pth/sys.path ordering that
461+
# causes this test to fail. For now, accept this as an XFAIL; if it
462+
# passes, fail as an indicator that the bug has been resolved, and we
463+
# can simplify the test.
464+
if pth_tester.has_socket:
465+
pytest.fail("Android .pth handling bug has been resolved.")
466+
else:
467+
pytest.xfail(
468+
"On Android, .pth files are processed before sys.path is finalized."
469+
)
470+
else:
471+
assert pth_tester.has_socket
1.64 KB
Binary file not shown.

0 commit comments

Comments
 (0)