commit 83c7a1f81cd6161b7f4622dc6cfcf263656a9129c6b61c2d5bcd61a8937951be Author: Steve Kowalik Date: Wed Nov 5 03:33:41 2025 +0000 - Update to 5.0.0: * ENH: Add support for sys.monitoring (Python >= 3.12) * FIX: Fixed issue when calling kernprof with neither the -l nor -b flag * FIX: Fixed auto-profiling of async function definitions * ENH: Added CLI argument -m to kernprof for running a library module as a script * FIX: Fixed explicit profiling of class methods; added handling for profiling static, bound, and partial methods, functools.partial objects, (cached) properties, and async generator functions * FIX: Fixed namespace bug when running kernprof -m on certain modules. * FIX: Fixed @contextlib.contextmanager bug where the cleanup code (e.g. restoration of sys attributes) is not run if exceptions occurred inside the context * ENH: Added CLI arguments -c to kernprof for (auto-)profiling module/package/inline-script execution instead of that of script files; passing '-' as the script-file name now also reads from and profiles stdin * ENH: In Python >=3.11, profiled objects are reported using their qualified name. * ENH: Highlight final summary using rich if enabled * ENH: Made it possible to use multiple profiler instances simultaneously * ENH: various improvements related to auto-profiling: * FIX: Fixed line tracing for Cython code; superseded use of the legacy tracing system with sys.monitoring * FIX: Tracing-system-related fixes * ENH: Added capability to parse TOML config files for defaults - Add patch no-python-in-path.patch: * Do not search the path for python. - Add patch support-python314.patch: * Support Python 3.14 sys.monitoring changes. OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/python-line_profiler?expand=0&rev=33 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9b03811 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,23 @@ +## Default LFS +*.7z filter=lfs diff=lfs merge=lfs -text +*.bsp filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.gem filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.jar filter=lfs diff=lfs merge=lfs -text +*.lz filter=lfs diff=lfs merge=lfs -text +*.lzma filter=lfs diff=lfs merge=lfs -text +*.obscpio filter=lfs diff=lfs merge=lfs -text +*.oxt filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.rpm filter=lfs diff=lfs merge=lfs -text +*.tbz filter=lfs diff=lfs merge=lfs -text +*.tbz2 filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.txz filter=lfs diff=lfs merge=lfs -text +*.whl filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57affb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.osc diff --git a/line_profiler-4.2.0.tar.gz b/line_profiler-4.2.0.tar.gz new file mode 100644 index 0000000..21b14d6 --- /dev/null +++ b/line_profiler-4.2.0.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09e10f25f876514380b3faee6de93fb0c228abba85820ba1a591ddb3eb451a96 +size 199037 diff --git a/line_profiler-5.0.0.tar.gz b/line_profiler-5.0.0.tar.gz new file mode 100644 index 0000000..3b27483 --- /dev/null +++ b/line_profiler-5.0.0.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a80f0afb05ba0d275d9dddc5ff97eab637471167ff3e66dcc7d135755059398c +size 376919 diff --git a/no-python-in-path.patch b/no-python-in-path.patch new file mode 100644 index 0000000..1fb9779 --- /dev/null +++ b/no-python-in-path.patch @@ -0,0 +1,46 @@ +Index: line_profiler-5.0.0/line_profiler/cli_utils.py +=================================================================== +--- line_profiler-5.0.0.orig/line_profiler/cli_utils.py ++++ line_profiler-5.0.0/line_profiler/cli_utils.py +@@ -188,9 +188,7 @@ def get_python_executable(): + Command or path thereto corresponding to + :py:data:`sys.executable`. + """ +- if os.path.samefile(shutil.which('python'), sys.executable): +- return 'python' +- elif os.path.samefile(shutil.which('python3'), sys.executable): ++ if os.path.samefile(shutil.which('python3'), sys.executable): + return 'python3' + else: + return short_string_path(sys.executable) +Index: line_profiler-5.0.0/tests/test_kernprof.py +=================================================================== +--- line_profiler-5.0.0.orig/tests/test_kernprof.py ++++ line_profiler-5.0.0/tests/test_kernprof.py +@@ -184,7 +184,7 @@ def test_kernprof_sys_restoration(capsys + {'^Output to stdout': True, + r"^Wrote .* '.*script\.py\.lprof'": True, + r'^Inspect results with:''\n' +- r'python -m line_profiler .*script\.py\.lprof': True, ++ r'.*python.* -m line_profiler .*script\.py\.lprof': True, + r'line_profiler\.autoprofile\.autoprofile' + r'\.run\(\n(?:.+,\n)*.*\)': False, + r'^\[kernprof .*\]': False, +@@ -194,7 +194,7 @@ def test_kernprof_sys_restoration(capsys + {'^Output to stdout': True, + r"^Wrote .* '.*script\.py\.lprof'": True, + r'^Inspect results with:''\n' +- r'python -m line_profiler .*script\.py\.lprof': False, ++ r'.*python.* -m line_profiler .*script\.py\.lprof': False, + r'line_profiler\.autoprofile\.autoprofile' + r'\.run\(\n(?:.+,\n)*.*\)': False, + r'^\[kernprof .*\]': False, +@@ -204,7 +204,7 @@ def test_kernprof_sys_restoration(capsys + {'^Output to stdout': True, + r"^\[kernprof .*\] Wrote .* '.*script\.py\.lprof'": True, + r'Inspect results with:''\n' +- r'python -m line_profiler .*script\.py\.lprof': False, ++ r'.*python.* -m line_profiler .*script\.py\.lprof': False, + r'line_profiler\.autoprofile\.autoprofile' + r'\.run\(\n(?:.+,\n)*.*\)': True, + r'^Function: main': True}, diff --git a/python-line_profiler.changes b/python-line_profiler.changes new file mode 100644 index 0000000..3e12192 --- /dev/null +++ b/python-line_profiler.changes @@ -0,0 +1,248 @@ +------------------------------------------------------------------- +Wed Nov 5 03:33:11 UTC 2025 - Steve Kowalik + +- Update to 5.0.0: + * ENH: Add support for sys.monitoring (Python >= 3.12) + * FIX: Fixed issue when calling kernprof with neither the -l nor -b flag + * FIX: Fixed auto-profiling of async function definitions + * ENH: Added CLI argument -m to kernprof for running a library module as + a script + * FIX: Fixed explicit profiling of class methods; added handling for + profiling static, bound, and partial methods, functools.partial objects, + (cached) properties, and async generator functions + * FIX: Fixed namespace bug when running kernprof -m on certain modules. + * FIX: Fixed @contextlib.contextmanager bug where the cleanup code (e.g. + restoration of sys attributes) is not run if exceptions occurred inside + the context + * ENH: Added CLI arguments -c to kernprof for (auto-)profiling + module/package/inline-script execution instead of that of script files; + passing '-' as the script-file name now also reads from and profiles + stdin + * ENH: In Python >=3.11, profiled objects are reported using their + qualified name. + * ENH: Highlight final summary using rich if enabled + * ENH: Made it possible to use multiple profiler instances simultaneously + * ENH: various improvements related to auto-profiling: + * FIX: Fixed line tracing for Cython code; superseded use of the legacy + tracing system with sys.monitoring + * FIX: Tracing-system-related fixes + * ENH: Added capability to parse TOML config files for defaults +- Add patch no-python-in-path.patch: + * Do not search the path for python. +- Add patch support-python314.patch: + * Support Python 3.14 sys.monitoring changes. + +------------------------------------------------------------------- +Sun May 4 08:46:41 UTC 2025 - Dirk Müller + +- update to 4.2.0: + * FIX: Fix issue with auto-profile of editable installs #279 + * FIX: Lookup OP-codes instead of hard coding them #284 + * CHANGE: Drop support for Python 3.6 and Python 3.7 + * ENH: Add support for Python 3.13 + +------------------------------------------------------------------- +Tue Nov 26 23:59:21 UTC 2024 - Steve Kowalik + +- Update to 4.1.3: + * FIX: duration summary now respects the stripzeros argument. + * FIX: minor test fixes. + * ENH: building osx wheels for x86 and arm64. + * ENH: documentation improvements. + * Invoke subshell with the current python interpreter + * Respect stripzeros in summary report + * Normalize path before comparison +- Switch to pyproject macros. +- Drop patch use-sys-executable-python.patch, included upstream. + +------------------------------------------------------------------- +Wed Mar 13 15:29:56 UTC 2024 - Dirk Müller + +- skip python 3.9 build + +------------------------------------------------------------------- +Tue Dec 5 12:55:42 UTC 2023 - Dirk Müller + +- update to 4.1.2: + * ENH: Add support for Python 3.12 #246 + * ENH: Add osx universal2 and arm64 wheels + * ENH: Fix issue with integer overflow on 32 bit systems + * FIX: ``get_stats`` is no longer slowed down when profiling + many code sections #236 + * FIX: skipzeros now checks for zero hits instead of zero time + * FIX: Fixed errors in Python 3.11 with duplicate functions. + * FIX: ``show_text`` now increases column sizes or switches to + scientific notation to maintain alignment + * ENH: ``show_text`` now has new options: sort and summarize + * ENH: Added new CLI arguments ``-srm`` to ``line_profiler`` to + control sorting, rich printing, and summary printing. + * ENH: New global ``profile`` function that can be enabled by + ``--profile`` or ``LINE_PROFILE=1``. + * ENH: New auto-profile feature in ``kernprof`` that will + profile all functions in specified modules. + * ENH: Kernprof now outputs instructions on how to view + results. + * ENH: Added readthedocs integration: + https://kernprof.readthedocs.io/en/latest/index.html +- Add patch use-sys-executable-python.patch: + * Use sys.executable, rather than 'python'. + +------------------------------------------------------------------- +Sun Aug 13 21:37:39 UTC 2023 - Dirk Müller + +- restrict to older Cython release + +------------------------------------------------------------------- +Sun Mar 26 19:55:08 UTC 2023 - Dirk Müller + +- update to 4.0.3: + * FIX: Stop requiring bleeding-edge Cython unless necesasry + (for Python 3.12). #206 + +------------------------------------------------------------------- +Mon Feb 20 07:01:56 UTC 2023 - Steve Kowalik + +- Update to 4.0.2: + * FIX: AttributeError on certain methods. #191 + * FIX: Profiling classmethods works again. #183 + * ENH: Python 3.11 is now supported. + * ENH: Profiling overhead is now drastically smaller, thanks to + reimplementing almost all of the tracing callback in C++. + * ENH: Added the ``-i <# of seconds>`` option to the ``kernprof`` script. + * CHANGE: Cython's native cythonize function is now used to compile the + project, instead of scikit-build's convoluted process. + * CHANGE: Due to optimizations done while reimplementing the callback in + C++, the profiler's code_map and last_time attributes now are indexed by + a hash of the code block's bytecode and its line number. + * FIX: filepath test in is_ipython_kernel_cell for Windows #161 + * ADD: setup.py now checks LINE_PROFILER_BUILD_METHOD to determine how + to build binaries + * ADD: LineProfiler.add_function warns if an added function has a + __wrapped__ attribute +- Due to build system change, massively simplify %build. + +------------------------------------------------------------------- +Thu Sep 29 14:50:17 UTC 2022 - Yogalakshmi Arunachalam + +- Update to Version 3.5.2 + * FIX: filepath test in is_ipython_kernel_cell for Windows #161 + * ADD: setup.py now checks LINE_PROFILER_BUILD_METHOD to determine how to build binaries + * ADD: LineProfiler.add_function warns if an added function has a __wrapped__ attribute + +- Update to Version 3.5.1 + * FIX: #19 line profiler now works on async functions again + +- Update to Version 3.5.0 + * FIX: #109 kernprof fails to write to stdout if stdout was replaced + * FIX: Fixes max of an empty sequence error #118 + * Make IPython optional + * FIX: #100 Exception raise ZeroDivisionError + +------------------------------------------------------------------- +Fri Jan 7 09:54:01 UTC 2022 - Ben Greiner + +- Update to 3.4.0 + * Drop support for Python <= 3.5.x + * FIX: #104 issue with new IPython kernels + * Wheels for musllinux are now included + * FIX: Fix bug where lines were not displayed in Jupyter>=6.0 via + #93 +- Release 3.3.1 + * CHANGE: moving forward, new pypi releases will be signed with + the GPG key 2A290272C174D28EA9CA48E9D7224DAF0347B114 for + PyUtils-CI openpyutils@gmail.com. For reference, older versions + were signed with either + 262A1DF005BE5D2D5210237C85CD61514641325F or + 1636DAF294BA22B89DBB354374F166CFA2F39C18. +- Release 3.2.5 + * Include c source files in manifest (#74) +- Fix IPython requirements +- Fix platform install dir + +------------------------------------------------------------------- +Thu Apr 22 12:59:14 UTC 2021 - Markéta Machová + +- update to 3.1.0 + * Restructure into package + * fix Python 3.9 + +------------------------------------------------------------------- +Wed Jan 2 12:35:35 UTC 2019 - Tomáš Chvátal + +- Regenerate cython files to fix build under python 3.7 + +------------------------------------------------------------------- +Mon Oct 29 15:21:25 UTC 2018 - Todd R + +- It doesn't have a direct dependency on prompt_toolkit. + +------------------------------------------------------------------- +Thu Jul 19 15:06:24 UTC 2018 - mcepl@suse.com + +- Switch off tests (they are broken, + https://github.com/rkern/line_profiler/issues/128) + +------------------------------------------------------------------- +Thu May 10 15:23:01 UTC 2018 - toddrme2178@gmail.com + +- Set minimum python3 version + +------------------------------------------------------------------- +Thu May 3 14:07:08 UTC 2018 - toddrme2178@gmail.com + +- Use %license tag + +------------------------------------------------------------------- +Thu Apr 19 17:16:36 UTC 2018 - toddrme2178@gmail.com + +- Update to version 2.1.2 + * ENH: Add support for Python 3.5 coroutines + * ENH: Documentation updates + * ENH: CI for most recent Python versions (3.5, 3.6, 3.6-dev, 3.7-dev, nightly) + * ENH: Add timer unit argument for output time granularity spec + +------------------------------------------------------------------- +Tue Aug 29 21:23:54 UTC 2017 - toddrme2178@gmail.com + +- Update to version 2.0 + * Added support for IPython 5.0+, removed support for IPython <=0.12 +- Update to version 1.1 + * Read source files as bytes. +- Implement single-spec version + +------------------------------------------------------------------- +Tue Nov 11 16:08:30 UTC 2014 - toddrme2178@gmail.com + +- Implement update-alternatives +- Implement unit tests + +------------------------------------------------------------------- +Thu Oct 2 17:50:42 UTC 2014 - termim@gmail.com + +- Update to 1.0 + * `kernprof.py` is now installed as `kernprof`. + * Python 3 support. Thanks to the long-suffering Mikhail Korobov for + being patient. + * Dropped 2.6 as it was too annoying. + * The `stripzeros` and `add_module` options. Thanks to Erik Tollerud + for contributing it. + * Support for IPython cell blocks. Thanks to Michael Forbes for adding + this feature. + * Better warnings when building without Cython. Thanks to David + Cournapeau for spotting this. + +------------------------------------------------------------------- +Mon Mar 10 15:05:50 UTC 2014 - toddrme2178@gmail.com + +- Update to 1.0b3 + * Profile generators. + * Update for compatibility with newer versions of Cython. + Thanks to Ondrej Certik for spotting the bug. + * Update IPython compatibility for 0.11+. Thanks to Yaroslav + Halchenko and others for providing the updated imports. + +------------------------------------------------------------------- +Fri Jul 1 08:10:03 UTC 2011 - saschpe@suse.de + +- Initial version + diff --git a/python-line_profiler.spec b/python-line_profiler.spec new file mode 100644 index 0000000..57c2639 --- /dev/null +++ b/python-line_profiler.spec @@ -0,0 +1,96 @@ +# +# spec file for package python-line_profiler +# +# Copyright (c) 2025 SUSE LLC and contributors +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via https://bugs.opensuse.org/ +# + + +%{?sle15_python_module_pythons} +Name: python-line_profiler +Version: 5.0.0 +Release: 0 +Summary: Line-by-line profiler +License: BSD-3-Clause +URL: https://github.com/pyutils/line_profiler +Source: https://files.pythonhosted.org/packages/source/l/line_profiler/line_profiler-%{version}.tar.gz +# PATCH-FIX-OPENSUSE We do not ship bare python, so don't look for it +Patch0: no-python-in-path.patch +# PATCH-FIX-UPSTREAM gh#pyutils/line_profiler#369 +Patch1: support-python314.patch +BuildRequires: %{python_module Cython} +BuildRequires: %{python_module devel} +BuildRequires: %{python_module ipython} +BuildRequires: %{python_module pip} +BuildRequires: %{python_module pytest} +BuildRequires: %{python_module scikit-build} +BuildRequires: %{python_module setuptools} +BuildRequires: %{python_module ubelt} +BuildRequires: %{python_module wheel} +BuildRequires: cmake +BuildRequires: fdupes +BuildRequires: gcc-c++ +BuildRequires: ninja +BuildRequires: python-rpm-macros +Requires: python-ipython +Requires(post): update-alternatives +Requires(postun): update-alternatives +%python_subpackages + +%description +line_profiler will profile the time individual lines of code take to execute. +The profiler is implemented in C via Cython in order to reduce the overhead of +profiling. + +Also included is the script kernprof.py which can be used to conveniently +profile Python applications and scripts either with line_profiler or with the +function-level profiling tools in the Python standard library. + +%prep +%autosetup -p1 -n line_profiler-%{version} + +%build +export CFLAGS="%{optflags} -fno-strict-aliasing" +# remove shebangs +sed -i '1{/env python/d}' line_profiler/line_profiler.py kernprof.py +%pyproject_wheel + +%install +%pyproject_install +%python_clone -a %{buildroot}%{_bindir}/kernprof +%python_compileall +%python_expand %fdupes %{buildroot}%{$python_sitearch} + +%post +%python_install_alternative kernprof + +%postun +%python_uninstall_alternative kernprof + +%check +export PATH=%{buildroot}%{_bindir}:$PATH +mv line_profiler line_profiler-do-not-import +# cython_examples not shipped +%pytest_arch -k 'not cython_source' +mv line_profiler-do-not-import line_profiler + +%files %{python_files} +%doc README.rst +%license LICENSE.txt LICENSE_Python.txt +%python_alternative %{_bindir}/kernprof +%{python_sitearch}/line_profiler +%{python_sitearch}/line_profiler-%{version}.dist-info +%{python_sitearch}/kernprof.py +%pycache_only %{python_sitearch}/__pycache__/kernprof.*.pyc + +%changelog diff --git a/support-python314.patch b/support-python314.patch new file mode 100644 index 0000000..e70f5b2 --- /dev/null +++ b/support-python314.patch @@ -0,0 +1,258 @@ +From 0b32904897bff5d91886cf2476e3bb98638cb31e Mon Sep 17 00:00:00 2001 +From: joncrall +Date: Tue, 29 Jul 2025 18:35:33 -0400 +Subject: [PATCH 1/9] Update xcookie + +--- + .github/workflows/tests.yml | 46 ++++++++++++++++++++++++------------- + docs/source/conf.py | 6 +++-- + 2 files changed, 34 insertions(+), 18 deletions(-) + +Index: line_profiler-5.0.0/pyproject.toml +=================================================================== +--- line_profiler-5.0.0.orig/pyproject.toml ++++ line_profiler-5.0.0/pyproject.toml +@@ -30,7 +30,7 @@ omit =[ + ] + + [tool.cibuildwheel] +-build = "cp38-* cp39-* cp310-* cp311-* cp312-* cp313-*" ++build = "cp38-* cp39-* cp310-* cp311-* cp312-* cp313-* cp314-*" + skip = ["*-win32", "cp313-musllinux_i686"] + build-frontend = "build" + build-verbosity = 1 +Index: line_profiler-5.0.0/requirements/build.txt +=================================================================== +--- line_profiler-5.0.0.orig/requirements/build.txt ++++ line_profiler-5.0.0/requirements/build.txt +@@ -7,7 +7,7 @@ scikit-build>=0.11.1 + cmake>=3.21.2 + ninja>=1.10.2 + +-cibuildwheel>=2.11.2 ; python_version < '4.0' and python_version >= '3.11' # Python 3.11+ +-cibuildwheel>=2.11.2 ; python_version < '3.11' and python_version >= '3.10' # Python 3.10 +-cibuildwheel>=2.11.2 ; python_version < '3.10' and python_version >= '3.9' # Python 3.9 +-cibuildwheel>=2.11.2 ; python_version < '3.9' and python_version >= '3.8' # Python 3.8 ++cibuildwheel>=3.1.2 ; python_version < '4.0' and python_version >= '3.11' # Python 3.11+ ++cibuildwheel>=3.1.2 ; python_version < '3.11' and python_version >= '3.10' # Python 3.10 ++cibuildwheel>=3.1.2 ; python_version < '3.10' and python_version >= '3.9' # Python 3.9 ++cibuildwheel>=3.1.2 ; python_version < '3.9' and python_version >= '3.8' # Python 3.8 +Index: line_profiler-5.0.0/setup.py +=================================================================== +--- line_profiler-5.0.0.orig/setup.py ++++ line_profiler-5.0.0/setup.py +@@ -304,6 +304,7 @@ if __name__ == '__main__': + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ++ 'Programming Language :: Python :: 3.14', + 'Programming Language :: Python :: Implementation :: CPython', + 'Topic :: Software Development', + ] +Index: line_profiler-5.0.0/line_profiler/_line_profiler.pyx +=================================================================== +--- line_profiler-5.0.0.orig/line_profiler/_line_profiler.pyx ++++ line_profiler-5.0.0/line_profiler/_line_profiler.pyx +@@ -260,7 +260,7 @@ cpdef _code_replace(func, co_code): + code = func.__func__.__code__ + if hasattr(code, 'replace'): + # python 3.8+ +- code = code.replace(co_code=co_code) ++ code = _copy_local_sysmon_events(code, code.replace(co_code=co_code)) + else: + # python <3.8 + co = code +@@ -273,6 +273,30 @@ cpdef _code_replace(func, co_code): + return code + + ++cpdef _copy_local_sysmon_events(old_code, new_code): ++ """ ++ Copy the local events from ``old_code`` over to ``new_code`` where ++ appropriate. ++ ++ Returns: ++ code: ``new_code`` ++ """ ++ try: ++ mon = sys.monitoring ++ except AttributeError: # Python < 3.12 ++ return new_code ++ # Tool ids are integers in the range 0 to 5 inclusive. ++ # https://docs.python.org/3/library/sys.monitoring.html#tool-identifiers ++ NUM_TOOLS = 6 ++ for tool_id in range(NUM_TOOLS): ++ try: ++ events = mon.get_local_events(tool_id, old_code) ++ mon.set_local_events(tool_id, new_code, events) ++ except ValueError: # Tool ID not in use ++ pass ++ return new_code ++ ++ + cpdef int _patch_events(int events, int before, int after): + """ + Patch ``events`` based on the differences between ``before`` and +@@ -370,22 +394,26 @@ cdef class _SysMonitoringState: + mon = sys.monitoring + + # Set prior state ++ # Note: in 3.14.0a1+, calling `sys.monitoring.free_tool_id()` ++ # also calls `.clear_tool_id()`, causing existing callbacks and ++ # code-object-local events to be wiped... so don't call free. ++ # this does have the side effect of not overriding the active ++ # profiling tool name if one is already in use, but it's ++ # probably better this way + self.name = mon.get_tool(self.tool_id) + if self.name is None: + self.events = mon.events.NO_EVENTS ++ mon.use_tool_id(self.tool_id, 'line_profiler') + else: + self.events = mon.get_events(self.tool_id) +- mon.free_tool_id(self.tool_id) +- mon.use_tool_id(self.tool_id, 'line_profiler') + mon.set_events(self.tool_id, self.events | self.line_tracing_events) + +- # Register tracebacks +- for event_id, callback in [ +- (mon.events.LINE, handle_line), +- (mon.events.PY_RETURN, handle_return), +- (mon.events.PY_YIELD, handle_yield), +- (mon.events.RAISE, handle_raise), +- (mon.events.RERAISE, handle_reraise)]: ++ # Register tracebacks and remember the existing ones ++ for event_id, callback in [(mon.events.LINE, handle_line), ++ (mon.events.PY_RETURN, handle_return), ++ (mon.events.PY_YIELD, handle_yield), ++ (mon.events.RAISE, handle_raise), ++ (mon.events.RERAISE, handle_reraise)]: + self.callbacks[event_id] = mon.register_callback( + self.tool_id, event_id, callback) + +@@ -394,12 +422,11 @@ cdef class _SysMonitoringState: + cdef dict wrapped_callbacks = self.callbacks + + # Restore prior state +- mon.free_tool_id(self.tool_id) +- if self.name is not None: +- mon.use_tool_id(self.tool_id, self.name) +- mon.set_events(self.tool_id, self.events) +- self.name = None +- self.events = mon.events.NO_EVENTS ++ mon.set_events(self.tool_id, self.events) ++ if self.name is None: ++ mon.free_tool_id(self.tool_id) ++ self.name = None ++ self.events = mon.events.NO_EVENTS + + # Reset tracebacks + while wrapped_callbacks: +@@ -1118,7 +1145,7 @@ datamodel.html#user-defined-functions + # function (on some instance); + # (re-)pad with no-op + co_code = base_co_code + NOP_BYTES * npad +- code = _code_replace(func, co_code=co_code) ++ code = _code_replace(func, co_code) + try: + func.__code__ = code + except AttributeError as e: +@@ -1155,6 +1182,9 @@ datamodel.html#user-defined-functions + code_hashes.append(code_hash) + # We can't replace the code object on Cython functions, but + # we can *store* a copy with the correct metadata ++ # Note: we don't use `_copy_local_sysmon_events()` here ++ # because Cython shim code objects don't support local ++ # events + code = code.replace(co_filename=cython_source) + profilers_to_update = {self} + # Update `._c_code_map` and `.code_hash_map` with the new line +Index: line_profiler-5.0.0/tests/test_sys_monitoring.py +=================================================================== +--- line_profiler-5.0.0.orig/tests/test_sys_monitoring.py ++++ line_profiler-5.0.0/tests/test_sys_monitoring.py +@@ -6,7 +6,7 @@ from functools import partial + from io import StringIO + from itertools import count + from types import CodeType, ModuleType +-from typing import (Any, Optional, Union, ++from typing import (Any, Optional, Union, Literal, + Callable, Generator, + Dict, FrozenSet, Tuple, + ClassVar) +@@ -754,3 +754,76 @@ def _test_callback_toggle_local_events_h + assert get_loop_hits() == nloop_before_disabling + nloop_after_reenabling + + return n ++ ++ ++@pytest.mark.parametrize('profile_when', ['before', 'after']) ++def test_local_event_preservation( ++ profile_when: Literal['before', 'after']) -> None: ++ """ ++ Check that existing :py:mod:`sys.monitoring` code-local events are ++ preserved when a profiler swaps out the callable's code object. ++ """ ++ prof = LineProfiler(wrap_trace=True) ++ ++ @prof ++ def func0(n: int) -> int: ++ """ ++ This function compiles down to the same bytecode as `func()` and ++ ensure that `prof` does bytecode padding with the latter later. ++ """ ++ x = 0 ++ for n in range(1, n + 1): ++ x += n ++ return x ++ ++ def func(n: int) -> int: ++ x = 0 ++ for n in range(1, n + 1): ++ x += n # Loop body ++ return x ++ ++ def profile() -> None: ++ nonlocal code ++ nonlocal func ++ orig_code = func.__code__ ++ orig_func, func = func, prof(func) ++ code = orig_func.__code__ ++ assert code is not orig_code, ( ++ '`line_profiler` didn\'t overwrite the function\'s code object') ++ ++ lines, first_lineno = inspect.getsourcelines(func) ++ lineno_loop = first_lineno + next( ++ offset for offset, line in enumerate(lines) ++ if line.rstrip().endswith('# Loop body')) ++ names = {func.__name__, func.__qualname__} ++ code = func.__code__ ++ callback = LineCallback(lambda code, _: code.co_name in names) ++ ++ n = 17 ++ try: ++ with ExitStack() as stack: ++ stack.enter_context(restore_events()) ++ stack.enter_context(restore_events(code=code)) ++ # Disable global line events, and enable local line events ++ disable_line_events() ++ if profile_when == 'before': ++ profile() ++ enable_line_events(code) ++ if profile_when != 'before': ++ # If we're here, the code object of `func()` is swapped ++ # out after code-local events have been registered to it ++ profile() ++ assert MON.get_current_callback() is callback ++ assert func(n) == n * (n + 1) // 2 ++ assert MON.get_current_callback() is callback ++ print(callback.nhits) ++ assert callback.nhits[_line_profiler.label(code)][lineno_loop] == n ++ finally: ++ with StringIO() as sio: ++ prof.print_stats(sio) ++ output = sio.getvalue() ++ print(output) ++ line = next(line for line in output.splitlines() ++ if line.endswith('# Loop body')) ++ nhits = int(line.split()[1]) ++ assert nhits == n