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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
__pycache__/
.idea
*.dist-info/
*.egg-info/
build/
logs/
.DS_Store
18 changes: 18 additions & 0 deletions pth-tester/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# pth-tester

This is a package that tests whether `.pth` files are correctly processed on
import. It is not designed to be published; it is only useful in the context of
the testbed app.

When installed, it includes a `.pth` file that invokes the `pth_tester.init()` method.
This sets the `initialized` attribute of the module to `True`. In this way, it is
possible to tell if `.pth` handling has occurred on app startup.

This project has been compiled into a wheel, stored in the `wheels` directory
of the top-level directory. The wheel can be rebuilt using:

$ pip install build
$ python -m build --wheel --outdir ../wheels

If you make any modifications to the code for this project, you will need to
rebuild the wheel.
21 changes: 21 additions & 0 deletions pth-tester/pth_tester.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
initialized = False
has_socket = False


# The pth_tester module should be initalized by processing the `.pth` file
# created on installation.
def init():
global initialized
global has_socket

initialized = True

# At the time that the module is initialized, it *should* have access
# to all of the standard library. This might not be true, depending on
# the initialization order of the site module and sys.path.
try:
import socket # NOQA: F401

has_socket = True
except ImportError:
pass
8 changes: 8 additions & 0 deletions pth-tester/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[build-system]
requires = ["setuptools==78.0.2", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "x-pth-tester"
version = "2025.3.26"
classifiers = ["Private :: Do Not Upload"]
44 changes: 44 additions & 0 deletions pth-tester/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import os

import setuptools
from setuptools.command.install import install


# Copied from setuptools:
# (https://github.com/pypa/setuptools/blob/7c859e017368360ba66c8cc591279d8964c031bc/setup.py#L40C6-L82)
class install_with_pth(install):
"""
Custom install command to install a .pth file.

This hack is necessary because there's no standard way to install behavior
on startup (and it's debatable if there should be one). This hack (ab)uses
the `extra_path` behavior in Setuptools to install a `.pth` file with
implicit behavior on startup.

The original source strongly recommends against using this behavior.
"""

_pth_name = "_pth_tester"
_pth_contents = "import pth_tester; pth_tester.init()"

def initialize_options(self):
install.initialize_options(self)
self.extra_path = self._pth_name, self._pth_contents

def finalize_options(self):
install.finalize_options(self)
self._restore_install_lib()

def _restore_install_lib(self):
"""
Undo secondary effect of `extra_path` adding to `install_lib`
"""
suffix = os.path.relpath(self.install_lib, self.install_libbase)

if suffix.strip() == self._pth_contents.strip():
self.install_lib = self.install_libbase


setuptools.setup(
cmdclass={"install": install_with_pth},
)
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ sources = ["src/testbed"]
test_sources = ["tests"]

requires = [
"x-pth-tester",

# Cryptography provides an ABI3 wheel for all desktop platforms, but requires cffi which doesn't.
"""cryptography; \
(platform_system != 'iOS' and platform_system != 'Android' and python_version < '3.14') \
Expand Down
25 changes: 25 additions & 0 deletions tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,3 +444,28 @@ def test_zoneinfo():

dt = datetime(2022, 5, 4, 13, 40, 42, tzinfo=ZoneInfo("Australia/Perth"))
assert str(dt) == "2022-05-04 13:40:42+08:00"


def test_pth_handling():
".pth files installed by a package are processed"
import pth_tester

# The pth_tester module should be "initialized" as a result of
# processing the .pth file created when the package is installed.
assert pth_tester.initialized

# When the .pth file is processed, the full standard library should be
# available. Check if the initialization process could import socket.
if sys.platform == "android" or hasattr(sys, "getandroidapilevel"):
# Android is known to have an issue with .pth/sys.path ordering that
# causes this test to fail. For now, accept this as an XFAIL; if it
# passes, fail as an indicator that the bug has been resolved, and we
# can simplify the test.
if pth_tester.has_socket:
pytest.fail("Android .pth handling bug has been resolved.")
else:
pytest.xfail(
"On Android, .pth files are processed before sys.path is finalized."
)
else:
assert pth_tester.has_socket
Binary file added wheels/x_pth_tester-2025.3.26-py3-none-any.whl
Binary file not shown.