Skip to content

gh-158540: Add the profiled script's directory to sys.path - #158548

Merged
pablogsal merged 5 commits into
python:mainfrom
hetaozdh:fix-sampling-script-path-main
Oct 5, 2026
Merged

pablogsal merged 5 commits into
python:mainfrom
hetaozdh:fix-sampling-script-path-main

Conversation

@hetaozdh

@hetaozdh hetaozdh commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Fixes gh-158540.

When a script is executed with python -m profiling.sampling run script.py
from another directory, the script cannot import modules placed next to it:
_sync_coordinator._execute_script() runs it with the working directory
(added by _setup_environment() for the -m case) as sys.path[0]
instead of the script's own directory.

This change adds the script's directory to sys.path in _execute_script(),
matching the behavior of python script.py.

Added a regression test in
Lib/test/test_profiling/test_sampling_profiler/test_sync_coordinator.py
that runs the coordinator on a script importing a sibling module, plus a
Misc/NEWS.d entry.

Tests:

  • ./python -m test test_profiling
  • ./python Tools/patchcheck/patchcheck.py

@hetaozdh
hetaozdh requested a review from pablogsal as a code owner October 1, 2026 05:01
@python-cla-bot

python-cla-bot Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

When a script is profiled with ``python -m profiling.sampling run
script.py`` from another directory, the script cannot import modules
placed next to it, because ``_sync_coordinator._execute_script()``
executes it with the working directory (added by
``_setup_environment()`` for the module case) as ``sys.path[0]``
instead of the script's own directory.

Make the script's directory importable in ``_execute_script()``,
matching the behavior of ``python script.py``.

Add a regression test that runs the coordinator on a script importing
a sibling module.
@hetaozdh
hetaozdh force-pushed the fix-sampling-script-path-main branch from 6c4dd14 to 16ff1ec Compare October 1, 2026 05:40
Comment thread Lib/profiling/sampling/_sync_coordinator.py Outdated
Co-authored-by: Eduardo Villalpando Mello <eduardo.villalpando.mello@gmail.com>
Comment on lines +171 to +174
# Put directory containing the script (not the current working directory) on sys.path so that modules next to it can be imported. Similar to ``python script.py``
script_dir = os.path.dirname(script_path)
if script_dir not in sys.path:
sys.path.insert(0, script_dir)

@maurycy maurycy Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about this edge case?

2026-10-01T12:57:30.615506000+0200 maurycy@gimel /Users/maurycy/work/cpython-pr158548 (pr158548 91c5ab3?) % tree w
w
├── link.py -> sub/where.py
└── sub
    ├── helper.py
    └── where.py

2 directories, 3 files
2026-10-01T12:57:32.453682000+0200 maurycy@gimel /Users/maurycy/work/cpython-pr158548 (pr158548 91c5ab3?) % sudo ./python.exe -m profiling.sampling run w/link.py
Traceback (most recent call last):
  File "<frozen runpy>", line 201, in _run_module_as_main
  File "<frozen runpy>", line 87, in _run_code
  File "/Users/maurycy/work/cpython-pr158548/Lib/profiling/sampling/_sync_coordinator.py", line 261, in <module>
    main()
    ~~~~^^
  File "/Users/maurycy/work/cpython-pr158548/Lib/profiling/sampling/_sync_coordinator.py", line 254, in main
    _execute_script(script_path, script_args, cwd)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maurycy/work/cpython-pr158548/Lib/profiling/sampling/_sync_coordinator.py", line 201, in _execute_script
    exec(code, main_module.__dict__)
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/maurycy/work/cpython-pr158548/w/link.py", line 1, in <module>
    import helper
ModuleNotFoundError: No module named 'helper'
2026-10-01T12:57:43.707720000+0200 maurycy@gimel /Users/maurycy/work/cpython-pr158548 (pr158548 91c5ab3?) % ./python.exe -VV
Python 3.16.0a0 (heads/pr158548:91c5ab3e096, Oct  1 2026, 10:22:50) [Clang 21.0.0 (clang-2100.3.34.2)]

