spyder_line_profiler-0.2.1/0000755000175000017500000000000013651765025016221 5ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/CHANGELOG.md0000644000175000017500000000706313651764113020035 0ustar jitsejitse00000000000000# History of changes ## Version 0.2.1 (2020/04/28) This release fixes some compatibility issues with Spyder 4.1 and some other bugs. ### Issues Closed * [Issue 44](https://github.com/spyder-ide/spyder-line-profiler/issues/44) - TextEditor initializer receives unexpected argument size ([PR 46](https://github.com/spyder-ide/spyder-line-profiler/pull/46)) * [Issue 41](https://github.com/spyder-ide/spyder-line-profiler/issues/41) - Move CI to github actions ([PR 45](https://github.com/spyder-ide/spyder-line-profiler/pull/45)) * [Issue 39](https://github.com/spyder-ide/spyder-line-profiler/issues/39) - Crash from opening options ([PR 40](https://github.com/spyder-ide/spyder-line-profiler/pull/40)) * [Issue 35](https://github.com/spyder-ide/spyder-line-profiler/issues/35) - Opening editor from line profiler output is broken ([PR 47](https://github.com/spyder-ide/spyder-line-profiler/pull/47)) In this release 4 issues were closed. ### Pull Requests Merged * [PR 47](https://github.com/spyder-ide/spyder-line-profiler/pull/47) - PR: Fix opening editor from profiler widget ([35](https://github.com/spyder-ide/spyder-line-profiler/issues/35)) * [PR 46](https://github.com/spyder-ide/spyder-line-profiler/pull/46) - PR: Fix initialization of TextEditor ([44](https://github.com/spyder-ide/spyder-line-profiler/issues/44)) * [PR 45](https://github.com/spyder-ide/spyder-line-profiler/pull/45) - PR: Move CI to GitHub Actions ([41](https://github.com/spyder-ide/spyder-line-profiler/issues/41)) * [PR 43](https://github.com/spyder-ide/spyder-line-profiler/pull/43) - PR: Fix invalid escape sequence in regex string * [PR 40](https://github.com/spyder-ide/spyder-line-profiler/pull/40) - PR: Add CONF_DEFAULTS ([39](https://github.com/spyder-ide/spyder-line-profiler/issues/39)) In this release 5 pull requests were closed. ## Version 0.2.0 (2019/12/18) This release updates the plugin to be used with Spyder 4 and fixes some bugs. ### Issues Closed * [Issue 33](https://github.com/spyder-ide/spyder-line-profiler/issues/33) - Sorting by time / % not working correctly ([PR 38](https://github.com/spyder-ide/spyder-line-profiler/pull/38)) * [Issue 26](https://github.com/spyder-ide/spyder-line-profiler/issues/26) - Update plugin to Spyder v4 ([PR 36](https://github.com/spyder-ide/spyder-line-profiler/pull/36)) In this release 2 issues were closed. ### Pull Requests Merged * [PR 38](https://github.com/spyder-ide/spyder-line-profiler/pull/38) - PR: Add natural sort for columns ([33](https://github.com/spyder-ide/spyder-line-profiler/issues/33)) * [PR 36](https://github.com/spyder-ide/spyder-line-profiler/pull/36) - PR: Compatibility changes for Spyder 4 ([26](https://github.com/spyder-ide/spyder-line-profiler/issues/26)) * [PR 31](https://github.com/spyder-ide/spyder-line-profiler/pull/31) - PR: Fix continuous integration services * [PR 30](https://github.com/spyder-ide/spyder-line-profiler/pull/30) - PR: "Profile by line" Button Behavior * [PR 24](https://github.com/spyder-ide/spyder-line-profiler/pull/24) - Update readme: Plugin can now be installed using conda or pip * [PR 23](https://github.com/spyder-ide/spyder-line-profiler/pull/23) - Add conda recipe ([15](https://github.com/spyder-ide/spyder-line-profiler/issues/15)) In this release 6 pull requests were closed. ## Version 0.1.1 (2017/03/26) This version improves the packaging. The code itself was not changed. ### Pull Requests Merged * [PR 22](https://github.com/spyder-ide/spyder-line-profiler/pull/22) - Install tests alongside package In this release 1 pull request was closed. ## Version 0.1.0 (2017/03/22) Initial release. spyder_line_profiler-0.2.1/LICENSE.txt0000644000175000017500000000210613064043412020026 0ustar jitsejitse00000000000000The MIT License (MIT) Copyright (c) 2013 Spyder Project Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. spyder_line_profiler-0.2.1/MANIFEST.in0000644000175000017500000000012413646554134017755 0ustar jitsejitse00000000000000include CHANGELOG.md LICENSE.txt README.md recursive-include spyder_line_profiler * spyder_line_profiler-0.2.1/PKG-INFO0000644000175000017500000000330213651765025017314 0ustar jitsejitse00000000000000Metadata-Version: 1.1 Name: spyder_line_profiler Version: 0.2.1 Summary: Plugin for the Spyder IDE that integrates the Python line profiler. Home-page: https://github.com/spyder-ide/spyder-line-profiler Author: Spyder Project Contributors Author-email: UNKNOWN License: MIT Description: This is a plugin for the Spyder IDE that integrates the Python line profiler. It allows you to see the time spent in every line. Usage ----- Add a ``@profile`` decorator to the functions that you wish to profile then press Shift+F10 (line profiler default) to run the profiler on the current script, or go to ``Run > Profile line by line``. The results will be shown in a dockwidget, grouped by function. Lines with a stronger color take more time to run. .. image: https://raw.githubusercontent.com/spyder-ide/spyder-line-profiler/master/img_src/screenshot_profler.png Keywords: Qt PyQt4 PyQt5 PySide spyder plugins spyplugins line_profiler profiler Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: X11 Applications :: Qt Classifier: Environment :: Win32 (MS Windows) Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Topic :: Software Development Classifier: Topic :: Text Editors :: Integrated Development Environments (IDE) spyder_line_profiler-0.2.1/README.md0000755000175000017500000000641213646267746017521 0ustar jitsejitse00000000000000# Spyder line profiler plugin ## Project details ![license](https://img.shields.io/pypi/l/spyder-line-profiler.svg) [![conda version](https://img.shields.io/conda/v/spyder-ide/spyder-line-profiler.svg)](https://www.anaconda.com/download/) [![download count](https://img.shields.io/conda/d/spyder-ide/spyder-line-profiler.svg)](https://www.anaconda.com/download/) [![pypi version](https://img.shields.io/pypi/v/spyder-line-profiler.svg)](https://pypi.python.org/pypi/spyder-line-profiler) [![Join the chat at https://gitter.im/spyder-ide/public](https://badges.gitter.im/spyder-ide/spyder.svg)](https://gitter.im/spyder-ide/public) [![OpenCollective Backers](https://opencollective.com/spyder/backers/badge.svg?color=blue)](#backers) [![OpenCollective Sponsors](https://opencollective.com/spyder/sponsors/badge.svg?color=blue)](#sponsors) ## Build status [![Windows status](https://github.com/spyder-ide/spyder-line-profiler/workflows/Windows%20tests/badge.svg)](https://github.com/spyder-ide/spyder-line-profiler/actions?query=workflow%3A%22Windows+tests%22) [![Linux status](https://github.com/spyder-ide/spyder-line-profiler/workflows/Linux%20tests/badge.svg)](https://github.com/spyder-ide/spyder-line-profiler/actions?query=workflow%3A%22Linux+tests%22) [![MacOS status](https://github.com/spyder-ide/spyder-line-profiler/workflows/Macos%20tests/badge.svg)](https://github.com/spyder-ide/spyder-line-profiler/actions?query=workflow%3A%22Macos+tests%22) [![codecov](https://codecov.io/gh/spyder-ide/spyder-line-profiler/branch/master/graph/badge.svg)](https://codecov.io/gh/spyder-ide/spyder-line-profiler/branch/master) ## Description This is a plugin to run the Python [line_profiler](https://pypi.python.org/pypi/line_profiler) from within the Python IDE [Spyder](https://github.com/spyder-ide/spyder). The code is an adaptation of the profiler plugin integrated in Spyder. ## Installation To install this plugin, you can use either ``pip`` or ``conda`` package managers, as follows: Using conda (the recommended way!): ``` conda install spyder-line-profiler -c spyder-ide ``` Using pip: ``` pip install spyder-line-profiler ``` ## Usage Add a `@profile` decorator to the functions that you wish to profile then Shift+F10 (line profiler default) to run the profiler on the current script, or go to `Run > Profile line by line`. The results will be shown in a dockwidget, grouped by function. Lines with a stronger color take more time to run. ## Screenshot ![Screenshot of spyder-line-profiler plugin showing profiler results](./img_src/screenshot_profler.png) ## Contributing Everyone is welcome to contribute! ## Sponsors Spyder and its subprojects are funded thanks to the generous support of [![Quansight](https://static.wixstatic.com/media/095d2c_2508c560e87d436ea00357abc404cf1d~mv2.png/v1/crop/x_0,y_9,w_915,h_329/fill/w_380,h_128,al_c,usm_0.66_1.00_0.01/095d2c_2508c560e87d436ea00357abc404cf1d~mv2.png)](https://www.quansight.com/)[![Numfocus](https://i2.wp.com/numfocus.org/wp-content/uploads/2017/07/NumFocus_LRG.png?fit=320%2C148&ssl=1)](https://numfocus.org/) and the donations we have received from our users around the world through [Open Collective](https://opencollective.com/spyder/): [![Sponsors](https://opencollective.com/spyder/sponsors.svg)](https://opencollective.com/spyder#support) spyder_line_profiler-0.2.1/setup.cfg0000644000175000017500000000004613651765025020042 0ustar jitsejitse00000000000000[egg_info] tag_build = tag_date = 0 spyder_line_profiler-0.2.1/setup.py0000644000175000017500000000603713554066047017741 0ustar jitsejitse00000000000000# -*- coding: utf-8 -*- # # Copyright © 2013 Spyder Project Contributors # Licensed under the terms of the MIT License # (see LICENSE.txt for details) """ Setup script for spyder_line_profiler """ from setuptools import setup, find_packages import os import os.path as osp def get_version(): """Get version from source file""" import codecs with codecs.open("spyder_line_profiler/__init__.py", encoding="utf-8") as f: lines = f.read().splitlines() for l in lines: if "__version__" in l: version = l.split("=")[1].strip() version = version.replace("'", '').replace('"', '') return version def get_package_data(name, extlist): """Return data files for package *name* with extensions in *extlist*""" flist = [] # Workaround to replace os.path.relpath (not available until Python 2.6): offset = len(name) + len(os.pathsep) for dirpath, _dirnames, filenames in os.walk(name): for fname in filenames: if not fname.startswith('.') and osp.splitext(fname)[1] in extlist: flist.append(osp.join(dirpath, fname)[offset:]) return flist # Requirements REQUIREMENTS = ['line_profiler', 'spyder>=4'] EXTLIST = ['.jpg', '.png', '.json', '.mo', '.ini'] LIBNAME = 'spyder_line_profiler' LONG_DESCRIPTION = """ This is a plugin for the Spyder IDE that integrates the Python line profiler. It allows you to see the time spent in every line. Usage ----- Add a ``@profile`` decorator to the functions that you wish to profile then press Shift+F10 (line profiler default) to run the profiler on the current script, or go to ``Run > Profile line by line``. The results will be shown in a dockwidget, grouped by function. Lines with a stronger color take more time to run. .. image: https://raw.githubusercontent.com/spyder-ide/spyder-line-profiler/master/img_src/screenshot_profler.png """ setup( name=LIBNAME, version=get_version(), packages=find_packages(), package_data={LIBNAME: get_package_data(LIBNAME, EXTLIST)}, keywords=["Qt PyQt4 PyQt5 PySide spyder plugins spyplugins line_profiler profiler"], install_requires=REQUIREMENTS, url='https://github.com/spyder-ide/spyder-line-profiler', license='MIT', author="Spyder Project Contributors", description='Plugin for the Spyder IDE that integrates the Python line profiler.', long_description=LONG_DESCRIPTION, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: X11 Applications :: Qt', 'Environment :: Win32 (MS Windows)', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Topic :: Software Development', 'Topic :: Text Editors :: Integrated Development Environments (IDE)']) spyder_line_profiler-0.2.1/spyder_line_profiler/0000755000175000017500000000000013651765025022440 5ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler/__init__.py0000644000175000017500000000073713651764540024561 0ustar jitsejitse00000000000000# -*- coding: utf-8 -*- # # Copyright © 2013 Spyder Project Contributors # Licensed under the terms of the MIT License # (see LICENSE.txt for details) __version__ = '0.2.1' # ============================================================================= # The following statements are required to register this 3rd party plugin: # ============================================================================= from .lineprofiler import LineProfiler PLUGIN_CLASS = LineProfiler spyder_line_profiler-0.2.1/spyder_line_profiler/data/0000755000175000017500000000000013651765025023351 5ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler/data/__init__.py0000644000175000017500000000000213045706742025450 0ustar jitsejitse00000000000000 spyder_line_profiler-0.2.1/spyder_line_profiler/data/images/0000755000175000017500000000000013651765025024616 5ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler/data/images/__init__.py0000644000175000017500000000000213045706742026715 0ustar jitsejitse00000000000000 spyder_line_profiler-0.2.1/spyder_line_profiler/data/images/spyder.line_profiler.png0000644000175000017500000000276713045706742031474 0ustar jitsejitse00000000000000PNG  IHDRw=sRGBgAMA a cHRMz&u0`:pQ<tEXtSoftwarePaint.NET v3.5.4>vPIDATHKUOg͘?mYdɲ;%K6r\ܧX~r`AG8@r80 gO|O>yzg{B90E8OxyyY+ɰ4Sfv?[:<@[y,?}62"BD,3߳jP]PfLMJ7E1[KrYF a5SWo?~]TGa [V_$-z `cS\w$;^IENDB`spyder_line_profiler-0.2.1/spyder_line_profiler/example/0000755000175000017500000000000013651765025024073 5ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler/example/__init__.py0000644000175000017500000000000013052142265026160 0ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler/example/profiling_test_script.py0000644000175000017500000000175113052142265031053 0ustar jitsejitse00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- u""" :author: Joseph Martinot-Lagarde Created on Sat Jan 19 14:57:57 2013 """ from __future__ import ( print_function, division, unicode_literals, absolute_import) import subdir.profiling_test_script2 as script2 @profile def fact(n): result = 1 for i in range(2, n // 4): result *= i result = 1 # This is a comment for i in range(2, n // 16): result *= i result = 1 if False: # This won't be run raise RuntimeError("What are you doing here ???") for i in range(2, n + 1): result *= i return result # This is after the end of the function. if False: # This won't be run raise RuntimeError("It's getting bad.") @profile def sum_(n): result = 0 for i in range(1, n + 1): result += i return result if __name__ == "__main__": print(fact(120)) print(sum_(120)) print(script2.fact2(120)) print(script2.sum2(120)) spyder_line_profiler-0.2.1/spyder_line_profiler/example/subdir/0000755000175000017500000000000013651765025025363 5ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler/example/subdir/__init__.py0000644000175000017500000000000013052142265027450 0ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler/example/subdir/profiling_test_script2.py0000644000175000017500000000076113052142265032425 0ustar jitsejitse00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- u""" :author: Joseph Martinot-Lagarde Created on Sat Jan 19 14:57:57 2013 """ from __future__ import ( print_function, division, unicode_literals, absolute_import) @profile def fact2(n): result = 1 for i in range(2, n + 1): result *= i * 2 return result def sum2(n): result = 0 for i in range(1, n + 1): result += i * 2 return result if __name__ == "__main__": print(fact2(120)) print(sum2(120)) spyder_line_profiler-0.2.1/spyder_line_profiler/lineprofiler.py0000644000175000017500000001332313646554170025507 0ustar jitsejitse00000000000000# -*- coding: utf-8 -*- # # Copyright © 2013 Spyder Project Contributors # Licensed under the terms of the MIT License # (see LICENSE.txt for details) """Line profiler Plugin.""" # Third party imports from qtpy.QtCore import Qt, Signal from qtpy.QtWidgets import QGroupBox, QLabel, QVBoxLayout # Need running QApplication before importing runconfig from spyder.utils.qthelpers import qapplication MAIN_APP = qapplication() from spyder.api.plugins import SpyderPluginWidget from spyder.api.preferences import PluginConfigPage from spyder.config.base import get_translation from spyder.utils import icon_manager as ima from spyder.utils.qthelpers import create_action # Local imports from .data import images from .widgets.lineprofiler import LineProfilerWidget, is_lineprofiler_installed _ = get_translation("line_profiler", dirname="spyder_line_profiler") class LineProfilerConfigPage(PluginConfigPage): """ Widget with configuration options for line profiler. """ def setup_page(self): settings_group = QGroupBox(_("Settings")) use_color_box = self.create_checkbox( _("Use deterministic colors to differentiate functions"), 'use_colors', default=True) results_group = QGroupBox(_("Results")) results_label1 = QLabel(_("Line profiler plugin results " "(the output of kernprof.py)\n" "are stored here:")) results_label1.setWordWrap(True) # Warning: do not try to regroup the following QLabel contents with # widgets above -- this string was isolated here in a single QLabel # on purpose: to fix Issue 863 of Profiler plugon results_label2 = QLabel(LineProfilerWidget.DATAPATH) results_label2.setTextInteractionFlags(Qt.TextSelectableByMouse) results_label2.setWordWrap(True) settings_layout = QVBoxLayout() settings_layout.addWidget(use_color_box) settings_group.setLayout(settings_layout) results_layout = QVBoxLayout() results_layout.addWidget(results_label1) results_layout.addWidget(results_label2) results_group.setLayout(results_layout) vlayout = QVBoxLayout() vlayout.addWidget(settings_group) vlayout.addWidget(results_group) vlayout.addStretch(1) self.setLayout(vlayout) class LineProfiler(SpyderPluginWidget): """ Line profiler. """ CONF_SECTION = 'lineprofiler' CONF_DEFAULTS = [(CONF_SECTION, {'use_colors': True})] CONFIGWIDGET_CLASS = LineProfilerConfigPage def __init__(self, parent=None): SpyderPluginWidget.__init__(self, parent) # Create widget and add to dockwindow self.widget = LineProfilerWidget(self.main) layout = QVBoxLayout() layout.addWidget(self.widget) self.setLayout(layout) def update_pythonpath(self): """ Update the PYTHONPATH used when running the line_profiler. This function is called whenever the Python path set in Spyder changes. It synchronizes the PYTHONPATH in the line_profiler widget with the PYTHONPATH in Spyder. """ self.widget.spyder_pythonpath = self.main.get_spyder_pythonpath() # --- SpyderPluginWidget API ---------------------------------------------- def get_plugin_title(self): """Return widget title.""" return _("Line profiler") def get_plugin_icon(self): """Return widget icon.""" path = images.__path__[0] return ima.icon('spyder.line_profiler', icon_path=path) def get_focus_widget(self): """ Return the widget to give focus to when this plugin's dockwidget is raised on top-level. """ return self.widget.datatree def get_plugin_actions(self): """Return a list of actions related to plugin.""" return [] def on_first_registration(self): """Action to be performed on first plugin registration.""" self.main.tabify_plugins(self.main.help, self) self.dockwidget.hide() def register_plugin(self): """Register plugin in Spyder's main window.""" super(LineProfiler, self).register_plugin() # Spyder PYTHONPATH self.update_pythonpath() self.main.sig_pythonpath_changed.connect(self.update_pythonpath) self.widget.datatree.edit_goto.connect(self.main.editor.load) self.widget.redirect_stdio.connect(self.main.redirect_internalshell_stdio) lineprofiler_act = create_action(self, _("Profile line by line"), icon=self.get_plugin_icon(), shortcut="Shift+F10", triggered=self.run_lineprofiler) lineprofiler_act.setEnabled(is_lineprofiler_installed()) self.main.run_menu_actions += [lineprofiler_act] self.main.editor.pythonfile_dependent_actions += [lineprofiler_act] def refresh_plugin(self): """Refresh line profiler widget.""" pass def closing_plugin(self, cancelable=False): """Perform actions before parent main window is closed.""" return True def apply_plugin_settings(self, options): """Apply configuration file's plugin settings.""" pass # --- Public API ---------------------------------------------------------- def run_lineprofiler(self): """Run line profiler.""" self.analyze(self.main.editor.get_current_filename()) def analyze(self, filename): """Reimplement analyze method.""" if self.dockwidget: self.switch_to_plugin() self.widget.analyze( filename=filename, use_colors=self.get_option('use_colors', True)) spyder_line_profiler-0.2.1/spyder_line_profiler/widgets/0000755000175000017500000000000013651765025024106 5ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler/widgets/__init__.py0000644000175000017500000000000213045706742026205 0ustar jitsejitse00000000000000 spyder_line_profiler-0.2.1/spyder_line_profiler/widgets/lineprofiler.py0000644000175000017500000005572613646554170027172 0ustar jitsejitse00000000000000# -*- coding: utf-8 -*- # # Copyright © 2011 Spyder Project Contributors # Licensed under the terms of the MIT License # (see LICENSE.txt for details) """ Line Profiler widget See the official documentation of line_profiler: http://pythonhosted.org/line_profiler/ """ # Standard library imports from __future__ import with_statement import hashlib import inspect import linecache import re import os import os.path as osp import time import sys # Third party imports from qtpy.compat import getopenfilename from qtpy.QtCore import (QByteArray, QProcess, Qt, QTextCodec, QProcessEnvironment, Signal) from qtpy.QtGui import QBrush, QColor, QFont from qtpy.QtWidgets import (QHBoxLayout, QWidget, QMessageBox, QVBoxLayout, QLabel, QTreeWidget, QTreeWidgetItem, QApplication) # Local imports from spyder.config.base import get_conf_path, get_translation from spyder.plugins.variableexplorer.widgets.texteditor import TextEditor from spyder.utils import programs from spyder.utils.misc import add_pathlist_to_PYTHONPATH from spyder.utils.qthelpers import create_toolbutton, get_icon from spyder.widgets.comboboxes import PythonModulesComboBox try: from spyder.py3compat import to_text_string, getcwd, pickle except ImportError: # python2 to_text_string = unicode getcwd = os.getcwdu import cPickle as pickle # This is needed for testing this module as a stand alone script try: _ = get_translation("line_profiler", dirname="spyder_line_profiler") except KeyError as error: import gettext _ = gettext.gettext locale_codec = QTextCodec.codecForLocale() COL_NO = 0 COL_HITS = 1 COL_TIME = 2 COL_PERHIT = 3 COL_PERCENT = 4 COL_LINE = 5 COL_POS = 0 # Position is not displayed but set as Qt.UserRole CODE_NOT_RUN_COLOR = QBrush(QColor.fromRgb(128, 128, 128, 200)) WEBSITE_URL = 'http://pythonhosted.org/line_profiler/' class TreeWidgetItem(QTreeWidgetItem): """ An extension of QTreeWidgetItem that replaces the sorting behaviour such that the sorting is not purely by ASCII index but by natural sorting, e.g. multi-digit numbers sorted based on their value instead of individual digits. Taken from https://stackoverflow.com/questions/21030719/sort-a-pyside-qtgui- qtreewidget-by-an-alpha-numeric-column/ """ def __lt__(self, other): """ Compare a widget text entry to another entry. """ column = self.treeWidget().sortColumn() key1 = self.text(column) key2 = other.text(column) return self.natural_sort_key(key1) < self.natural_sort_key(key2) @staticmethod def natural_sort_key(key): """ Natural sorting for both numbers and strings containing numbers. """ regex = r'(\d*\.\d+|\d+)' parts = re.split(regex, key) return tuple((e if i % 2 == 0 else float(e)) for i, e in enumerate(parts)) def is_lineprofiler_installed(): """ Checks if the program and the library for line_profiler is installed. """ return (programs.is_module_installed('line_profiler') and programs.find_program('kernprof') is not None) class LineProfilerWidget(QWidget): """ Line profiler widget. """ DATAPATH = get_conf_path('lineprofiler.results') VERSION = '0.0.1' redirect_stdio = Signal(bool) sig_finished = Signal() def __init__(self, parent): QWidget.__init__(self, parent) # Need running QApplication before importing runconfig from spyder.preferences import runconfig self.runconfig = runconfig self.spyder_pythonpath = None self.setWindowTitle("Line profiler") self.output = None self.error_output = None self.use_colors = True self._last_wdir = None self._last_args = None self._last_pythonpath = None self.filecombo = PythonModulesComboBox(self) self.start_button = create_toolbutton( self, icon=get_icon('run.png'), text=_("Profile by line"), tip=_("Run line profiler"), triggered=(lambda checked=False: self.analyze()), text_beside_icon=True) self.stop_button = create_toolbutton( self, icon=get_icon('terminate.png'), text=_("Stop"), tip=_("Stop current profiling"), text_beside_icon=True) self.filecombo.valid.connect(self.start_button.setEnabled) #self.filecombo.valid.connect(self.show_data) # FIXME: The combobox emits this signal on almost any event # triggering show_data() too early, too often. browse_button = create_toolbutton( self, icon=get_icon('fileopen.png'), tip=_('Select Python script'), triggered=self.select_file) self.datelabel = QLabel() self.log_button = create_toolbutton( self, icon=get_icon('log.png'), text=_("Output"), text_beside_icon=True, tip=_("Show program's output"), triggered=self.show_log) self.datatree = LineProfilerDataTree(self) self.collapse_button = create_toolbutton( self, icon=get_icon('collapse.png'), triggered=lambda dD=-1: self.datatree.collapseAll(), tip=_('Collapse all')) self.expand_button = create_toolbutton( self, icon=get_icon('expand.png'), triggered=lambda dD=1: self.datatree.expandAll(), tip=_('Expand all')) hlayout1 = QHBoxLayout() hlayout1.addWidget(self.filecombo) hlayout1.addWidget(browse_button) hlayout1.addWidget(self.start_button) hlayout1.addWidget(self.stop_button) hlayout2 = QHBoxLayout() hlayout2.addWidget(self.collapse_button) hlayout2.addWidget(self.expand_button) hlayout2.addStretch() hlayout2.addWidget(self.datelabel) hlayout2.addStretch() hlayout2.addWidget(self.log_button) layout = QVBoxLayout() layout.addLayout(hlayout1) layout.addLayout(hlayout2) layout.addWidget(self.datatree) self.setLayout(layout) self.process = None self.set_running_state(False) self.start_button.setEnabled(False) if not is_lineprofiler_installed(): for widget in (self.datatree, self.filecombo, self.log_button, self.start_button, self.stop_button, browse_button, self.collapse_button, self.expand_button): widget.setDisabled(True) text = _( 'Please install the line_profiler module' ) % WEBSITE_URL self.datelabel.setText(text) self.datelabel.setOpenExternalLinks(True) else: pass # self.show_data() def analyze(self, filename=None, wdir=None, args=None, pythonpath=None, use_colors=True): self.use_colors = use_colors if not is_lineprofiler_installed(): return self.kill_if_running() #index, _data = self.get_data(filename) # FIXME: storing data is not implemented yet if filename is not None: filename = osp.abspath(to_text_string(filename)) index = self.filecombo.findText(filename) if index == -1: self.filecombo.addItem(filename) self.filecombo.setCurrentIndex(self.filecombo.count()-1) else: self.filecombo.setCurrentIndex(index) self.filecombo.selected() if self.filecombo.is_valid(): filename = to_text_string(self.filecombo.currentText()) runconf = self.runconfig.get_run_configuration(filename) if runconf is not None: if wdir is None: if runconf.wdir_enabled: wdir = runconf.wdir elif runconf.cw_dir: wdir = os.getcwd() elif runconf.file_dir: wdir = osp.dirname(filename) elif runconf.fixed_dir: wdir = runconf.dir if args is None: if runconf.args_enabled: args = runconf.args if wdir is None: wdir = osp.dirname(filename) if pythonpath is None: pythonpath = self.spyder_pythonpath self.start(wdir, args, pythonpath) def select_file(self): self.redirect_stdio.emit(False) filename, _selfilter = getopenfilename( self, _("Select Python script"), getcwd(), _("Python scripts")+" (*.py ; *.pyw)") self.redirect_stdio.emit(False) if filename: self.analyze(filename) def show_log(self): if self.output: editor = TextEditor(self.output, title=_("Line profiler output"), readonly=True) # Call .show() to dynamically resize editor; # see spyder-ide/spyder#12202 editor.show() editor.exec_() def show_errorlog(self): if self.error_output: editor = TextEditor(self.error_output, title=_("Line profiler output"), readonly=True) # Call .show() to dynamically resize editor; # see spyder-ide/spyder#12202 editor.show() editor.exec_() def start(self, wdir=None, args=None, pythonpath=None): filename = to_text_string(self.filecombo.currentText()) if wdir is None: wdir = self._last_wdir if wdir is None: wdir = osp.basename(filename) if args is None: args = self._last_args if args is None: args = [] if pythonpath is None: pythonpath = self._last_pythonpath self._last_wdir = wdir self._last_args = args self._last_pythonpath = pythonpath self.datelabel.setText(_('Profiling, please wait...')) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.SeparateChannels) self.process.setWorkingDirectory(wdir) self.process.readyReadStandardOutput.connect(self.read_output) self.process.readyReadStandardError.connect( lambda: self.read_output(error=True)) self.process.finished.connect(self.finished) self.stop_button.clicked.connect(self.process.kill) if pythonpath is not None: env = [to_text_string(_pth) for _pth in self.process.systemEnvironment()] add_pathlist_to_PYTHONPATH(env, pythonpath) processEnvironment = QProcessEnvironment() for envItem in env: envName, separator, envValue = envItem.partition('=') processEnvironment.insert(envName, envValue) self.process.setProcessEnvironment(processEnvironment) self.output = '' self.error_output = '' if os.name == 'nt': # On Windows, one has to replace backslashes by slashes to avoid # confusion with escape characters (otherwise, for example, '\t' # will be interpreted as a tabulation): filename = osp.normpath(filename).replace(os.sep, '/') p_args = ['-lvb', '-o', '"' + self.DATAPATH + '"', '"' + filename + '"'] if args: p_args.extend(programs.shell_split(args)) executable = '"' + programs.find_program('kernprof') + '"' executable += ' ' + ' '.join(p_args) executable = executable.replace(os.sep, '/') self.process.start(executable) else: p_args = ['-lvb', '-o', self.DATAPATH, filename] if args: p_args.extend(programs.shell_split(args)) executable = 'kernprof' self.process.start(executable, p_args) running = self.process.waitForStarted() self.set_running_state(running) if not running: QMessageBox.critical(self, _("Error"), _("Process failed to start")) def set_running_state(self, state=True): self.start_button.setEnabled(not state) self.stop_button.setEnabled(state) def read_output(self, error=False): if error: self.process.setReadChannel(QProcess.StandardError) else: self.process.setReadChannel(QProcess.StandardOutput) qba = QByteArray() while self.process.bytesAvailable(): if error: qba += self.process.readAllStandardError() else: qba += self.process.readAllStandardOutput() text = to_text_string(locale_codec.toUnicode(qba.data())) if error: self.error_output += text else: self.output += text def finished(self): self.set_running_state(False) self.show_errorlog() # If errors occurred, show them. self.output = self.error_output + self.output # FIXME: figure out if show_data should be called here or # as a signal from the combobox self.show_data(justanalyzed=True) self.sig_finished.emit() def kill_if_running(self): if self.process is not None: if self.process.state() == QProcess.Running: self.process.kill() self.process.waitForFinished() def show_data(self, justanalyzed=False): if not justanalyzed: self.output = None self.log_button.setEnabled( self.output is not None and len(self.output) > 0) self.kill_if_running() filename = to_text_string(self.filecombo.currentText()) if not filename: return self.datatree.load_data(self.DATAPATH) self.datelabel.setText(_('Sorting data, please wait...')) QApplication.processEvents() self.datatree.show_tree() text_style = "%s " date_text = text_style % time.strftime("%d %b %Y %H:%M", time.localtime()) self.datelabel.setText(date_text) class LineProfilerDataTree(QTreeWidget): """ Convenience tree widget (with built-in model) to store and view line profiler data. """ edit_goto = Signal(str, int, str) def __init__(self, parent=None): QTreeWidget.__init__(self, parent) self.header_list = [ _('Line #'), _('Hits'), _('Time (ms)'), _('Per hit (ms)'), _('% Time'), _('Line contents')] self.stats = None # To be filled by self.load_data() self.max_time = 0 # To be filled by self.load_data() self.header().setDefaultAlignment(Qt.AlignCenter) self.setColumnCount(len(self.header_list)) self.setHeaderLabels(self.header_list) self.clear() self.itemActivated.connect(self.item_activated) def show_tree(self): """Populate the tree with line profiler data and display it.""" self.clear() # Clear before re-populating self.setItemsExpandable(True) self.setSortingEnabled(False) self.populate_tree() self.expandAll() for col in range(self.columnCount()-1): self.resizeColumnToContents(col) if self.topLevelItemCount() > 1: self.collapseAll() self.setSortingEnabled(True) self.sortItems(COL_POS, Qt.AscendingOrder) def load_data(self, profdatafile): """Load line profiler data saved by kernprof module""" # lstats has the following layout : # lstats.timings = # {(filename1, line_no1, function_name1): # [(line_no1, hits1, total_time1), # (line_no2, hits2, total_time2)], # (filename2, line_no2, function_name2): # [(line_no1, hits1, total_time1), # (line_no2, hits2, total_time2), # (line_no3, hits3, total_time3)]} # lstats.unit = time_factor with open(profdatafile, 'rb') as fid: lstats = pickle.load(fid) # First pass to group by filename self.stats = dict() linecache.checkcache() for func_info, stats in lstats.timings.items(): # func_info is a tuple containing (filename, line, function anme) filename, start_line_no = func_info[:2] # Read code start_line_no -= 1 # include the @profile decorator all_lines = linecache.getlines(filename) block_lines = inspect.getblock(all_lines[start_line_no:]) # Loop on each line of code func_stats = [] func_total_time = 0.0 next_stat_line = 0 for line_no, code_line in enumerate(block_lines): line_no += start_line_no + 1 # Lines start at 1 code_line = code_line.rstrip('\n') if (next_stat_line >= len(stats) or line_no != stats[next_stat_line][0]): # Line didn't run hits, line_total_time, time_per_hit = None, None, None else: # Compute line times hits, line_total_time = stats[next_stat_line][1:] line_total_time *= lstats.unit time_per_hit = line_total_time / hits func_total_time += line_total_time next_stat_line += 1 func_stats.append( [line_no, code_line, line_total_time, time_per_hit, hits]) # Compute percent time for line in func_stats: line_total_time = line[2] if line_total_time is None: line.append(None) else: line.append(line_total_time / func_total_time) # Fill dict self.stats[func_info] = [func_stats, func_total_time] def fill_item(self, item, filename, line_no, code, time, percent, perhit, hits): item.setData(COL_POS, Qt.UserRole, (osp.normpath(filename), line_no)) item.setData(COL_NO, Qt.DisplayRole, line_no) item.setData(COL_LINE, Qt.DisplayRole, code) if percent is None: percent = '' else: percent = '%.1f' % (100 * percent) item.setData(COL_PERCENT, Qt.DisplayRole, percent) item.setTextAlignment(COL_PERCENT, Qt.AlignCenter) if time is None: time = '' else: time = '%.3f' % (time * 1e3) item.setData(COL_TIME, Qt.DisplayRole, time) item.setTextAlignment(COL_TIME, Qt.AlignCenter) if perhit is None: perhit = '' else: perhit = '%.3f' % (perhit * 1e3) item.setData(COL_PERHIT, Qt.DisplayRole, perhit) item.setTextAlignment(COL_PERHIT, Qt.AlignCenter) if hits is None: hits = '' else: hits = '%d' % hits item.setData(COL_HITS, Qt.DisplayRole, hits) item.setTextAlignment(COL_HITS, Qt.AlignCenter) def populate_tree(self): """Create each item (and associated data) in the tree""" if not self.stats: warn_item = TreeWidgetItem(self) warn_item.setData( 0, Qt.DisplayRole, _('No timings to display. ' 'Did you forget to add @profile decorators ?') .format(url=WEBSITE_URL)) warn_item.setFirstColumnSpanned(True) warn_item.setTextAlignment(0, Qt.AlignCenter) font = warn_item.font(0) font.setStyle(QFont.StyleItalic) warn_item.setFont(0, font) return try: monospace_font = self.window().editor.get_plugin_font() except AttributeError: # If run standalone for testing monospace_font = QFont("Courier New") monospace_font.setPointSize(10) for func_info, func_data in self.stats.items(): # Function name and position filename, start_line_no, func_name = func_info func_stats, func_total_time = func_data func_item = TreeWidgetItem(self) func_item.setData( 0, Qt.DisplayRole, _('{func_name} ({time_ms:.3f}ms) in file "{filename}", ' 'line {line_no}').format( filename=filename, line_no=start_line_no, func_name=func_name, time_ms=func_total_time * 1e3)) func_item.setFirstColumnSpanned(True) func_item.setData(COL_POS, Qt.UserRole, (osp.normpath(filename), start_line_no)) # For sorting by time func_item.setData(COL_TIME, Qt.DisplayRole, func_total_time * 1e3) func_item.setData(COL_PERCENT, Qt.DisplayRole, func_total_time * 1e3) if self.parent().use_colors: # Choose deteministic unique color for the function md5 = hashlib.md5((filename + func_name).encode("utf8")).hexdigest() hue = (int(md5[:2], 16) - 68) % 360 # avoid blue (unreadable) func_color = QColor.fromHsv(hue, 200, 255) else: # Red color only func_color = QColor.fromRgb(255, 0, 0) # Lines of code for line_info in func_stats: line_item = TreeWidgetItem(func_item) (line_no, code_line, line_total_time, time_per_hit, hits, percent) = line_info self.fill_item( line_item, filename, line_no, code_line, line_total_time, percent, time_per_hit, hits) # Color background if line_total_time is not None: alpha = percent color = QColor(func_color) color.setAlphaF(alpha) # Returns None color = QBrush(color) for col in range(self.columnCount()): line_item.setBackground(col, color) else: for col in range(self.columnCount()): line_item.setForeground(col, CODE_NOT_RUN_COLOR) # Monospace font for code line_item.setFont(COL_LINE, monospace_font) def item_activated(self, item): filename, line_no = item.data(COL_POS, Qt.UserRole) self.edit_goto.emit(filename, line_no, '') def test(): """Run widget test""" from spyder.utils.qthelpers import qapplication app = qapplication() widget = LineProfilerWidget(None) widget.resize(800, 600) widget.show() widget.analyze(osp.normpath(osp.join(osp.dirname(__file__), os.pardir, 'tests/profiling_test_script.py')), use_colors=True) sys.exit(app.exec_()) if __name__ == '__main__': test() spyder_line_profiler-0.2.1/spyder_line_profiler/widgets/tests/0000755000175000017500000000000013651765025025250 5ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler/widgets/tests/__init__.py0000644000175000017500000000000013071004337027333 0ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler/widgets/tests/test_lineprofiler.py0000644000175000017500000000435713064043412031347 0ustar jitsejitse00000000000000# -*- coding: utf-8 -*- # # Copyright © 2017 Spyder Project Contributors # Licensed under the terms of the MIT License # (see LICENSE.txt for details) """Tests for lineprofiler.py.""" # Standard library imports import os # Third party imports from pytestqt import qtbot from qtpy.QtCore import Qt from spyder.utils.qthelpers import qapplication MAIN_APP = qapplication() # Local imports from spyder_line_profiler.widgets.lineprofiler import LineProfilerWidget try: from unittest.mock import Mock except ImportError: from mock import Mock # Python 2 TEST_SCRIPT = \ """import time @profile def foo(): time.sleep(1) xs = [] for k in range(100): xs = xs + ['x'] foo()""" def test_profile_and_display_results(qtbot, tmpdir, monkeypatch): """Run profiler on simple script and check that results are okay.""" os.chdir(tmpdir.strpath) testfilename = tmpdir.join('test_foo.py').strpath with open(testfilename, 'w') as f: f.write(TEST_SCRIPT) MockQMessageBox = Mock() monkeypatch.setattr('spyder_line_profiler.widgets.lineprofiler.QMessageBox', MockQMessageBox) widget = LineProfilerWidget(None) qtbot.addWidget(widget) with qtbot.waitSignal(widget.sig_finished, timeout=10000, raising=True): widget.analyze(testfilename) MockQMessageBox.assert_not_called() dt = widget.datatree assert dt.topLevelItemCount() == 1 # number of functions profiled top = dt.topLevelItem(0) assert top.data(0, Qt.DisplayRole).startswith('foo ') assert top.childCount() == 6 for i in range(6): assert top.child(i).data(0, Qt.DisplayRole) == i + 2 # line no assert top.child(2).data(1, Qt.DisplayRole) == '1' # hits assert top.child(3).data(1, Qt.DisplayRole) == '1' assert top.child(4).data(1, Qt.DisplayRole) == '101' assert top.child(5).data(1, Qt.DisplayRole) == '100' assert float(top.child(2).data(2, Qt.DisplayRole)) >= 900 # time (ms) assert float(top.child(2).data(2, Qt.DisplayRole)) <= 1200 assert float(top.child(3).data(2, Qt.DisplayRole)) <= 100 assert float(top.child(4).data(2, Qt.DisplayRole)) <= 100 assert float(top.child(5).data(2, Qt.DisplayRole)) <= 100 spyder_line_profiler-0.2.1/spyder_line_profiler.egg-info/0000755000175000017500000000000013651765025024132 5ustar jitsejitse00000000000000spyder_line_profiler-0.2.1/spyder_line_profiler.egg-info/PKG-INFO0000644000175000017500000000330213651765025025225 0ustar jitsejitse00000000000000Metadata-Version: 1.1 Name: spyder-line-profiler Version: 0.2.1 Summary: Plugin for the Spyder IDE that integrates the Python line profiler. Home-page: https://github.com/spyder-ide/spyder-line-profiler Author: Spyder Project Contributors Author-email: UNKNOWN License: MIT Description: This is a plugin for the Spyder IDE that integrates the Python line profiler. It allows you to see the time spent in every line. Usage ----- Add a ``@profile`` decorator to the functions that you wish to profile then press Shift+F10 (line profiler default) to run the profiler on the current script, or go to ``Run > Profile line by line``. The results will be shown in a dockwidget, grouped by function. Lines with a stronger color take more time to run. .. image: https://raw.githubusercontent.com/spyder-ide/spyder-line-profiler/master/img_src/screenshot_profler.png Keywords: Qt PyQt4 PyQt5 PySide spyder plugins spyplugins line_profiler profiler Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: X11 Applications :: Qt Classifier: Environment :: Win32 (MS Windows) Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Topic :: Software Development Classifier: Topic :: Text Editors :: Integrated Development Environments (IDE) spyder_line_profiler-0.2.1/spyder_line_profiler.egg-info/SOURCES.txt0000644000175000017500000000155713651765025026026 0ustar jitsejitse00000000000000CHANGELOG.md LICENSE.txt MANIFEST.in README.md setup.py spyder_line_profiler/__init__.py spyder_line_profiler/lineprofiler.py spyder_line_profiler.egg-info/PKG-INFO spyder_line_profiler.egg-info/SOURCES.txt spyder_line_profiler.egg-info/dependency_links.txt spyder_line_profiler.egg-info/requires.txt spyder_line_profiler.egg-info/top_level.txt spyder_line_profiler/data/__init__.py spyder_line_profiler/data/images/__init__.py spyder_line_profiler/data/images/spyder.line_profiler.png spyder_line_profiler/example/__init__.py spyder_line_profiler/example/profiling_test_script.py spyder_line_profiler/example/subdir/__init__.py spyder_line_profiler/example/subdir/profiling_test_script2.py spyder_line_profiler/widgets/__init__.py spyder_line_profiler/widgets/lineprofiler.py spyder_line_profiler/widgets/tests/__init__.py spyder_line_profiler/widgets/tests/test_lineprofiler.pyspyder_line_profiler-0.2.1/spyder_line_profiler.egg-info/dependency_links.txt0000644000175000017500000000000113651765025030200 0ustar jitsejitse00000000000000 spyder_line_profiler-0.2.1/spyder_line_profiler.egg-info/requires.txt0000644000175000017500000000003013651765025026523 0ustar jitsejitse00000000000000line_profiler spyder>=4 spyder_line_profiler-0.2.1/spyder_line_profiler.egg-info/top_level.txt0000644000175000017500000000002513651765025026661 0ustar jitsejitse00000000000000spyder_line_profiler