I'm wondering if this might be an approach to mimic:

cpython/Lib/pdb.py

Lines 192 to 197 in 763b6ed

self._target = self._safe_realpath(target)
# If PYTHONSAFEPATH (-P) is not set, sys.path[0] is the directory
# of pdb, and we should replace it with the directory of the script
if not sys.flags.safe_path:
sys.path[0] = os.path.dirname(self._target)

@hetaozdh hetaozdh Oct 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment. Fixed in 264a36c

@maurycy maurycy Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A note for the reviewer is that there are two approaches in the stdlib.

pdb does not insert but replace:

sys.path[0] = os.path.dirname(self._target)

Same as trace:

sys.path[0] = os.path.dirname(opts.progname)

While profile does insert:

sys.path.insert(0, os.path.dirname(progname))

… sys.path

``python script.py`` resolves symlinks when computing ``sys.path[0]``, so
a script reached through a symlink (``link.py -> sub/where.py``) imports
modules from the real script's directory.  Apply ``os.path.realpath()``
before taking the directory name, and make sure the result is placed at
the front of ``sys.path`` even if it was already listed.

Add a regression test for a symlinked script.
@hetaozdh
hetaozdh force-pushed the fix-sampling-script-path-main branch from d9d8e40 to c8242dd Compare October 1, 2026 17:06
@pablogsal
pablogsal merged commit 3f02aab into python:main Oct 5, 2026
51 checks passed
@pablogsal pablogsal added the needs backport to 3.15 bugs and security fixes label Oct 5, 2026
@miss-islington-app

Copy link
Copy Markdown

Thanks @hetaozdh for the PR, and @pablogsal for merging it 🌮🎉.. I'm working now to backport this PR to: 3.15.
🐍🍒⛏🤖

@bedevere-app

bedevere-app Bot commented Oct 5, 2026

Copy link
Copy Markdown

GH-158844 is a backport of this pull request to the 3.15 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.15 bugs and security fixes label Oct 5, 2026
pablogsal pushed a commit that referenced this pull request Oct 5, 2026
…-158548) (#158844)

gh-158540: Add the profiled script's directory to sys.path (GH-158548)

* gh-158540: Add the profiled script's directory to sys.path

When a script is profiled with ``python -m profiling.sampling run
script.py`` from another directory, the script cannot import modules
placed next to it, because ``_sync_coordinator._execute_script()``
executes it with the working directory (added by
``_setup_environment()`` for the module case) as ``sys.path[0]``
instead of the script's own directory.

Make the script's directory importable in ``_execute_script()``,
matching the behavior of ``python script.py``.

Add a regression test that runs the coordinator on a script importing
a sibling module.

* Update _sync_coordinator.py Comment simplified



* gh-158540: Resolve symlinks when adding the script directory to sys.path

``python script.py`` resolves symlinks when computing ``sys.path[0]``, so
a script reached through a symlink (``link.py -> sub/where.py``) imports
modules from the real script's directory.  Apply ``os.path.realpath()``
before taking the directory name, and make sure the result is placed at
the front of ``sys.path`` even if it was already listed.

Add a regression test for a symlinked script.

---------
(cherry picked from commit 3f02aab)

Co-authored-by: he_tao <53343436+hetaozdh@users.noreply.github.com>
Co-authored-by: Eduardo Villalpando Mello <eduardo.villalpando.mello@gmail.com>
pablogsal added a commit to pablogsal/cpython that referenced this pull request Oct 5, 2026
pablogsal added a commit that referenced this pull request Oct 11, 2026
…58882)

* [3.15] gh-153364: Make frame, coroutine, and task-waiter chain walks iterative and bounded (GH-153365) (#158813)

gh-153364: Make frame, coroutine, and task-waiter chain walks iterative and bounded (GH-153365)

* let me declare single limit

* use our new limit in process_frame_chain()

* add it in parse_async_frame_chain()

* parse_coro_chain()

* NEWS

* async in the message?

* test

* no race

* process_task_awaited_by

* process_task_awaited_by limit test

* NEWS

* MAX_TASK_WAITER_CHAIN_DEPTH

* TASK_WAITER_CHAIN_DEPTH in test

* TASK_WAITER_CHAIN_DEPTH 256

* prevent the drift with the comment

* better naming, better style

* MAX_TASK_WAITER_CHAIN_DEPTH comment

* task-waiter iterative bfs walk

* iterative coro-walk

* nicer news

* 1 << 14

* comment

* unused read_Py_ssize_t

* fix tombstones

* simplify

* correct msg

* better test

* news for tombstones

* left-over from when testing buggy version

* redundant new line
(cherry picked from commit e0861c6)

Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
(cherry picked from commit 9e401cf)

* [3.15] gh-155811: Add a seqcount to `gc_stats` to prevent torn reads (GH-155828) (#158829)

* update_seq

* no need for XCHGL, MOVL is enough?

* gh-155811: Retry an inconsistent GC snapshot once

---------
(cherry picked from commit 5fecd44)

Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 0281240)

* [3.15] gh-151292: `_remote_debugging`: Do not corrupt the binary file when hitting `OverflowError` (GH-152892) (#158830)

gh-151292: `_remote_debugging`: Do not corrupt the binary file when hitting `OverflowError` (GH-152892)

* the kolektor

* test

* better test

* news

* =Q, move const to the base, not self.running

* gh-151292: Track binary writer finalization state

---------
(cherry picked from commit f839c06)

Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
(cherry picked from commit 8a7c23f)

* [3.15] gh-158583: Fix uninitialized memory read in bytes.fromhex() (GH-158584) (#158691)

gh-158583: Fix uninitialized memory read in bytes.fromhex() (GH-158584)
(cherry picked from commit 9d22a53)

Co-authored-by: Victor Stinner <vstinner@python.org>
(cherry picked from commit 4f7af46)

* [3.15] gh-154194: Degrade frames in Tachyon instead of failing the sample (GH-154195) (#158831)

* gh-154194: Degrade frames in Tachyon instead of failing the sample (#154195)

* degrade gracefully

* news

* better NEWS wording

* do not raise on MAX_REMOTE_STR_READ

* bye MAX_REMOTE_STR_READ

* fix -m asyncio ps|pstree

* test truncation and linetable sentinel

* simpler

* simpler

* redundant now

* respect #157790 in the news

---------

Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
(cherry picked from commit 7d25916)

* Preserve the stable ABI when creating fallback frame names

---------

Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
(cherry picked from commit c27f494)

* [3.15] Add MSan to CI (GH-158625) (#158832)

Run a job that the test suite with MSan to the CI (#158625)

* Run the test suite with MSan in CI

* Additional fixes

* Add `_Py_MSAN_UNPOISON_STRING`

* Apply Victor's suggestions

* Apply Victor's suggestions

---------

(cherry picked from commit b93fb19)

Co-authored-by: Stan Ulbrych <stan@python.org>
Co-authored-by: Victor Stinner <victor.stinner@gmail.com>
(cherry picked from commit 1bc78de)

* [3.15] gh-156810: Write the profiler's collapsed-stack export as UTF-8 (GH-156811) (#156814)

gh-156810: Write the profiler's collapsed-stack export as UTF-8 (GH-156811)
(cherry picked from commit c3706f4)

Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com>
(cherry picked from commit fce28da)

* [3.15] gh-158552: Wait for Windows threads to suspend before blocking sampling (GH-158802) (#158845)

gh-158552: Wait for Windows threads to suspend before blocking sampling (GH-158802)

* gh-158552: Wait for Windows threads to suspend before blocking sampling

* Use a named Windows thread enumeration status constant
(cherry picked from commit 1643525)

Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
(cherry picked from commit 31288da)

* [3.15] gh-152721: Fix quadratic RLE replay time in the profiling binary reader (GH-152722) (#158850)

Backport of GH-152722.

Co-authored-by: tonghuaroot <tonghuaroot@gmail.com>
(cherry picked from commit ebaca2a)

* [3.15] gh-156545: Fix flamegraph export RecursionError on deeply recursive programs (GH-156546) (#158851)

Backport of GH-156546.

Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com>
(cherry picked from commit 0ac7217)

* [3.15] gh-158540: Add the profiled script's directory to sys.path (GH-158548) (#158844)

gh-158540: Add the profiled script's directory to sys.path (GH-158548)

* gh-158540: Add the profiled script's directory to sys.path

When a script is profiled with ``python -m profiling.sampling run
script.py`` from another directory, the script cannot import modules
placed next to it, because ``_sync_coordinator._execute_script()``
executes it with the working directory (added by
``_setup_environment()`` for the module case) as ``sys.path[0]``
instead of the script's own directory.

Make the script's directory importable in ``_execute_script()``,
matching the behavior of ``python script.py``.

Add a regression test that runs the coordinator on a script importing
a sibling module.

* Update _sync_coordinator.py Comment simplified

* gh-158540: Resolve symlinks when adding the script directory to sys.path

``python script.py`` resolves symlinks when computing ``sys.path[0]``, so
a script reached through a symlink (``link.py -> sub/where.py``) imports
modules from the real script's directory.  Apply ``os.path.realpath()``
before taking the directory name, and make sure the result is placed at
the front of ``sys.path`` even if it was already listed.

Add a regression test for a symlinked script.

---------
(cherry picked from commit 3f02aab)

Co-authored-by: he_tao <53343436+hetaozdh@users.noreply.github.com>
Co-authored-by: Eduardo Villalpando Mello <eduardo.villalpando.mello@gmail.com>
(cherry picked from commit f52d831)

* [3.15] gh-153838: Skip non-regular source files in the heatmap exporter (GH-153839) (#158853)

Backport of GH-153839.

Co-authored-by: tonghuaroot <tonghuaroot@gmail.com>
(cherry picked from commit 48998df)

* [3.15] gh-158539: Fix exception mode missing handlers in generators/coroutines (GH-158581) (#158852)

* [3.15] gh-158539: Fix exception mode missing handlers in generators/coroutines (GH-158581)

Backport of GH-158581.

Co-authored-by: LucasZhou <donghao.zhou@outlook.com>

* [3.15] gh-158539: Use portable static assertion messages

* [3.15] gh-158539: Keep layout assertions with debug-offset validation

* [3.15] gh-158539: Use the platform guard for in-process inspection tests

---------

Co-authored-by: LucasZhou <donghao.zhou@outlook.com>
(cherry picked from commit d625ecb)

* [3.15] gh-158522: Fix truncated stack for a task whose coroutine recurses (GH-158526) (#158870)

Co-authored-by: Timofei Ivankov <128279579+deadlovelll@users.noreply.github.com>
(cherry picked from commit a904b39)

* gh-156545, gh-158539: Fix deep flamegraph export on small C stacks and macOS runtime lookup (#158874)

(cherry picked from commit 114de19)

Include the C-stack test helper from main, introduced by
ce5ae29, which the regression test
requires but 3.15 does not yet provide.

---------

Co-authored-by: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Co-authored-by: Maurycy Pawłowski-Wieroński <maurycy@maurycy.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Stan Ulbrych <stan@python.org>
Co-authored-by: Victor Stinner <victor.stinner@gmail.com>
Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com>
Co-authored-by: he_tao <53343436+hetaozdh@users.noreply.github.com>
Co-authored-by: Eduardo Villalpando Mello <eduardo.villalpando.mello@gmail.com>
Co-authored-by: LucasZhou <donghao.zhou@outlook.com>
Co-authored-by: Timofei Ivankov <128279579+deadlovelll@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

profiling.sampling: run script.py does not put the script's directory on sys.path, so sibling imports fail

4 participants