pax_global_header00006660000000000000000000000064145210313210014502gustar00rootroot0000000000000052 comment=1902bf1dc5adce02d472555e74da4e1b7dd8211f line_profiler-4.1.2/000077500000000000000000000000001452103132100143375ustar00rootroot00000000000000line_profiler-4.1.2/.circleci/000077500000000000000000000000001452103132100161725ustar00rootroot00000000000000line_profiler-4.1.2/.circleci/config.yml000066400000000000000000000106761452103132100201740ustar00rootroot00000000000000# Python CircleCI 2.0 configuration file # Check https://circleci.com/docs/2.0/language-python/ for more details # # References: # # how to setup multiple python versions # https://stackoverflow.com/questions/948354/default-behavior-of-git-push-without-a-branch-specified # https://github.com/adambrenecki/virtualfish/blob/aa3d6271bcb86ad27b6d24f96b5bd386d176f588/.circleci/config.yml # # # Multiple files for a checksum # https://discuss.circleci.com/t/cant-checksum-multiple-files-with-slashes-in-the-file-path/20667/2 # # # Auto Cancel Redundant Builds # https://circleci.com/docs/2.0/skip-build/#steps-to-enable-auto-cancel-for-pipelines-triggered-by-pushes-to-github-or-the-api # https://app.circleci.com/settings/project/github/pyutils/line_profiler/advanced?return-to=https%3A%2F%2Fapp.circleci.com%2Fpipelines%2Fgithub%2Fpyutils%2FPYPKG # Note: Main CI is now in github actions version: 2 workflows: version: 2 test: jobs: - test_full/cp39-cp39-manylinux2010 - test_full/cp38-cp38-manylinux2010 - test_full/cp37-cp37m-manylinux2010 - test_full/cp36-cp36m-manylinux2010 jobs: ########### # TEMPLATES ########### .common_template: &common_template docker: - image: cimg/python steps: - checkout .test_full_template: &test_full_template <<: - *common_template resource_class: small steps: - checkout - setup_remote_docker - run: name: prepare_env command: | python -m venv venv . venv/bin/activate pip install --upgrade pip - run: name: build_wheel command: | . venv/bin/activate VERSION=$(python -c "import setup; print(setup.VERSION)") export CIBW_BUILD="${MB_PYTHON_TAG}-*" docker info pip install -r requirements/build.txt cibuildwheel --platform=linux --arch=native - persist_to_workspace: root: . paths: - wheelhouse - run: name: install_wheel command: | . venv/bin/activate VERSION=$(python -c "import setup; print(setup.VERSION)") # Hack in the arch BDIST_WHEEL_PATH=$(ls wheelhouse/*-$VERSION-$MB_PYTHON_TAG*manylinux*_x86_64.whl) echo "BDIST_WHEEL_PATH=${BDIST_WHEEL_PATH}" pip install ${BDIST_WHEEL_PATH}[all] - run: name: run_tests command: | . venv/bin/activate python run_tests.py ################################### ### INHERIT FROM BASE TEMPLATES ### ################################### # Define tests fo the other python verisons using the "test3.6" template # and indicating what needs to be modified. # # All we need to do is change the base docker image so python is the # version we want we can reuse everything else from the template test_full/cp39-cp39-manylinux2010: <<: *test_full_template environment: - MB_PYTHON_TAG=cp39 docker: - image: cimg/python:3.9 working_directory: ~/repo-full-cp39 test_full/cp38-cp38-manylinux2010: <<: *test_full_template environment: - MB_PYTHON_TAG=cp38 docker: - image: cimg/python:3.8 working_directory: ~/repo-full-cp38 test_full/cp37-cp37m-manylinux2010: <<: *test_full_template environment: - MB_PYTHON_TAG=cp37 docker: - image: cimg/python:3.7 working_directory: ~/repo-full-cp37 test_full/cp36-cp36m-manylinux2010: <<: *test_full_template environment: - MB_PYTHON_TAG=cp36 docker: - image: cimg/python:3.6 working_directory: ~/repo-full-cp36 __scratch_work__: docker: - image: pypy:3 working_directory: ~/dev-only-not-a-real-job steps: - | __doc__=" # Run circleci scripts on a local machine # snap install circleci mkdir -p $HOME/Downloads curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | DESTDIR=$HOME/Downloads bash circleci update circleci switch circleci config validate circleci local execute --job test_full/cp39-cp39-manylinux2010 circleci local execute --config .circleci/config.yml --job test_full/cp38-cp38-manylinux2010 circleci local execute --config .circleci/config.yml circleci local execute --job test_full/cp38-cp38-manylinux2010 " line_profiler-4.1.2/.codecov.yml000066400000000000000000000007671452103132100165740ustar00rootroot00000000000000# For more configuration details: # https://docs.codecov.io/docs/codecov-yaml # Check if this file is valid by running in bash: # curl -X POST --data-binary @.codecov.yml https://codecov.io/validate codecov: require_ci_to_pass: yes coverage: precision: 2 round: down range: "50...100" parsers: gcov: branch_detection: conditional: yes loop: yes method: no macro: no comment: layout: "reach,diff,flags,files,footer" behavior: default require_changes: no line_profiler-4.1.2/.github/000077500000000000000000000000001452103132100156775ustar00rootroot00000000000000line_profiler-4.1.2/.github/dependabot.yml000066400000000000000000000002671452103132100205340ustar00rootroot00000000000000version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" day: "friday" line_profiler-4.1.2/.github/workflows/000077500000000000000000000000001452103132100177345ustar00rootroot00000000000000line_profiler-4.1.2/.github/workflows/tests.yml000066400000000000000000000423411452103132100216250ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions # Based on ~/code/xcookie/xcookie/rc/tests.yml.in # Now based on ~/code/xcookie/xcookie/builders/github_actions.py # See: https://github.com/Erotemic/xcookie name: BinPyCI on: push: pull_request: branches: [ main ] jobs: lint_job: ## # Run quick linting and typing checks. # To disable all linting add "linter=false" to the xcookie config. # To disable type checks add "notypes" to the xcookie tags. ## runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v4 - name: Set up Python 3.12 for linting uses: actions/setup-python@v4.7.1 with: python-version: '3.12' - name: Install dependencies run: |- python -m pip install --upgrade pip python -m pip install flake8 - name: Lint with flake8 run: |- # stop the build if there are Python syntax errors or undefined names flake8 ./line_profiler --count --select=E9,F63,F7,F82 --show-source --statistics - name: Typecheck with mypy run: |- python -m pip install mypy mypy --install-types --non-interactive ./line_profiler mypy ./line_profiler build_and_test_sdist: ## # Build the binary package from source and test it in the same # environment. ## name: Build sdist runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v4 - name: Set up Python 3.12 uses: actions/setup-python@v4.7.1 with: python-version: '3.12' - name: Upgrade pip run: |- python -m pip install --upgrade pip python -m pip install --prefer-binary -r requirements/tests.txt python -m pip install --prefer-binary -r requirements/runtime.txt - name: Build sdist shell: bash run: |- python -m pip install pip -U python -m pip install setuptools>=0.8 wheel build python -m build --sdist --outdir wheelhouse - name: Install sdist run: |- ls -al ./wheelhouse pip install --prefer-binary wheelhouse/line_profiler*.tar.gz -v - name: Test minimal loose sdist run: |- pwd ls -al # Run in a sandboxed directory WORKSPACE_DNAME="testsrcdir_minimal_${CI_PYTHON_VERSION}_${GITHUB_RUN_ID}_${RUNNER_OS}" mkdir -p $WORKSPACE_DNAME cd $WORKSPACE_DNAME # Run the tests # Get path to installed package MOD_DPATH=$(python -c "import line_profiler, os; print(os.path.dirname(line_profiler.__file__))") echo "MOD_DPATH = $MOD_DPATH" python -m pytest --verbose --cov={self.mod_name} $MOD_DPATH ../tests cd .. - name: Test full loose sdist run: |- pwd ls -al true # Run in a sandboxed directory WORKSPACE_DNAME="testsrcdir_full_${CI_PYTHON_VERSION}_${GITHUB_RUN_ID}_${RUNNER_OS}" mkdir -p $WORKSPACE_DNAME cd $WORKSPACE_DNAME # Run the tests # Get path to installed package MOD_DPATH=$(python -c "import line_profiler, os; print(os.path.dirname(line_profiler.__file__))") echo "MOD_DPATH = $MOD_DPATH" python -m pytest --verbose --cov={self.mod_name} $MOD_DPATH ../tests cd .. - name: Upload sdist artifact uses: actions/upload-artifact@v3 with: name: wheels path: ./wheelhouse/*.tar.gz build_binpy_wheels: ## # Build the binary wheels. Note: even though cibuildwheel will test # them internally here, we will test them independently later in the # test_binpy_wheels step. ## name: ${{ matrix.os }}, arch=${{ matrix.arch }} runs-on: ${{ matrix.os }} strategy: matrix: # Normally, xcookie generates explicit lists of platforms to build / test # on, but in this case cibuildwheel does that for us, so we need to just # set the environment variables for cibuildwheel. These are parsed out of # the standard [tool.cibuildwheel] section in pyproject.toml and set # explicitly here. os: - ubuntu-latest - macOS-latest - windows-latest cibw_skip: - '*-win32' arch: - auto steps: - name: Checkout source uses: actions/checkout@v4 - name: Enable MSVC 64bit uses: ilammy/msvc-dev-cmd@v1 if: matrix.os == 'windows-latest' && ${{ contains(matrix.cibw_skip, '*-win32') }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 if: runner.os == 'Linux' && matrix.arch != 'auto' with: platforms: all - name: Build binary wheels uses: pypa/cibuildwheel@v2.16.2 with: output-dir: wheelhouse config-file: pyproject.toml env: CIBW_SKIP: ${{ matrix.cibw_skip }} CIBW_ARCHS_LINUX: ${{ matrix.arch }} - name: Show built files shell: bash run: ls -la wheelhouse - name: Set up Python 3.12 to combine coverage uses: actions/setup-python@v4.7.1 if: runner.os == 'Linux' with: python-version: '3.12' - name: Combine coverage Linux if: runner.os == 'Linux' run: |- echo '############ PWD' pwd cp .wheelhouse/.coverage* . || true ls -al python -m pip install coverage[toml] echo '############ combine' coverage combine . || true echo '############ XML' coverage xml -o ./coverage.xml || true echo '### The cwd should now have a coverage.xml' ls -altr pwd - uses: codecov/codecov-action@v3 name: Codecov Upload with: file: ./coverage.xml - uses: actions/upload-artifact@v3 name: Upload wheels artifact with: name: wheels path: ./wheelhouse/line_profiler*.whl test_binpy_wheels: ## # Download the previously build binary wheels from the # build_binpy_wheels step, and test them in an independent # environment. ## name: ${{ matrix.python-version }} on ${{ matrix.os }}, arch=${{ matrix.arch }} with ${{ matrix.install-extras }} runs-on: ${{ matrix.os }} needs: - build_binpy_wheels strategy: matrix: # Xcookie generates an explicit list of environments that will be used # for testing instead of using the more concise matrix notation. include: - python-version: '3.6' install-extras: tests-strict,runtime-strict os: ubuntu-20.04 arch: auto - python-version: '3.6' install-extras: tests-strict,runtime-strict os: macOS-latest arch: auto - python-version: '3.6' install-extras: tests-strict,runtime-strict os: windows-latest arch: auto - python-version: '3.12' install-extras: tests-strict,runtime-strict,optional-strict os: ubuntu-latest arch: auto - python-version: '3.12' install-extras: tests-strict,runtime-strict,optional-strict os: macOS-latest arch: auto - python-version: '3.12' install-extras: tests-strict,runtime-strict,optional-strict os: windows-latest arch: auto - python-version: '3.12' install-extras: tests os: windows-latest arch: auto - python-version: '3.12' install-extras: tests os: windows-latest arch: auto - python-version: '3.6' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.7' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.8' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.9' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.10' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.11' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.12' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.6' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.7' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.8' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.9' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.10' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.11' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.12' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.6' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.7' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.8' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.9' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.10' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.11' install-extras: tests,optional os: windows-latest arch: auto - python-version: '3.12' install-extras: tests,optional os: windows-latest arch: auto steps: - name: Checkout source uses: actions/checkout@v4 - name: Enable MSVC 64bit uses: ilammy/msvc-dev-cmd@v1 if: matrix.os == 'windows-latest' - name: Set up QEMU uses: docker/setup-qemu-action@v3 if: runner.os == 'Linux' && matrix.arch != 'auto' with: platforms: all - name: Setup Python uses: actions/setup-python@v4.7.1 with: python-version: ${{ matrix.python-version }} - uses: actions/download-artifact@v3 name: Download wheels with: name: wheels path: wheelhouse - name: Install wheel ${{ matrix.install-extras }} shell: bash env: INSTALL_EXTRAS: ${{ matrix.install-extras }} run: |- echo "Finding the path to the wheel" ls wheelhouse || echo "wheelhouse does not exist" echo "Installing helpers" pip install setuptools>=0.8 setuptools_scm wheel build -U pip install tomli pkginfo export WHEEL_FPATH=$(python -c "import pathlib; print(str(sorted(pathlib.Path('wheelhouse').glob('line_profiler*.whl'))[-1]).replace(chr(92), chr(47)))") export MOD_VERSION=$(python -c "from pkginfo import Wheel; print(Wheel('$WHEEL_FPATH').version)") pip install --prefer-binary "line_profiler[$INSTALL_EXTRAS]==$MOD_VERSION" -f wheelhouse echo "Install finished." - name: Test wheel ${{ matrix.install-extras }} shell: bash env: CI_PYTHON_VERSION: py${{ matrix.python-version }} run: |- echo "Creating test sandbox directory" export WORKSPACE_DNAME="testdir_${CI_PYTHON_VERSION}_${GITHUB_RUN_ID}_${RUNNER_OS}" echo "WORKSPACE_DNAME=$WORKSPACE_DNAME" mkdir -p $WORKSPACE_DNAME echo "cd-ing into the workspace" cd $WORKSPACE_DNAME pwd ls -altr # Get the path to the installed package and run the tests export MOD_DPATH=$(python -c "import line_profiler, os; print(os.path.dirname(line_profiler.__file__))") echo " --- MOD_DPATH = $MOD_DPATH --- running the pytest command inside the workspace --- " python -m pytest --verbose -p pytester -p no:doctest --xdoctest --cov-config ../pyproject.toml --cov-report term --cov="line_profiler" "$MOD_DPATH" ../tests echo "pytest command finished, moving the coverage file to the repo root" ls -al # Move coverage file to a new name mv .coverage "../.coverage.$WORKSPACE_DNAME" echo "changing directory back to th repo root" cd .. ls -al - name: Combine coverage Linux if: runner.os == 'Linux' run: |- echo '############ PWD' pwd cp .wheelhouse/.coverage* . || true ls -al python -m pip install coverage[toml] echo '############ combine' coverage combine . || true echo '############ XML' coverage xml -o ./coverage.xml || true echo '### The cwd should now have a coverage.xml' ls -altr pwd - uses: codecov/codecov-action@v3 name: Codecov Upload with: file: ./coverage.xml test_deploy: name: Uploading Test to PyPi runs-on: ubuntu-latest if: github.event_name == 'push' && ! startsWith(github.event.ref, 'refs/tags') && ! startsWith(github.event.ref, 'refs/heads/release') needs: - build_and_test_sdist - build_binpy_wheels - test_binpy_wheels steps: - name: Checkout source uses: actions/checkout@v4 - uses: actions/download-artifact@v3 name: Download wheels and sdist with: name: wheels path: wheelhouse - name: Show files to upload shell: bash run: ls -la wheelhouse - name: Sign and Publish env: TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.TEST_TWINE_PASSWORD }} CI_SECRET: ${{ secrets.CI_SECRET }} run: |- GPG_EXECUTABLE=gpg $GPG_EXECUTABLE --version openssl version $GPG_EXECUTABLE --list-keys echo "Decrypting Keys" openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:CI_SECRET -d -a -in dev/ci_public_gpg_key.pgp.enc | $GPG_EXECUTABLE --import openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:CI_SECRET -d -a -in dev/gpg_owner_trust.enc | $GPG_EXECUTABLE --import-ownertrust openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:CI_SECRET -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | $GPG_EXECUTABLE --import echo "Finish Decrypt Keys" $GPG_EXECUTABLE --list-keys || true $GPG_EXECUTABLE --list-keys || echo "first invocation of gpg creates directories and returns 1" $GPG_EXECUTABLE --list-keys VERSION=$(python -c "import setup; print(setup.VERSION)") pip install twine pip install urllib3 requests[security] twine GPG_KEYID=$(cat dev/public_gpg_key) echo "GPG_KEYID = '$GPG_KEYID'" DO_GPG=True GPG_KEYID=$GPG_KEYID TWINE_REPOSITORY_URL=${TWINE_REPOSITORY_URL} TWINE_PASSWORD=$TWINE_PASSWORD TWINE_USERNAME=$TWINE_USERNAME GPG_EXECUTABLE=$GPG_EXECUTABLE DO_UPLOAD=True DO_TAG=False ./publish.sh live_deploy: name: Uploading Live to PyPi runs-on: ubuntu-latest if: github.event_name == 'push' && (startsWith(github.event.ref, 'refs/tags') || startsWith(github.event.ref, 'refs/heads/release')) needs: - build_and_test_sdist - build_binpy_wheels - test_binpy_wheels steps: - name: Checkout source uses: actions/checkout@v4 - uses: actions/download-artifact@v3 name: Download wheels and sdist with: name: wheels path: wheelhouse - name: Show files to upload shell: bash run: ls -la wheelhouse - name: Sign and Publish env: TWINE_REPOSITORY_URL: https://upload.pypi.org/legacy/ TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} CI_SECRET: ${{ secrets.CI_SECRET }} run: |- GPG_EXECUTABLE=gpg $GPG_EXECUTABLE --version openssl version $GPG_EXECUTABLE --list-keys echo "Decrypting Keys" openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:CI_SECRET -d -a -in dev/ci_public_gpg_key.pgp.enc | $GPG_EXECUTABLE --import openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:CI_SECRET -d -a -in dev/gpg_owner_trust.enc | $GPG_EXECUTABLE --import-ownertrust openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:CI_SECRET -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | $GPG_EXECUTABLE --import echo "Finish Decrypt Keys" $GPG_EXECUTABLE --list-keys || true $GPG_EXECUTABLE --list-keys || echo "first invocation of gpg creates directories and returns 1" $GPG_EXECUTABLE --list-keys VERSION=$(python -c "import setup; print(setup.VERSION)") pip install twine pip install urllib3 requests[security] twine GPG_KEYID=$(cat dev/public_gpg_key) echo "GPG_KEYID = '$GPG_KEYID'" DO_GPG=True GPG_KEYID=$GPG_KEYID TWINE_REPOSITORY_URL=${TWINE_REPOSITORY_URL} TWINE_PASSWORD=$TWINE_PASSWORD TWINE_USERNAME=$TWINE_USERNAME GPG_EXECUTABLE=$GPG_EXECUTABLE DO_UPLOAD=True DO_TAG=False ./publish.sh line_profiler-4.1.2/.gitignore000066400000000000000000000003431452103132100163270ustar00rootroot00000000000000# Swap files. .*.swp .*.swo *~ *.pyc *.pyo *.pyd *.so *.o *.a *.cpp build/ dist/ _skbuild _line_profiler.c line_profiler.egg-info/ MANIFEST pypi-site-docs.zip index.html .coverage tests/coverage.xml tests/htmlcov wheelhouse line_profiler-4.1.2/.readthedocs.yml000066400000000000000000000006541452103132100174320ustar00rootroot00000000000000# .readthedocs.yml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # # See Also: # https://readthedocs.org/dashboard/line_profiler/advanced/ # Required version: 2 build: os: "ubuntu-22.04" tools: python: "3.11" sphinx: configuration: docs/source/conf.py formats: all python: install: - requirements: requirements/docs.txt - method: pip path: . line_profiler-4.1.2/CHANGELOG.rst000066400000000000000000000132051452103132100163610ustar00rootroot00000000000000Changes ======= 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 4.1.1 ~~~~ * FIX: ``get_stats`` is no longer slowed down when profiling many code sections #236 4.1.0 ~~~~ * 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 4.0.3 ~~~~ * FIX: Stop requiring bleeding-edge Cython unless necesasry (for Python 3.12). #206 4.0.2 ~~~~~ * FIX: AttributeError on certain methods. #191 4.0.1 ~~~~~ * FIX: Profiling classmethods works again. #183 4.0.0 ~~~~~ * 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++. You can expect to see reductions of between 0.3 and 1 microseconds per line hit, resulting in a speedup of up to 4x for codebases with many lines of Python that only do a little work per line. * ENH: Added the ``-i <# of seconds>`` option to the ``kernprof`` script. This uses the threading module to output profiling data to the output file every n seconds, and is useful for long-running tasks that shouldn't be stopped in the middle of processing. * 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. Any code that directly reads (and processes) or edits the code_map and/or last_time attributes will likely break. 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 3.5.1 ~~~~~ * FIX: #19 line profiler now works on async functions again 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 3.4.0 ~~~~~ * Drop support for Python <= 3.5.x * FIX: #104 issue with new IPython kernels 3.3.1 ~~~~~ * FIX: Fix bug where lines were not displayed in Jupyter>=6.0 via #93 * CHANGE: moving forward, new pypi releases will be signed with the GPG key 2A290272C174D28EA9CA48E9D7224DAF0347B114 for PyUtils-CI . For reference, older versions were signed with either 262A1DF005BE5D2D5210237C85CD61514641325F or 1636DAF294BA22B89DBB354374F166CFA2F39C18. 3.3.0 ~~~~~ * New CI for building wheels. 3.2.6 ~~~~~ * FIX: Update MANIFEST.in to package pyproj.toml and missing pyx file * CHANGE: Removed version experimental augmentation. 3.2.5 ~~~~~ * FIX: Update MANIFEST.in to package nested c source files in the sdist 3.2.4 ~~~~~ * FIX: Update MANIFEST.in to package nested CMakeLists.txt in the sdist 3.2.3 ~~~~~ * FIX: Use ImportError instead of ModuleNotFoundError while 3.5 is being supported * FIX: Add MANIFEST.in to package CMakeLists.txt in the sdist 3.2.2 ~~~~~ * ENH: Added better error message when c-extension is not compiled. * FIX: Kernprof no longer imports line_profiler to avoid side effects. 3.2.0 ~~~~~ * Dropped 2.7 support, manylinux docker images no longer support 2.7 * ENH: Add command line option to specify time unit and skip displaying functions which have not been profiled. * ENH: Unified versions of line_profiler and kernprof: kernprof version is now identical to line_profiler version. 3.1.0 ~~~~~ * ENH: fix Python 3.9 3.0.2 ~~~~~ * BUG: fix ``__version__`` attribute in Python 2 CLI. 3.0.1 ~~~~~ * BUG: fix calling the package from the command line 3.0.0 ~~~~~ * ENH: Fix Python 3.7 * ENH: Restructure into package 2.1 ~~~ * 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 2.0 ~~~ * BUG: Added support for IPython 5.0+, removed support for IPython <=0.12 1.1 ~~~ * BUG: Read source files as bytes. 1.0 ~~~ * ENH: `kernprof.py` is now installed as `kernprof`. * ENH: Python 3 support. Thanks to the long-suffering Mikhail Korobov for being patient. * Dropped 2.6 as it was too annoying. * ENH: The `stripzeros` and `add_module` options. Thanks to Erik Tollerud for contributing it. * ENH: Support for IPython cell blocks. Thanks to Michael Forbes for adding this feature. * ENH: Better warnings when building without Cython. Thanks to David Cournapeau for spotting this. 1.0b3 ~~~~~ * ENH: Profile generators. * BUG: Update for compatibility with newer versions of Cython. Thanks to Ondrej Certik for spotting the bug. * BUG: Update IPython compatibility for 0.11+. Thanks to Yaroslav Halchenko and others for providing the updated imports. 1.0b2 ~~~~~ * BUG: fixed line timing overflow on Windows. * DOC: improved the README. 1.0b1 ~~~~~ * Initial release. line_profiler-4.1.2/CMakeLists.txt000066400000000000000000000026021452103132100170770ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.13.0) project(_line_profiler LANGUAGES C) ### # Private helper function to execute `python -c ""` # # Runs a python command and populates an outvar with the result of stdout. # Be careful of indentation if `cmd` is multiline. # function(pycmd outvar cmd) execute_process( COMMAND "${PYTHON_EXECUTABLE}" -c "${cmd}" RESULT_VARIABLE _exitcode OUTPUT_VARIABLE _output) if(NOT ${_exitcode} EQUAL 0) message(ERROR "Failed when running python code: \"\"\" ${cmd}\"\"\"") message(FATAL_ERROR "Python command failed with error code: ${_exitcode}") endif() # Remove supurflous newlines (artifacts of print) string(STRIP "${_output}" _output) set(${outvar} "${_output}" PARENT_SCOPE) endfunction() find_package(PythonInterp REQUIRED) ### # Find scikit-build and include its cmake resource scripts # if (NOT SKBUILD) pycmd(skbuild_location "import os, skbuild; print(os.path.dirname(skbuild.__file__))") set(skbuild_cmake_dir "${skbuild_location}/resources/cmake") message(STATUS "[LINE_PROFILER] skbuild_cmake_dir = ${skbuild_cmake_dir}") # If skbuild is not the driver, then we need to include its utilities in our CMAKE_MODULE_PATH list(APPEND CMAKE_MODULE_PATH ${skbuild_cmake_dir}) endif() find_package(Cython REQUIRED) find_package(PythonExtensions REQUIRED) find_package(PythonLibs REQUIRED) add_subdirectory("line_profiler") line_profiler-4.1.2/LICENSE.txt000066400000000000000000000031201452103132100161560ustar00rootroot00000000000000This software is OSI Certified Open Source Software. OSI Certified is a certification mark of the Open Source Initiative. Copyright (c) 2008, Enthought, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Enthought, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. line_profiler-4.1.2/LICENSE_Python.txt000066400000000000000000000330571452103132100175330ustar00rootroot00000000000000The file timers.c was derived from the timer code in Python 2.5.2's _lsprof.c file and falls under the PSF license given below. A. HISTORY OF THE SOFTWARE ========================== Python was created in the early 1990s by Guido van Rossum at Stichting Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands as a successor of a language called ABC. Guido remains Python's principal author, although it includes many contributions from others. In 1995, Guido continued his work on Python at the Corporation for National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) in Reston, Virginia where he released several versions of the software. In May 2000, Guido and the Python core development team moved to BeOpen.com to form the BeOpen PythonLabs team. In October of the same year, the PythonLabs team moved to Digital Creations (now Zope Corporation, see http://www.zope.com). In 2001, the Python Software Foundation (PSF, see http://www.python.org/psf/) was formed, a non-profit organization created specifically to own Python-related Intellectual Property. Zope Corporation is a sponsoring member of the PSF. All Python releases are Open Source (see http://www.opensource.org for the Open Source Definition). Historically, most, but not all, Python releases have also been GPL-compatible; the table below summarizes the various releases. Release Derived Year Owner GPL- from compatible? (1) 0.9.0 thru 1.2 1991-1995 CWI yes 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 1.6 1.5.2 2000 CNRI no 2.0 1.6 2000 BeOpen.com no 1.6.1 1.6 2001 CNRI yes (2) 2.1 2.0+1.6.1 2001 PSF no 2.0.1 2.0+1.6.1 2001 PSF yes 2.1.1 2.1+2.0.1 2001 PSF yes 2.2 2.1.1 2001 PSF yes 2.1.2 2.1.1 2002 PSF yes 2.1.3 2.1.2 2002 PSF yes 2.2.1 2.2 2002 PSF yes 2.2.2 2.2.1 2002 PSF yes 2.2.3 2.2.2 2003 PSF yes 2.3 2.2.2 2002-2003 PSF yes 2.3.1 2.3 2002-2003 PSF yes 2.3.2 2.3.1 2002-2003 PSF yes 2.3.3 2.3.2 2002-2003 PSF yes 2.3.4 2.3.3 2004 PSF yes 2.3.5 2.3.4 2005 PSF yes 2.4 2.3 2004 PSF yes 2.4.1 2.4 2005 PSF yes 2.4.2 2.4.1 2005 PSF yes 2.4.3 2.4.2 2006 PSF yes 2.4.4 2.4.3 2006 PSF yes 2.5 2.4 2006 PSF yes 2.5.1 2.5 2007 PSF yes 2.5.2 2.5.2 2008 PSF yes Footnotes: (1) GPL-compatible doesn't mean that we're distributing Python under the GPL. All Python licenses, unlike the GPL, let you distribute a modified version without making your changes open source. The GPL-compatible licenses make it possible to combine Python with other software that is released under the GPL; the others don't. (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, because its license has a choice of law clause. According to CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 is "not incompatible" with the GPL. Thanks to the many outside volunteers who have worked under Guido's direction to make these releases possible. B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON =============================================================== PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 ------------------------------------------- BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the Individual or Organization ("Licensee") accessing and otherwise using this software in source or binary form and its associated documentation ("the Software"). 2. Subject to the terms and conditions of this BeOpen Python License Agreement, BeOpen hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use the Software alone or in any derivative version, provided, however, that the BeOpen Python License is retained in the Software, alone or in any derivative version prepared by Licensee. 3. BeOpen is making the Software available to Licensee on an "AS IS" basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 5. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 6. This License Agreement shall be governed by and interpreted in all respects by the law of the State of California, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between BeOpen and Licensee. This License Agreement does not grant permission to use BeOpen trademarks or trade names in a trademark sense to endorse or promote products or services of Licensee, or any third party. As an exception, the "BeOpen Python" logos available at http://www.pythonlabs.com/logos.html may be used according to the permissions granted on that web page. 7. By copying, installing or otherwise using the software, Licensee agrees to be bound by the terms and conditions of this License Agreement. CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 --------------------------------------- 1. This LICENSE AGREEMENT is between the Corporation for National Research Initiatives, having an office at 1895 Preston White Drive, Reston, VA 20191 ("CNRI"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 1.6.1 software in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, CNRI hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 1.6.1 alone or in any derivative version, provided, however, that CNRI's License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 1995-2001 Corporation for National Research Initiatives; All Rights Reserved" are retained in Python 1.6.1 alone or in any derivative version prepared by Licensee. Alternately, in lieu of CNRI's License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6.1 is made available subject to the terms and conditions in CNRI's License Agreement. This Agreement together with Python 1.6.1 may be located on the Internet using the following unique, persistent identifier (known as a handle): 1895.22/1013. This Agreement may also be obtained from a proxy server on the Internet using the following URL: http://hdl.handle.net/1895.22/1013". 3. In the event Licensee prepares a derivative work that is based on or incorporates Python 1.6.1 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python 1.6.1. 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. This License Agreement shall be governed by the federal intellectual property law of the United States, including without limitation the federal copyright law, and, to the extent such U.S. federal law does not apply, by the law of the Commonwealth of Virginia, excluding Virginia's conflict of law provisions. Notwithstanding the foregoing, with regard to derivative works based on Python 1.6.1 that incorporate non-separable material that was previously distributed under the GNU General Public License (GPL), the law of the Commonwealth of Virginia shall govern this License Agreement only as to issues arising under or with respect to Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between CNRI and Licensee. This License Agreement does not grant permission to use CNRI trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By clicking on the "ACCEPT" button where indicated, or by copying, installing or otherwise using Python 1.6.1, Licensee agrees to be bound by the terms and conditions of this License Agreement. ACCEPT CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 -------------------------------------------------- Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, The Netherlands. All rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. line_profiler-4.1.2/MANIFEST.in000066400000000000000000000005741452103132100161030ustar00rootroot00000000000000include *.md include *.rst include *.py include *.txt include *.toml include run_tests.sh recursive-include requirements *.txt recursive-include tests *.py recursive-include line_profiler *.txt recursive-include line_profiler *.pyx recursive-include line_profiler *.pxd recursive-include line_profiler *.pyd recursive-include line_profiler *.c recursive-include line_profiler *.h line_profiler-4.1.2/README.rst000066400000000000000000000471561452103132100160430ustar00rootroot00000000000000line_profiler and kernprof -------------------------- |Pypi| |ReadTheDocs| |Downloads| |CircleCI| |GithubActions| |Codecov| This is the official ``line_profiler`` repository. The most recent version of `line-profiler `_ on pypi points to this repo. The original `line_profiler `_ package by `@rkern `_ is unmaintained. This fork is the official continuation of the project. +---------------+--------------------------------------------+ | Github | https://github.com/pyutils/line_profiler | +---------------+--------------------------------------------+ | Pypi | https://pypi.org/project/line_profiler | +---------------+--------------------------------------------+ | ReadTheDocs | https://kernprof.readthedocs.io/en/latest/ | +---------------+--------------------------------------------+ ---- ``line_profiler`` is a module for doing line-by-line profiling of functions. kernprof is a convenient script for running either ``line_profiler`` or the Python standard library's cProfile or profile modules, depending on what is available. They are available under a `BSD license`_. .. _BSD license: https://raw.githubusercontent.com/pyutils/line_profiler/master/LICENSE.txt .. contents:: Quick Start =========== To profile a python script: * Install line_profiler: ``pip install line_profiler``. * Decorate function(s) you want to profile with @profile. The decorator will be made automatically available on run. * Run ``kernprof -lv script_to_profile.py``. Installation ============ Releases of ``line_profiler`` can be installed using pip:: $ pip install line_profiler Installation while ensuring a compatible IPython version can also be installed using pip:: $ pip install line_profiler[ipython] To check out the development sources, you can use Git_:: $ git clone https://github.com/pyutils/line_profiler.git You may also download source tarballs of any snapshot from that URL. Source releases will require a C compiler in order to build `line_profiler`. In addition, git checkouts will also require Cython. Source releases on PyPI should contain the pregenerated C sources, so Cython should not be required in that case. ``kernprof`` is a single-file pure Python script and does not require a compiler. If you wish to use it to run cProfile and not line-by-line profiling, you may copy it to a directory on your ``PATH`` manually and avoid trying to build any C extensions. As of 2021-06-04 Linux (x86_64 and i686), OSX (10_9_x86_64), and Win32 (win32, and amd64) binaries are available on pypi. The last version of line profiler to support Python 2.7 was 3.1.0 and the last version to support Python 3.5 was 3.3.1. .. _git: http://git-scm.com/ .. _Cython: http://www.cython.org .. _build and install: http://docs.python.org/install/index.html line_profiler ============= The current profiling tools supported in Python only time function calls. This is a good first step for locating hotspots in one's program and is frequently all one needs to do to optimize the program. However, sometimes the cause of the hotspot is actually a single line in the function, and that line may not be obvious from just reading the source code. These cases are particularly frequent in scientific computing. Functions tend to be larger (sometimes because of legitimate algorithmic complexity, sometimes because the programmer is still trying to write FORTRAN code), and a single statement without function calls can trigger lots of computation when using libraries like numpy. cProfile only times explicit function calls, not special methods called because of syntax. Consequently, a relatively slow numpy operation on large arrays like this, :: a[large_index_array] = some_other_large_array is a hotspot that never gets broken out by cProfile because there is no explicit function call in that statement. LineProfiler can be given functions to profile, and it will time the execution of each individual line inside those functions. In a typical workflow, one only cares about line timings of a few functions because wading through the results of timing every single line of code would be overwhelming. However, LineProfiler does need to be explicitly told what functions to profile. The easiest way to get started is to use the ``kernprof`` script. :: $ kernprof -l script_to_profile.py ``kernprof`` will create an instance of LineProfiler and insert it into the ``__builtins__`` namespace with the name ``profile``. It has been written to be used as a decorator, so in your script, you decorate the functions you want to profile with @profile. :: @profile def slow_function(a, b, c): ... The default behavior of ``kernprof`` is to put the results into a binary file script_to_profile.py.lprof . You can tell ``kernprof`` to immediately view the formatted results at the terminal with the [-v/--view] option. Otherwise, you can view the results later like so:: $ python -m line_profiler script_to_profile.py.lprof For example, here are the results of profiling a single function from a decorated version of the pystone.py benchmark (the first two lines are output from ``pystone.py``, not ``kernprof``):: Pystone(1.1) time for 50000 passes = 2.48 This machine benchmarks at 20161.3 pystones/second Wrote profile results to pystone.py.lprof Timer unit: 1e-06 s File: pystone.py Function: Proc2 at line 149 Total time: 0.606656 s Line # Hits Time Per Hit % Time Line Contents ============================================================== 149 @profile 150 def Proc2(IntParIO): 151 50000 82003 1.6 13.5 IntLoc = IntParIO + 10 152 50000 63162 1.3 10.4 while 1: 153 50000 69065 1.4 11.4 if Char1Glob == 'A': 154 50000 66354 1.3 10.9 IntLoc = IntLoc - 1 155 50000 67263 1.3 11.1 IntParIO = IntLoc - IntGlob 156 50000 65494 1.3 10.8 EnumLoc = Ident1 157 50000 68001 1.4 11.2 if EnumLoc == Ident1: 158 50000 63739 1.3 10.5 break 159 50000 61575 1.2 10.1 return IntParIO The source code of the function is printed with the timing information for each line. There are six columns of information. * Line #: The line number in the file. * Hits: The number of times that line was executed. * Time: The total amount of time spent executing the line in the timer's units. In the header information before the tables, you will see a line "Timer unit:" giving the conversion factor to seconds. It may be different on different systems. * Per Hit: The average amount of time spent executing the line once in the timer's units. * % Time: The percentage of time spent on that line relative to the total amount of recorded time spent in the function. * Line Contents: The actual source code. Note that this is always read from disk when the formatted results are viewed, *not* when the code was executed. If you have edited the file in the meantime, the lines will not match up, and the formatter may not even be able to locate the function for display. If you are using IPython, there is an implementation of an %lprun magic command which will let you specify functions to profile and a statement to execute. It will also add its LineProfiler instance into the __builtins__, but typically, you would not use it like that. For IPython 0.11+, you can install it by editing the IPython configuration file ``~/.ipython/profile_default/ipython_config.py`` to add the ``'line_profiler'`` item to the extensions list:: c.TerminalIPythonApp.extensions = [ 'line_profiler', ] Or explicitly call:: %load_ext line_profiler To get usage help for %lprun, use the standard IPython help mechanism:: In [1]: %lprun? These two methods are expected to be the most frequent user-level ways of using LineProfiler and will usually be the easiest. However, if you are building other tools with LineProfiler, you will need to use the API. There are two ways to inform LineProfiler of functions to profile: you can pass them as arguments to the constructor or use the ``add_function(f)`` method after instantiation. :: profile = LineProfiler(f, g) profile.add_function(h) LineProfiler has the same ``run()``, ``runctx()``, and ``runcall()`` methods as cProfile.Profile as well as ``enable()`` and ``disable()``. It should be noted, though, that ``enable()`` and ``disable()`` are not entirely safe when nested. Nesting is common when using LineProfiler as a decorator. In order to support nesting, use ``enable_by_count()`` and ``disable_by_count()``. These functions will increment and decrement a counter and only actually enable or disable the profiler when the count transitions from or to 0. After profiling, the ``dump_stats(filename)`` method will pickle the results out to the given file. ``print_stats([stream])`` will print the formatted results to sys.stdout or whatever stream you specify. ``get_stats()`` will return LineStats object, which just holds two attributes: a dictionary containing the results and the timer unit. kernprof ======== ``kernprof`` also works with cProfile, its third-party incarnation lsprof, or the pure-Python profile module depending on what is available. It has a few main features: * Encapsulation of profiling concerns. You do not have to modify your script in order to initiate profiling and save the results. Unless if you want to use the advanced __builtins__ features, of course. * Robust script execution. Many scripts require things like __name__, __file__, and sys.path to be set relative to it. A naive approach at encapsulation would just use execfile(), but many scripts which rely on that information will fail. kernprof will set those variables correctly before executing the script. * Easy executable location. If you are profiling an application installed on your PATH, you can just give the name of the executable. If kernprof does not find the given script in the current directory, it will search your PATH for it. * Inserting the profiler into __builtins__. Sometimes, you just want to profile a small part of your code. With the [-b/--builtin] argument, the Profiler will be instantiated and inserted into your __builtins__ with the name "profile". Like LineProfiler, it may be used as a decorator, or enabled/disabled with ``enable_by_count()`` and ``disable_by_count()``, or even as a context manager with the "with profile:" statement. * Pre-profiling setup. With the [-s/--setup] option, you can provide a script which will be executed without profiling before executing the main script. This is typically useful for cases where imports of large libraries like wxPython or VTK are interfering with your results. If you can modify your source code, the __builtins__ approach may be easier. The results of profile script_to_profile.py will be written to script_to_profile.py.prof by default. It will be a typical marshalled file that can be read with pstats.Stats(). They may be interactively viewed with the command:: $ python -m pstats script_to_profile.py.prof Such files may also be viewed with graphical tools. A list of 3rd party tools built on ``cProfile`` or ``line_profiler`` are as follows: * `pyprof2calltree `_: converts profiling data to a format that can be visualized using kcachegrind_ (linux only), wincachegrind_ (windows only, unmaintained), or qcachegrind_. * `Line Profiler GUI `_: Qt GUI for line_profiler. * `SnakeViz `_: A web viewer for Python profiling data. * `SnakeRunner `_: A fork of RunSnakeRun_, ported to Python 3. * `Pycharm plugin `_: A PyCharm plugin for line_profiler. * `Spyder plugin `_: A plugin to run line_profiler from within the Spyder IDE. * `pprof `_: A render web report for ``line_profiler``. .. _qcachegrind: https://sourceforge.net/projects/qcachegrindwin/ .. _kcachegrind: https://kcachegrind.github.io/html/Home.html .. _wincachegrind: https://github.com/ceefour/wincachegrind .. _pyprof2calltree: http://pypi.python.org/pypi/pyprof2calltree/ .. _SnakeViz: https://github.com/jiffyclub/snakeviz/ .. _SnakeRunner: https://github.com/venthur/snakerunner .. _RunSnakeRun: https://pypi.org/project/RunSnakeRun/ .. _qt_profiler_gui: https://github.com/Nodd/lineprofilergui .. _pycharm_line_profiler_plugin: https://plugins.jetbrains.com/plugin/16536-line-profiler .. _spyder_line_profiler_plugin: https://github.com/spyder-ide/spyder-line-profiler .. _web_profiler_ui: https://github.com/mirecl/pprof Related Work ============ Check out these other Python profilers: * `Scalene `_: A CPU+GPU+memory sampling based profiler. * `PyInstrument `_: A call stack profiler. * `Yappi `_: A tracing profiler that is multithreading, asyncio and gevent aware. * `profile / cProfile `_: The builtin profile module. * `timeit `_: The builtin timeit module for profiling single statements. * `timerit `_: A multi-statements alternative to the builtin ``timeit`` module. Frequently Asked Questions ========================== * Why the name "kernprof"? I didn't manage to come up with a meaningful name, so I named it after myself. * The line-by-line timings don't add up when one profiled function calls another. What's up with that? Let's say you have function F() calling function G(), and you are using LineProfiler on both. The total time reported for G() is less than the time reported on the line in F() that calls G(). The reason is that I'm being reasonably clever (and possibly too clever) in recording the times. Basically, I try to prevent recording the time spent inside LineProfiler doing all of the bookkeeping for each line. Each time Python's tracing facility issues a line event (which happens just before a line actually gets executed), LineProfiler will find two timestamps, one at the beginning before it does anything (t_begin) and one as close to the end as possible (t_end). Almost all of the overhead of LineProfiler's data structures happens in between these two times. When a line event comes in, LineProfiler finds the function it belongs to. If it's the first line in the function, we record the line number and *t_end* associated with the function. The next time we see a line event belonging to that function, we take t_begin of the new event and subtract the old t_end from it to find the amount of time spent in the old line. Then we record the new t_end as the active line for this function. This way, we are removing most of LineProfiler's overhead from the results. Well almost. When one profiled function F calls another profiled function G, the line in F that calls G basically records the total time spent executing the line, which includes the time spent inside the profiler while inside G. The first time this question was asked, the questioner had the G() function call as part of a larger expression, and he wanted to try to estimate how much time was being spent in the function as opposed to the rest of the expression. My response was that, even if I could remove the effect, it might still be misleading. G() might be called elsewhere, not just from the relevant line in F(). The workaround would be to modify the code to split it up into two lines, one which just assigns the result of G() to a temporary variable and the other with the rest of the expression. I am open to suggestions on how to make this more robust. Or simple admonitions against trying to be clever. * Why do my list comprehensions have so many hits when I use the LineProfiler? LineProfiler records the line with the list comprehension once for each iteration of the list comprehension. * Why is kernprof distributed with line_profiler? It works with just cProfile, right? Partly because kernprof.py is essential to using line_profiler effectively, but mostly because I'm lazy and don't want to maintain the overhead of two projects for modules as small as these. However, kernprof.py is a standalone, pure Python script that can be used to do function profiling with just the Python standard library. You may grab it and install it by itself without ``line_profiler``. * Do I need a C compiler to build ``line_profiler``? kernprof.py? You do need a C compiler for line_profiler. kernprof.py is a pure Python script and can be installed separately, though. * Do I need Cython to build ``line_profiler``? Wheels for supported versions of Python are available on PyPI and support linux, osx, and windows for x86-64 architectures. Linux additionally ships with i686 wheels for manylinux and musllinux. If you have a different CPU architecture, or an unsupported Python version, then you will need to build from source. * What version of Python do I need? Both ``line_profiler`` and ``kernprof`` have been tested with Python 3.6-3.11. Older versions of ``line_profiler`` support older versions of Python. To Do ===== cProfile uses a neat "rotating trees" data structure to minimize the overhead of looking up and recording entries. LineProfiler uses Python dictionaries and extension objects thanks to Cython. This mostly started out as a prototype that I wanted to play with as quickly as possible, so I passed on stealing the rotating trees for now. As usual, I got it working, and it seems to have acceptable performance, so I am much less motivated to use a different strategy now. Maybe later. Contributions accepted! Bugs and Such ============= Bugs and pull requested can be submitted on GitHub_. .. _GitHub: https://github.com/pyutils/line_profiler Changes ======= See `CHANGELOG`_. .. _CHANGELOG: CHANGELOG.rst .. |CircleCI| image:: https://circleci.com/gh/pyutils/line_profiler.svg?style=svg :target: https://circleci.com/gh/pyutils/line_profiler .. |Travis| image:: https://img.shields.io/travis/pyutils/line_profiler/master.svg?label=Travis%20CI :target: https://travis-ci.org/pyutils/line_profiler?branch=master .. |Appveyor| image:: https://ci.appveyor.com/api/projects/status/github/pyutils/line_profiler?branch=master&svg=True :target: https://ci.appveyor.com/project/pyutils/line_profiler/branch/master .. |Codecov| image:: https://codecov.io/github/pyutils/line_profiler/badge.svg?branch=master&service=github :target: https://codecov.io/github/pyutils/line_profiler?branch=master .. |Pypi| image:: https://img.shields.io/pypi/v/line_profiler.svg :target: https://pypi.python.org/pypi/line_profiler .. |Downloads| image:: https://img.shields.io/pypi/dm/line_profiler.svg :target: https://pypistats.org/packages/line_profiler .. |GithubActions| image:: https://github.com/pyutils/line_profiler/actions/workflows/tests.yml/badge.svg?branch=main :target: https://github.com/pyutils/line_profiler/actions?query=branch%3Amain .. |ReadTheDocs| image:: https://readthedocs.org/projects/kernprof/badge/?version=latest :target: http://kernprof.readthedocs.io/en/latest/ line_profiler-4.1.2/build_wheels.sh000077500000000000000000000012241452103132100173430ustar00rootroot00000000000000#!/bin/bash __doc__=" Runs cibuildwheel to create linux binary wheels. Requirements: pip install cibuildwheel SeeAlso: pyproject.toml " if ! which docker ; then echo "Missing requirement: docker. Please install docker before running build_wheels.sh" exit 1 fi if ! which cibuildwheel ; then echo "The cibuildwheel module is not installed. Please pip install cibuildwheel before running build_wheels.sh" exit 1 fi #pip wheel -w wheelhouse . # python -m build --wheel -o wheelhouse # line_profiler: +COMMENT_IF(binpy) cibuildwheel --config-file pyproject.toml --platform linux --arch x86_64 # line_profiler: +UNCOMMENT_IF(binpy) line_profiler-4.1.2/clean.sh000077500000000000000000000014541452103132100157640ustar00rootroot00000000000000#!/bin/bash echo "start clean" rm -rf _skbuild rm -rf _line_profiler.c rm -rf *.so rm -rf line_profiler/_line_profiler.c rm -rf line_profiler/*.so rm -rf build rm -rf line_profiler.egg-info rm -rf dist rm -rf mb_work rm -rf wheelhouse rm -rf pip-wheel-metadata rm -rf htmlcov rm -rf tests/htmlcov rm -rf CMakeCache.txt rm -rf CMakeTmp rm -rf CMakeFiles rm -rf tests/htmlcov rm -rf demo_primes* rm -rf docs/demo.py* rm -rf docs/script_to_profile.py* rm -rf tests/complex_example.py.lprof rm -rf tests/complex_example.py.prof rm -rf script_to_profile.py* if [ -f "distutils.errors" ]; then rm distutils.errors || echo "skip rm" fi CLEAN_PYTHON='find . -regex ".*\(__pycache__\|\.py[co]\)" -delete || find . -iname *.pyc -delete || find . -iname *.pyo -delete' bash -c "$CLEAN_PYTHON" echo "finish clean" line_profiler-4.1.2/dev/000077500000000000000000000000001452103132100151155ustar00rootroot00000000000000line_profiler-4.1.2/dev/autoprofile-poc.py000066400000000000000000000077701452103132100206120ustar00rootroot00000000000000import ubelt as ub # try: # import ast # unparse = ast.unparse # except AttributeError: # try: # import astunparse # unparse = astunparse.unparse # except ModuleNotFoundError: # unparse = None # import sys # sys.path.append('../') from line_profiler.autoprofile import autoprofile def create_poc(dry_run=False): root = ub.Path.appdir('line_profiler/test/poc/') repo = (root / 'repo') modpaths = {} modpaths['script'] = (root / 'repo/script.py') modpaths['foo'] = (root / 'repo/foo') modpaths['foo.__init__'] = (root / 'repo/foo/__init__.py') modpaths['foo.bar'] = (root / 'repo/foo/bar.py') modpaths['foo.baz'] = (root / 'repo/foo/baz') modpaths['foo.baz.__init__'] = (root / 'repo/foo/baz/__init__.py') modpaths['foo.baz.spam'] = (root / 'repo/foo/baz/spam.py') modpaths['foo.baz.eggs'] = (root / 'repo/foo/baz/eggs.py') if not dry_run: root.delete().ensuredir() repo.ensuredir() modpaths['script'].touch() modpaths['foo'].ensuredir() modpaths['foo.__init__'].touch() modpaths['foo.bar'].touch() modpaths['foo.bar'].write_text('def asdf():\n 2**(1/65536)') modpaths['foo.baz'].ensuredir() modpaths['foo.baz.__init__'].touch() modpaths['foo.baz.spam'].touch() modpaths['foo.baz.spam'].write_text('def spamfunc():\n ...') modpaths['foo.baz.eggs'].touch() modpaths['foo.baz.eggs'].write_text('def eggfunc():\n ...') """different import variations to handle""" script_text = ub.codeblock( ''' import foo # mod import foo.bar # py from foo import bar # py from foo.bar import asdf # fn import foo.baz as foodotbaz # mod from foo import baz as foobaz # mod from foo import bar, baz as baz2 # py,mod import foo.baz.eggs # py from foo.baz import eggs # py from foo.baz.eggs import eggfunc # fn from foo.baz.spam import spamfunc as yum # fn from numpy import round # fn # @profile def test(): 2**65536 foo.bar.asdf() def main(): 2**65536 test() # foo.bar.asdf() main() test() # asdf() ''') ub.writeto(modpaths['script'], script_text) return root, repo, modpaths def main(): root, repo, modpaths = create_poc(dry_run=False) script_file = str(modpaths['script']) """separate from prof_mod, profile all imports in script""" profile_script_imports = False """modnames to profile""" modnames = [ # 'fool', # doesn't exist # 'foo', 'foo.bar', # 'foo.baz', # 'foo.baz.eggs', # 'foo.baz.spam', # 'numpy.round', ] """modpaths to profile""" modpaths = [ # str(root), # str((repo / 'fool')), # doesn't exist # str(modpaths['foo']), # str(modpaths['foo.__init__']), # str(modpaths['foo.bar']), # str(modpaths['foo.baz']), # str(modpaths['foo.baz.__init__']), # str(modpaths['foo.baz.spam']), # str(modpaths['foo.baz.eggs']), # str(modpaths['script']), # special case to profile all items ] prof_mod = modnames + modpaths # prof_mod = modpaths # prof_mod = modnames """mimick running using kernprof""" import sys import os import builtins __file__ = script_file __name__ = '__main__' script_directory = os.path.realpath(os.path.dirname(script_file)) sys.path.insert(0, script_directory) import line_profiler prof = line_profiler.LineProfiler() builtins.__dict__['profile'] = prof ns = locals() autoprofile.run(script_file, ns, prof_mod=prof_mod) print('\nprofiled') print('=' * 10) prof.print_stats(output_unit=1e-6, stripzeros=True, stream=sys.stdout) if __name__ == '__main__': main() line_profiler-4.1.2/dev/ci_public_gpg_key.pgp.enc000066400000000000000000000037371452103132100220410ustar00rootroot00000000000000U2FsdGVkX1+qPKg9Cb2XjakyhUJn2P6eAD8akDWJ9k9qlFY/C6vZCWjcOzw8Em/7 /VMY09zTYVb5EhhZIjxiFPjvcK7EKXi0WaZ5qmNDIE1qV1VlNP8UQB66/Q5rPz/R NDQmfPJvI2675vRbqreGA8n1+5wwwszpY4EIfzfDDnBV96kNLXE7q7gHGR0aGEz9 1c5gCXbjbFBuGD/9eW4IffQWvUr0AieI+uvre+ggnBCFGnrkdYEB5g4GRu5SKOdb heMM3QFPd3giTMIdJQvucXYCIDJ7bQJpTXtovY/1Ni/FDbeGquuXx6w+mJrDlQxM iHQg7tOMl5Lgn9WlU+PWndJHyo7x3yUxHUh+8+R/3og3Z7XaN8zApdj3Y6fMja8s a9IomJGYxmTkuQ43YPe8P6RsBwLAwaxWAgUoZJ67DELdlejZNuga6iXQvA4nld+A eGIX+S2cmfCC5oU4eWBIfNsAHoP3lBE84JTC+mDeOfiRY2ZMTIT4VIyznq1H0unb Ws8mtzwFAW9b2a/E6dFqUMfu0h4n3R+g+IvuhxU6CV/jkFPrVq/Er8A5RyyVJxn4 xL5X3X7tagmM4SOdWztF0koFy7PEQIYoLwCbedPn9Ew3zOR4O05xk3/CvGH/bVzR /53rwlrtFzE3oDVXWH9gzdKvLv52PUTV7Rv5xFXlqyNAajLSZe7X7xC9PSv94T8I kJWGSOq+5uSXJpgBdT5BYaHlM7QPzC58Qs3c+h3LNb9UzPHIyP0q6bYRRkZ1dY1t QRyuHaQearqkFrcsLg7CXKL+etI5mV+3m9KXZKHBLYigY5YcaJnW6NM4A8LzUJrV CC4JwetxtFY3hSrRuX+5GgzciJiVkkSupDo8guWhXCIPt0G8jIQYOXjk9y+w6Qng GuJJmI+rgQczWBdYH3ZzC0iZHv0eNdBoeXZ2NmFkiXFjtUWugBQYT8grgy35z7ZZ uR4XaR7pdICB4IRgiyKh4KzguQhnZbtdgj3poBx4iooUHMKAKa7nus67okVBbCqZ ZamJAnD0jBBVY09Gxh/yNGpU56hGYd95mk5pE41TID3D5qZnXuCFulhC4P5CE/l1 FCWuqwwOoGR+83ThQc1UGSKEat/K5dWYq1rN4J3iYho7B/ppruubCpdn4UQvsMaD SQnRPghp3MHKU3768liH1bdijku0kAjmCCHqngXMMh9Zq1XFaU74UN5Si3zdcrBI Lh963rPoqs3zqGHZcMzLOhilXt9Px5JFbjnGZIzWbNSdDz2tdaRp/DIbjAwVpcT/ 6FRmnDN0aATwWcoFc1P8itidQQOL4T7fxttt7JtJZPA0kXxtynqNs2G3uvpzeGr0 7pDMzCXVcK0B5dUnJWZFU/i39ixDIfxJdD1UzaYpVUSUVdv0vNaqQoXC2YnENlqQ 94AoLWr04f/I0oh4x/ooWSd6ONrMflgpWNV7Tr9DyZKgQdZ7A7qWShby0BhUDbDf 85x+B9QnWcW6ueRfdgwRVePIpZAo9cwUeYD77uBK1+wH2UhnPhK1Qkhb7y/9d/4m +xFYrfNoy5drzLCYZi0xdXo026D/dFJ87x/KA9C6bwnCQd6K+whdMH6/tmAyrCU7 rAfcnBaZOs/glYK/ak1y9uQd+M+16XNs7ev2i9JH8gqTm30zVE4nmHXU8AWPXf6m dXyg8I7htnr3Hpvk7l7yQpur+FKEEuthzq9OLFTuCEJ0UNyL6+jwnnMpxLlPfXud glNnoYom+0k4okcWa7m33VHXzYuSxaIzrqLSDQHOdWo6W7pn+gMVwUCGmxNfuK5j 5wv5LWM1+GaaGPN2WNuo3kRpsxBY98wBjaAAZLklR/9waAziCMSbR98BBQzCK1sV US0pSIP5+9ALL/V4xFAP+7Dz1Xmp9fAEzgXnt1bL3P39FkdpO2TQCUH+qQogkCsC UVVrmVYCm/8fMJbNamFW3s0OHmNe2C9DpZHIvjfwg1r0gmHgZ/j5/MuYo4kFFqzK line_profiler-4.1.2/dev/ci_secret_gpg_subkeys.pgp.enc000066400000000000000000000027601452103132100227400ustar00rootroot00000000000000U2FsdGVkX1/pRjQeonOw8CdJoiHMQzb6Cv4CEJKTf1q5JaEcwQgfz+2Z7bNKr7yQ YvW4ydsaRVEuJ09NWqhdg+C46yJvJrQ9CAd7Flp4TLVxtczdY+YVoe2zejPAJbDR Rm/B/1egxwmFoKbiFy1DH6cZiZlpqwlX9nnY257Vquyu/4xv/w3ziOF3A0knQFwF 3RjEMbnZXd1/zh5JdF9W3oAdn0dSfjDDf/jJZVyHb7riPnTyDN+NEgKRnqgLSm2x EUcJufhu2AiQQLhIZ2xu9ckHh0XXYrtsbvaEJgYbyvcyY4xDTIsjzH7MBs17Zjl8 I6GyGQtYwLc4zEeC4Icna2WyyxNhP+D3G1hhFotXFzzWrDHOdCgeWmkKtE4xrpI5 qa0FPJ6xIxoqZ5WOQMowRtERjB3dzCgRrp5wLPEBGkco/AKFd24ZT7qqv6Q438rA Q/Mza4i0JfCkUFn8weaAq6sAyDZ/ji9qJU3iNGc/lxQHbhRg1nuJOUNzy0NT/u0Q B5Ai0cj4xR1L3RWD5l6mHZAuXjSSHJyRpV9eSxpl1H+croqjujoCvtpDkXFBKJir /myY5DKW7eXcX9RWstv466SBKYJe25Ebpah60u0VnbBrnklQK0n6xVIrqzDQ19Oh lT7doHpPnKWfpbwkCrg+8RFpE38Us0Q6P4devpTo1oCR7wJt+r0uaefTI69eEnQR pSoW4f/0of1ITQOSx8s5Hna+gZNWUzD9rfMB3q3t0wC8JY5erj1FfjHVDv1UwxpD ATRqTYtQFutZPGs63TD0nXCkRf6SxGOQYkswo+e42WYqx+NBJcyGAi8Bu3hXZfBX 5dDvfWTATinNs89K7TV2lb5VpGXN+dFNxx8GYKjOj/p40PB71/6+RCjn+JLEXoom V3xeJf5uxaX+F9bEN4YLiKND3b9zd08XuSsr6Ye3qZpkdl75x21jJfHS1A1wp1Nu yjsAFHphc489LkMG1QfmCVqgQ/gEzLrtfNNazgpotxiM5RHxtsLsep4V1yVn8kwG lBLxgIgsjiCI5SPGkI2NyimYASaewpvt8MO4G+GVydS+639x+1Y1oDqpbVASYr1J cqAShtKjdo6F4AsVLJXww89Mueq6dkIh5RzZRkdXFFhElXjAxaU6iq9KnZ26f2AF z6weArFd0isU/2fHm6Ll+tpZWI7FqsZkmAY73n6e8wFJA8wJHXufZH+t3YykgB9+ NJtkxYGnfea33vJ7vzcS9GtTfFeaOv15X9tA7N4NHVupWXLE8Uy5YjuBek+Tyt8D K3Bs2n9naSowPp2Uhu4NXtpjFB1MrkarcwZw95y49mk/489FuBwDtPNWXpQ9iyAp +OLr9IJCWU6XVIzZptx7WmrjBdOzFFXhMniNtj1hdyQ4cdrFtgvQqrBjitp9MpWk gj+8L/ZoqzPxXtUyh9utdKwgAsfSb0fa8mV9h8FFxIjfuerqjYyof9Tw33w5kgel pw0jmKBIZJ1WWHqLjHDo2Q== line_profiler-4.1.2/dev/gpg_owner_trust.enc000066400000000000000000000012431452103132100210340ustar00rootroot00000000000000U2FsdGVkX1/VV/oZL+0zJuZbiNgyUZSW8rfYJBPwP5miuereqn/I62Ya0Yd7QQAD Y+eb/PTMAHRTYrb6nGCig9WztCS5gdaYs2V7RwkGdfQOqeMOdm8tWOSRH21EVtc7 zyg2EqSTjLyJQReWjf2c1U8AOM1HAp6ZzCnoZkqXIXC2vUDPPipLlTICzlMzytKD xxpuyl99N7ZIrpHo28Hy8LV9HTidx+OqV6PmzhoSxjiVG23FtflfSrsWh5zxTW8f ZsDrNielzg63V+IPjzRTP+KLrIxAWvuymKm5QDY/IbSxTuQbI5g8Dny9uOw0BzEW fHmOZjhEwLECJAhOONUh9VxFlc9SqvfOBMlaLd/HjTtD8pTwD11Zhv5YYdvbhIjw fgYLF/y6LExpS1B4qanYLvSx35h6c3COeB6AKjVPPBLNYBHBuGKifF1nWeYaSC8s SiaQ52I6rJdhHAzHR+5FyXlwALKPzGHIYL+chyYHt8g8mIZd131Wu/+6VQtkxb3r QcKZ8hYACJDTZBuqk11jWiOiFqRH0yOzyq1KUzVV72q4hmp+RLqFPOvTUB1iL7H9 PsIC2D5+R5Mc2D1dCCbsFSRGYMRsaiORROJQEeZK+WpK0sN8zQTiP3/XomhWGUDF eWCqjUSYcxv3gVVzksJkbQ== line_profiler-4.1.2/dev/maintain/000077500000000000000000000000001452103132100167155ustar00rootroot00000000000000line_profiler-4.1.2/dev/maintain/port_utilities.py000066400000000000000000000045501452103132100223520ustar00rootroot00000000000000""" Statically port utilities from ubelt and xdocest need for the autoprofile features. """ import ubelt as ub import liberator import re def generate_util_static(): lib = liberator.Liberator(verbose=0) import ubelt import xdoctest if 1: lib.add_dynamic(ubelt.util_import.modpath_to_modname) lib.add_dynamic(ubelt.util_import.modname_to_modpath) lib.expand(['ubelt']) if 1: lib.add_dynamic(xdoctest.static_analysis.package_modpaths) lib.expand(['xdoctest']) # # Hack because ubelt and xdoctest define this del lib.body_defs['xdoctest.utils.util_import._platform_pylib_exts'] # lib.expand(['ubelt', 'xdoctest']) text = lib.current_sourcecode() """ pip install rope pip install parso """ prefix = ub.codeblock( ''' """ This file was autogenerated based on code in :py:mod:`ubelt` and :py:mod:`xdoctest` via dev/maintain/port_utilities.py in the line_profiler repo. """ ''') # Remove doctest references to ubelt new_lines = [] for line in text.split('\n'): if line.strip().startswith('>>> from ubelt'): continue if line.strip().startswith('>>> import ubelt as ub'): line = re.sub('>>> .*', '>>> # xdoctest: +SKIP("ubelt dependency")', line) new_lines.append(line) text = '\n'.join(new_lines) text = prefix + '\n' + text + '\n' return text def main(): text = generate_util_static() print(ub.highlight_code(text, backend='rich')) import parso import line_profiler target_fpath = ub.Path(line_profiler.__file__).parent / 'autoprofile' / 'util_static.py' new_module = parso.parse(text) if target_fpath.exists(): old_module = parso.parse(target_fpath.read_text()) new_names = [child.name.value for child in new_module.children if child.type in {'funcdef', 'classdef'}] old_names = [child.name.value for child in old_module.children if child.type in {'funcdef', 'classdef'}] print(set(old_names) - set(new_names)) print(set(new_names) - set(old_names)) target_fpath.write_text(text) # Fixup formatting if 1: ub.cmd(['black', target_fpath]) if __name__ == '__main__': """ CommandLine: python ~/code/line_profiler/dev/maintain/port_utilities.py """ main() line_profiler-4.1.2/dev/public_gpg_key000066400000000000000000000000511452103132100200170ustar00rootroot000000000000002A290272C174D28EA9CA48E9D7224DAF0347B114 line_profiler-4.1.2/dev/secrets_configuration.sh000066400000000000000000000005671452103132100220600ustar00rootroot00000000000000export VARNAME_CI_SECRET="PYUTILS_CI_SECRET" export VARNAME_TWINE_PASSWORD="PYUTILS_PYPI_MASTER_TOKEN" export VARNAME_TEST_TWINE_PASSWORD="PYUTILS_TEST_PYPI_MASTER_TOKEN" export VARNAME_TWINE_USERNAME="PYUTILS_PYPI_MASTER_TOKEN_USERNAME" export VARNAME_TEST_TWINE_USERNAME="PYUTILS_TEST_PYPI_MASTER_TOKEN_USERNAME" export GPG_IDENTIFIER="=PyUtils-CI " line_profiler-4.1.2/dev/setup_secrets.sh000066400000000000000000000471371452103132100203550ustar00rootroot00000000000000#!/bin/bash __doc__=' ============================ SETUP CI SECRET INSTRUCTIONS ============================ TODO: These instructions are currently pieced together from old disparate instances, and are not yet fully organized. The original template file should be: ~/code/xcookie/dev/setup_secrets.sh Development script for updating secrets when they rotate The intent of this script is to help setup secrets for whichever of the following CI platforms is used: ../.github/workflows/tests.yml ../.gitlab-ci.yml ../.circleci/config.yml ========================= GITHUB ACTION INSTRUCTIONS ========================= * `PERSONAL_GITHUB_PUSH_TOKEN` - This is only needed if you want to automatically git-tag release branches. To make a API token go to: https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token ========================= GITLAB ACTION INSTRUCTIONS ========================= ```bash cat .setup_secrets.sh | \ sed "s|utils||g" | \ sed "s|xcookie||g" | \ sed "s|travis-ci-Erotemic||g" | \ sed "s|CI_SECRET||g" | \ sed "s|GITLAB_ORG_PUSH_TOKEN||g" | \ sed "s|gitlab.org.com|gitlab.your-instance.com|g" | \ tee /tmp/repl && colordiff .setup_secrets.sh /tmp/repl ``` * Make sure you add Runners to your project https://gitlab.org.com/utils/xcookie/-/settings/ci_cd in Runners-> Shared Runners and Runners-> Available specific runners * Ensure that you are auto-cancel redundant pipelines. Navigate to https://gitlab.kitware.com/utils/xcookie/-/settings/ci_cd and ensure "Auto-cancel redundant pipelines" is checked. More details are here https://docs.gitlab.com/ee/ci/pipelines/settings.html#auto-cancel-redundant-pipelines * TWINE_USERNAME - this is your pypi username twine info is only needed if you want to automatically publish to pypi * TWINE_PASSWORD - this is your pypi password * CI_SECRET - We will use this as a secret key to encrypt/decrypt gpg secrets This is only needed if you want to automatically sign published wheels with a gpg key. * GITLAB_ORG_PUSH_TOKEN - This is only needed if you want to automatically git-tag release branches. Create a new personal access token in User->Settings->Tokens, You can name the token GITLAB_ORG_PUSH_TOKEN_VALUE Give it api and write repository permissions SeeAlso: https://gitlab.org.com/profile/personal_access_tokens Take this variable and record its value somewhere safe. I put it in my secrets file as such: export GITLAB_ORG_PUSH_TOKEN_VALUE= I also create another variable with the prefix "git-push-token", which is necessary export GITLAB_ORG_PUSH_TOKEN=git-push-token:$GITLAB_ORG_PUSH_TOKEN_VALUE Then add this as a secret variable here: https://gitlab.org.com/groups/utils/-/settings/ci_cd Note the value of GITLAB_ORG_PUSH_TOKEN will look something like: "{token-name}:{token-password}" For instance it may look like this: "git-push-token:62zutpzqga6tvrhklkdjqm" References: https://stackoverflow.com/questions/51465858/how-do-you-push-to-a-gitlab-repo-using-a-gitlab-ci-job # ADD RELEVANT VARIABLES TO GITLAB SECRET VARIABLES # https://gitlab.kitware.com/computer-vision/kwcoco/-/settings/ci_cd # Note that it is important to make sure that these variables are # only decrpyted on protected branches by selecting the protected # and masked option. Also make sure you have master and release # branches protected. # https://gitlab.kitware.com/computer-vision/kwcoco/-/settings/repository#js-protected-branches-settings ============================ Relevant CI Secret Locations ============================ https://github.com/pyutils/line_profiler/settings/secrets/actions https://app.circleci.com/settings/project/github/pyutils/line_profiler/environment-variables?return-to=https%3A%2F%2Fapp.circleci.com%2Fpipelines%2Fgithub%2Fpyutils%2Fline_profiler ' setup_package_environs(){ __doc__=" Setup environment variables specific for this project. The remainder of this script should ideally be general to any repo. These non-secret variables are written to disk and loaded by the script, such that the specific repo only needs to modify that configuration file. " echo "Choose an organization specific setting or make your own. This needs to be generalized more" } ### FIXME: Should be configurable for general use setup_package_environs_gitlab_kitware(){ echo ' export VARNAME_CI_SECRET="CI_KITWARE_SECRET" export VARNAME_TWINE_PASSWORD="EROTEMIC_PYPI_MASTER_TOKEN" export VARNAME_TEST_TWINE_PASSWORD="EROTEMIC_TEST_PYPI_MASTER_TOKEN" export VARNAME_PUSH_TOKEN="GITLAB_KITWARE_TOKEN" export VARNAME_TWINE_USERNAME="EROTEMIC_PYPI_MASTER_TOKEN_USERNAME" export VARNAME_TEST_TWINE_USERNAME="EROTEMIC_TEST_PYPI_MASTER_TOKEN_USERNAME" export GPG_IDENTIFIER="=Erotemic-CI " ' | python -c "import sys; from textwrap import dedent; print(dedent(sys.stdin.read()).strip(chr(10)))" > dev/secrets_configuration.sh git add dev/secrets_configuration.sh } setup_package_environs_github_erotemic(){ echo ' export VARNAME_CI_SECRET="EROTEMIC_CI_SECRET" export VARNAME_TWINE_PASSWORD="EROTEMIC_PYPI_MASTER_TOKEN" export VARNAME_TEST_TWINE_PASSWORD="EROTEMIC_TEST_PYPI_MASTER_TOKEN" export VARNAME_TWINE_USERNAME="EROTEMIC_PYPI_MASTER_TOKEN_USERNAME" export VARNAME_TEST_TWINE_USERNAME="EROTEMIC_TEST_PYPI_MASTER_TOKEN_USERNAME" export GPG_IDENTIFIER="=Erotemic-CI " ' | python -c "import sys; from textwrap import dedent; print(dedent(sys.stdin.read()).strip(chr(10)))" > dev/secrets_configuration.sh git add dev/secrets_configuration.sh } setup_package_environs_github_pyutils(){ echo ' export VARNAME_CI_SECRET="PYUTILS_CI_SECRET" export VARNAME_TWINE_PASSWORD="PYUTILS_PYPI_MASTER_TOKEN" export VARNAME_TEST_TWINE_PASSWORD="PYUTILS_TEST_PYPI_MASTER_TOKEN" export VARNAME_TWINE_USERNAME="PYUTILS_PYPI_MASTER_TOKEN_USERNAME" export VARNAME_TEST_TWINE_USERNAME="PYUTILS_TEST_PYPI_MASTER_TOKEN_USERNAME" export GPG_IDENTIFIER="=PyUtils-CI " ' | python -c "import sys; from textwrap import dedent; print(dedent(sys.stdin.read()).strip(chr(10)))" > dev/secrets_configuration.sh git add dev/secrets_configuration.sh #echo ' #export VARNAME_CI_SECRET="PYUTILS_CI_SECRET" #export GPG_IDENTIFIER="=PyUtils-CI " #' | python -c "import sys; from textwrap import dedent; print(dedent(sys.stdin.read()).strip(chr(10)))" > dev/secrets_configuration.sh } upload_github_secrets(){ load_secrets unset GITHUB_TOKEN #printf "%s" "$GITHUB_TOKEN" | gh auth login --hostname Github.com --with-token if ! gh auth status ; then gh auth login fi source dev/secrets_configuration.sh gh secret set "TWINE_USERNAME" -b"${!VARNAME_TWINE_USERNAME}" gh secret set "TEST_TWINE_USERNAME" -b"${!VARNAME_TEST_TWINE_USERNAME}" toggle_setx_enter gh secret set "CI_SECRET" -b"${!VARNAME_CI_SECRET}" gh secret set "TWINE_PASSWORD" -b"${!VARNAME_TWINE_PASSWORD}" gh secret set "TEST_TWINE_PASSWORD" -b"${!VARNAME_TEST_TWINE_PASSWORD}" toggle_setx_exit } toggle_setx_enter(){ # Can we do something like a try/finally? # https://stackoverflow.com/questions/15656492/writing-try-catch-finally-in-shell echo "Enter sensitive area" if [[ -n "${-//[^x]/}" ]]; then __context_1_toggle_setx=1 else __context_1_toggle_setx=0 fi if [[ "$__context_1_toggle_setx" == "1" ]]; then echo "Setx was on, disable temporarilly" set +x fi } toggle_setx_exit(){ echo "Exit sensitive area" # Can we guarentee this will happen? if [[ "$__context_1_toggle_setx" == "1" ]]; then set -x fi } upload_gitlab_group_secrets(){ __doc__=" Use the gitlab API to modify group-level secrets " # In Repo Directory load_secrets REMOTE=origin GROUP_NAME=$(git remote get-url $REMOTE | cut -d ":" -f 2 | cut -d "/" -f 1) HOST=https://$(git remote get-url $REMOTE | cut -d "/" -f 1 | cut -d "@" -f 2 | cut -d ":" -f 1) echo " * GROUP_NAME = $GROUP_NAME * HOST = $HOST " PRIVATE_GITLAB_TOKEN=$(git_token_for "$HOST") if [[ "$PRIVATE_GITLAB_TOKEN" == "ERROR" ]]; then echo "Failed to load authentication key" return 1 fi TMP_DIR=$(mktemp -d -t ci-XXXXXXXXXX) curl --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups" > "$TMP_DIR/all_group_info" GROUP_ID=$(< "$TMP_DIR/all_group_info" jq ". | map(select(.path==\"$GROUP_NAME\")) | .[0].id") echo "GROUP_ID = $GROUP_ID" curl --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID" > "$TMP_DIR/group_info" < "$TMP_DIR/group_info" jq # Get group-level secret variables curl --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID/variables" > "$TMP_DIR/group_vars" < "$TMP_DIR/group_vars" jq '.[] | .key' if [[ "$?" != "0" ]]; then echo "Failed to access group level variables. Probably a permission issue" fi source dev/secrets_configuration.sh SECRET_VARNAME_ARR=(VARNAME_CI_SECRET VARNAME_TWINE_PASSWORD VARNAME_TEST_TWINE_PASSWORD VARNAME_TWINE_USERNAME VARNAME_TEST_TWINE_USERNAME VARNAME_PUSH_TOKEN) for SECRET_VARNAME_PTR in "${SECRET_VARNAME_ARR[@]}"; do SECRET_VARNAME=${!SECRET_VARNAME_PTR} echo "" echo " ---- " LOCAL_VALUE=${!SECRET_VARNAME} REMOTE_VALUE=$(< "$TMP_DIR/group_vars" jq -r ".[] | select(.key==\"$SECRET_VARNAME\") | .value") # Print current local and remote value of a variable echo "SECRET_VARNAME_PTR = $SECRET_VARNAME_PTR" echo "SECRET_VARNAME = $SECRET_VARNAME" echo "(local) $SECRET_VARNAME = $LOCAL_VALUE" echo "(remote) $SECRET_VARNAME = $REMOTE_VALUE" #curl --request GET --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID/variables/SECRET_VARNAME" | jq -r .message if [[ "$REMOTE_VALUE" == "" ]]; then # New variable echo "Remove variable does not exist, posting" toggle_setx_enter curl --request POST --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID/variables" \ --form "key=${SECRET_VARNAME}" \ --form "value=${LOCAL_VALUE}" \ --form "protected=true" \ --form "masked=true" \ --form "environment_scope=*" \ --form "variable_type=env_var" toggle_setx_exit elif [[ "$REMOTE_VALUE" != "$LOCAL_VALUE" ]]; then echo "Remove variable does not agree, putting" # Update variable value toggle_setx_enter curl --request PUT --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID/variables/$SECRET_VARNAME" \ --form "value=${LOCAL_VALUE}" toggle_setx_exit else echo "Remote value agrees with local" fi done rm "$TMP_DIR/group_vars" } upload_gitlab_repo_secrets(){ __doc__=" Use the gitlab API to modify group-level secrets " # In Repo Directory load_secrets REMOTE=origin GROUP_NAME=$(git remote get-url $REMOTE | cut -d ":" -f 2 | cut -d "/" -f 1) PROJECT_NAME=$(git remote get-url $REMOTE | cut -d ":" -f 2 | cut -d "/" -f 2 | cut -d "." -f 1) HOST=https://$(git remote get-url $REMOTE | cut -d "/" -f 1 | cut -d "@" -f 2 | cut -d ":" -f 1) echo " * GROUP_NAME = $GROUP_NAME * PROJECT_NAME = $PROJECT_NAME * HOST = $HOST " PRIVATE_GITLAB_TOKEN=$(git_token_for "$HOST") if [[ "$PRIVATE_GITLAB_TOKEN" == "ERROR" ]]; then echo "Failed to load authentication key" return 1 fi TMP_DIR=$(mktemp -d -t ci-XXXXXXXXXX) toggle_setx_enter curl --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups" > "$TMP_DIR/all_group_info" toggle_setx_exit GROUP_ID=$(< "$TMP_DIR/all_group_info" jq ". | map(select(.path==\"$GROUP_NAME\")) | .[0].id") echo "GROUP_ID = $GROUP_ID" toggle_setx_enter curl --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/groups/$GROUP_ID" > "$TMP_DIR/group_info" toggle_setx_exit GROUP_ID=$(< "$TMP_DIR/all_group_info" jq ". | map(select(.path==\"$GROUP_NAME\")) | .[0].id") < "$TMP_DIR/group_info" jq PROJECT_ID=$(< "$TMP_DIR/group_info" jq ".projects | map(select(.path==\"$PROJECT_NAME\")) | .[0].id") echo "PROJECT_ID = $PROJECT_ID" # Get group-level secret variables toggle_setx_enter curl --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/projects/$PROJECT_ID/variables" > "$TMP_DIR/project_vars" toggle_setx_exit < "$TMP_DIR/project_vars" jq '.[] | .key' if [[ "$?" != "0" ]]; then echo "Failed to access project level variables. Probably a permission issue" fi LIVE_MODE=1 source dev/secrets_configuration.sh SECRET_VARNAME_ARR=(VARNAME_CI_SECRET VARNAME_TWINE_PASSWORD VARNAME_TEST_TWINE_PASSWORD VARNAME_TWINE_USERNAME VARNAME_TEST_TWINE_USERNAME VARNAME_PUSH_TOKEN) for SECRET_VARNAME_PTR in "${SECRET_VARNAME_ARR[@]}"; do SECRET_VARNAME=${!SECRET_VARNAME_PTR} echo "" echo " ---- " LOCAL_VALUE=${!SECRET_VARNAME} REMOTE_VALUE=$(< "$TMP_DIR/project_vars" jq -r ".[] | select(.key==\"$SECRET_VARNAME\") | .value") # Print current local and remote value of a variable echo "SECRET_VARNAME_PTR = $SECRET_VARNAME_PTR" echo "SECRET_VARNAME = $SECRET_VARNAME" echo "(local) $SECRET_VARNAME = $LOCAL_VALUE" echo "(remote) $SECRET_VARNAME = $REMOTE_VALUE" #curl --request GET --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/projects/$PROJECT_ID/variables/SECRET_VARNAME" | jq -r .message if [[ "$REMOTE_VALUE" == "" ]]; then # New variable echo "Remove variable does not exist, posting" if [[ "$LIVE_MODE" == "1" ]]; then curl --request POST --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/projects/$PROJECT_ID/variables" \ --form "key=${SECRET_VARNAME}" \ --form "value=${LOCAL_VALUE}" \ --form "protected=true" \ --form "masked=true" \ --form "environment_scope=*" \ --form "variable_type=env_var" else echo "dry run, not posting" fi elif [[ "$REMOTE_VALUE" != "$LOCAL_VALUE" ]]; then echo "Remove variable does not agree, putting" # Update variable value if [[ "$LIVE_MODE" == "1" ]]; then curl --request PUT --header "PRIVATE-TOKEN: $PRIVATE_GITLAB_TOKEN" "$HOST/api/v4/projects/$PROJECT_ID/variables/$SECRET_VARNAME" \ --form "value=${LOCAL_VALUE}" else echo "dry run, not putting" fi else echo "Remote value agrees with local" fi done rm "$TMP_DIR/project_vars" } export_encrypted_code_signing_keys(){ # You will need to rerun this whenever the signkeys expire and are renewed # Load or generate secrets load_secrets source dev/secrets_configuration.sh CI_SECRET="${!VARNAME_CI_SECRET}" echo "VARNAME_CI_SECRET = $VARNAME_CI_SECRET" echo "CI_SECRET=$CI_SECRET" echo "GPG_IDENTIFIER=$GPG_IDENTIFIER" # ADD RELEVANT VARIABLES TO THE CI SECRET VARIABLES # HOW TO ENCRYPT YOUR SECRET GPG KEY # You need to have a known public gpg key for this to make any sense MAIN_GPG_KEYID=$(gpg --list-keys --keyid-format LONG "$GPG_IDENTIFIER" | head -n 2 | tail -n 1 | awk '{print $1}') GPG_SIGN_SUBKEY=$(gpg --list-keys --with-subkey-fingerprints "$GPG_IDENTIFIER" | grep "\[S\]" -A 1 | tail -n 1 | awk '{print $1}') # Careful, if you don't have a subkey, requesting it will export more than you want. # Export the main key instead (its better to have subkeys, but this is a lesser evil) if [[ "$GPG_SIGN_SUBKEY" == "" ]]; then # NOTE: if you get here this probably means your subkeys expired (and # wont even be visible), so we probably should check for that here and # thrown an error instead of using this hack, which likely wont work # anyway. GPG_SIGN_SUBKEY=$(gpg --list-keys --with-subkey-fingerprints "$GPG_IDENTIFIER" | grep "\[C\]" -A 1 | tail -n 1 | awk '{print $1}') fi echo "MAIN_GPG_KEYID = $MAIN_GPG_KEYID" echo "GPG_SIGN_SUBKEY = $GPG_SIGN_SUBKEY" # Only export the signing secret subkey # Export plaintext gpg public keys, private sign key, and trust info mkdir -p dev gpg --armor --export-options export-backup --export-secret-subkeys "${GPG_SIGN_SUBKEY}!" > dev/ci_secret_gpg_subkeys.pgp gpg --armor --export "${GPG_SIGN_SUBKEY}" > dev/ci_public_gpg_key.pgp gpg --export-ownertrust > dev/gpg_owner_trust # Encrypt gpg keys and trust with CI secret GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -e -a -in dev/ci_public_gpg_key.pgp > dev/ci_public_gpg_key.pgp.enc GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -e -a -in dev/ci_secret_gpg_subkeys.pgp > dev/ci_secret_gpg_subkeys.pgp.enc GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -e -a -in dev/gpg_owner_trust > dev/gpg_owner_trust.enc echo "$MAIN_GPG_KEYID" > dev/public_gpg_key # Test decrpyt GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc | gpg --list-packets --verbose GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | gpg --list-packets --verbose GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc cat dev/public_gpg_key unload_secrets # Look at what we did, clean up, and add it to git ls dev/*.enc rm dev/*.pgp rm dev/gpg_owner_trust git status git add dev/*.enc git add dev/gpg_owner_trust git add dev/public_gpg_key } # See the xcookie module gitlab python API #gitlab_set_protected_branches(){ #} _test_gnu(){ # shellcheck disable=SC2155 export GNUPGHOME=$(mktemp -d -t) ls -al "$GNUPGHOME" chmod 700 -R "$GNUPGHOME" source dev/secrets_configuration.sh gpg -k load_secrets CI_SECRET="${!VARNAME_CI_SECRET}" echo "CI_SECRET = $CI_SECRET" cat dev/public_gpg_key GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc | gpg --import GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc | gpg --import-ownertrust GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | gpg --import gpg -k # | gpg --import # | gpg --list-packets --verbose } line_profiler-4.1.2/docs/000077500000000000000000000000001452103132100152675ustar00rootroot00000000000000line_profiler-4.1.2/docs/Makefile000066400000000000000000000011761452103132100167340ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) line_profiler-4.1.2/docs/for_developers/000077500000000000000000000000001452103132100203055ustar00rootroot00000000000000line_profiler-4.1.2/docs/for_developers/release_process.rst000066400000000000000000000025501452103132100242170ustar00rootroot00000000000000Releasing a New Version ======================= The github action to push to PYPI will trigger on the push of a new tag. By convention we tag each release as ``v{__version__}``, where ``{__version__}`` is the current ``__version__`` number in ``line_profiler/__init__.py``. The ``./publish.sh`` script handles the creation of the version tag and pushing it to github, at which point the github action will build wheels for all supported operating systems and machine architectures. The steps are as follows: 1. Run ``./publish.sh``. 2. When given the prompt, ``Do you want to git tag and push version='{}'?``, confirm the new version looks correct and respond with "yes". 3. When asked: ``do you need to build wheels?`` Respond "no". (The CI will take care of this). 4. When asked: ``Are you ready to directly publish version xxx?`` Answer no. Again, the CI will do this. 5. The script will summarize its actions. Double check them, then press enter to create and push the new release tag. These options can be programatically given for a non-interactive interface. See the ``publish.sh`` script for details (and make a PR that adds that information here). Notes on Signed Releases ======================== The "dev" folder contains encrypted GPG keys used to sign the wheels on the CI. The CI is given a secret variable which is the key to decrypt them. line_profiler-4.1.2/docs/make.bat000066400000000000000000000014441452103132100166770ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.https://www.sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd line_profiler-4.1.2/docs/source/000077500000000000000000000000001452103132100165675ustar00rootroot00000000000000line_profiler-4.1.2/docs/source/conf.py000066400000000000000000000733031452103132100200740ustar00rootroot00000000000000""" Notes: Based on template code in: ~/code/xcookie/xcookie/builders/docs_conf.py ~/code/xcookie/xcookie/rc/conf_ext.py http://docs.readthedocs.io/en/latest/getting_started.html pip install sphinx sphinx-autobuild sphinx_rtd_theme sphinxcontrib-napoleon cd ~/code/line_profiler mkdir -p docs cd docs sphinx-quickstart # need to edit the conf.py cd ~/code/line_profiler/docs sphinx-apidoc --private -f -o ~/code/line_profiler/docs/source ~/code/line_profiler/line_profiler --separate make html git add source/*.rst Also: To turn on PR checks https://docs.readthedocs.io/en/stable/guides/autobuild-docs-for-pull-requests.html https://readthedocs.org/dashboard/line-profiler/advanced/ ensure your github account is connected to readthedocs https://readthedocs.org/accounts/social/connections/ ### For gitlab The user will need to enable the repo on their readthedocs account: https://readthedocs.org/dashboard/import/manual/? To enable the read-the-docs go to https://readthedocs.org/dashboard/ and login Make sure you have a .readthedocs.yml file Click import project: (for github you can select, but gitlab you need to import manually) Set the Repository NAME: line_profiler Set the Repository URL: https://github.com/pyutils/line_profiler For gitlab you also need to setup an integrations. Navigate to: https://readthedocs.org/dashboard/line-profiler/integrations/create/ Then add gitlab incoming webhook and copy the URL (make sure you copy the real url and not the text so https is included). Then go to https://github.com/pyutils/line_profiler/hooks and add the URL select push, tag, and merge request See Docs for more details https://docs.readthedocs.io/en/stable/integrations.html Will also need to activate the main branch: https://readthedocs.org/projects/line-profiler/versions/ """ # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/stable/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- import sphinx_rtd_theme from os.path import exists from os.path import dirname from os.path import join def parse_version(fpath): """ Statically parse the version number from a python file """ import ast if not exists(fpath): raise ValueError('fpath={!r} does not exist'.format(fpath)) with open(fpath, 'r') as file_: sourcecode = file_.read() pt = ast.parse(sourcecode) class VersionVisitor(ast.NodeVisitor): def visit_Assign(self, node): for target in node.targets: if getattr(target, 'id', None) == '__version__': self.version = node.value.s visitor = VersionVisitor() visitor.visit(pt) return visitor.version project = 'line_profiler' copyright = '2023, Robert Kern' author = 'Robert Kern' modname = 'line_profiler' modpath = join(dirname(dirname(dirname(__file__))), 'line_profiler', '__init__.py') release = parse_version(modpath) version = '.'.join(release.split('.')[0:2]) # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ # 'autoapi.extension', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.viewcode', # 'myst_parser', # TODO 'sphinx.ext.githubpages', # 'sphinxcontrib.redirects', 'sphinx_reredirects', ] todo_include_todos = True napoleon_google_docstring = True napoleon_use_param = False napoleon_use_ivar = True autodoc_inherit_docstrings = False autodoc_member_order = 'bysource' autoclass_content = 'both' # autodoc_mock_imports = ['torch', 'torchvision', 'visdom'] # autoapi_modules = { # modname: { # 'override': False, # 'output': 'auto' # } # } # autoapi_dirs = [f'../../src/{modname}'] # autoapi_keep_files = True intersphinx_mapping = { # 'pytorch': ('http://pytorch.org/docs/master/', None), 'python': ('https://docs.python.org/3', None), 'click': ('https://click.palletsprojects.com/', None), # 'xxhash': ('https://pypi.org/project/xxhash/', None), # 'pygments': ('https://pygments.org/docs/', None), # 'tqdm': ('https://tqdm.github.io/', None), # Requries that the repo have objects.inv 'kwarray': ('https://kwarray.readthedocs.io/en/latest/', None), 'kwimage': ('https://kwimage.readthedocs.io/en/latest/', None), # 'kwplot': ('https://kwplot.readthedocs.io/en/latest/', None), 'ndsampler': ('https://ndsampler.readthedocs.io/en/latest/', None), 'ubelt': ('https://ubelt.readthedocs.io/en/latest/', None), 'xdoctest': ('https://xdoctest.readthedocs.io/en/latest/', None), 'networkx': ('https://networkx.org/documentation/stable/', None), 'scriptconfig': ('https://scriptconfig.readthedocs.io/en/latest/', None), 'rich': ('https://rich.readthedocs.io/en/latest/', None), 'pytest': ('https://docs.pytest.org/en/latest/', None), # 'pytest._pytest.doctest': ('https://docs.pytest.org/en/latest/_modules/_pytest/doctest.html', None), # 'colorama': ('https://pypi.org/project/colorama/', None), # 'numpy': ('http://docs.scipy.org/doc/numpy/', None), # 'cv2' : ('http://docs.opencv.org/2.4/', None), # 'h5py' : ('http://docs.h5py.org/en/latest/', None) } __dev_note__ = """ python -m sphinx.ext.intersphinx https://docs.python.org/3/objects.inv python -m sphinx.ext.intersphinx https://kwcoco.readthedocs.io/en/latest/objects.inv python -m sphinx.ext.intersphinx https://networkx.org/documentation/stable/objects.inv python -m sphinx.ext.intersphinx https://kwarray.readthedocs.io/en/latest/objects.inv python -m sphinx.ext.intersphinx https://kwimage.readthedocs.io/en/latest/objects.inv python -m sphinx.ext.intersphinx https://ubelt.readthedocs.io/en/latest/objects.inv python -m sphinx.ext.intersphinx https://networkx.org/documentation/stable/objects.inv sphobjinv suggest -t 90 -u https://readthedocs.org/projects/pytest/reference/objects.inv "signal.convolve2d" python -m sphinx.ext.intersphinx https://pygments-doc.readthedocs.io/en/latest/objects.inv """ # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { 'collapse_navigation': False, 'display_version': True, # 'logo_only': True, } # html_logo = '.static/line_profiler.svg' # html_favicon = '.static/line_profiler.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = project + 'doc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'line_profiler.tex', 'line_profiler Documentation', 'Robert Kern', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'line_profiler', 'line_profiler Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'line_profiler', 'line_profiler Documentation', author, 'line_profiler', 'One line description of project.', 'Miscellaneous'), ] # -- Extension configuration ------------------------------------------------- from sphinx.domains.python import PythonDomain # NOQA # from sphinx.application import Sphinx # NOQA from typing import Any, List # NOQA class PatchedPythonDomain(PythonDomain): """ References: https://github.com/sphinx-doc/sphinx/issues/3866 """ def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): # TODO: can use this to resolve references nicely # if target.startswith('ub.'): # target = 'ubelt.' + target[3] return_value = super(PatchedPythonDomain, self).resolve_xref( env, fromdocname, builder, typ, target, node, contnode) return return_value class GoogleStyleDocstringProcessor: """ A small extension that runs after napoleon and reformats erotemic-flavored google-style docstrings for sphinx. """ def __init__(self, autobuild=1): self.registry = {} if autobuild: self._register_builtins() def register_section(self, tag, alias=None): """ Decorator that adds a custom processing function for a non-standard google style tag. The decorated function should accept a list of docstring lines, where the first one will be the google-style tag that likely needs to be replaced, and then return the appropriate sphinx format (TODO what is the name? Is it just RST?). """ alias = [] if alias is None else alias alias = [alias] if not isinstance(alias, (list, tuple, set)) else alias alias.append(tag) alias = tuple(alias) # TODO: better tag patterns def _wrap(func): self.registry[tag] = { 'tag': tag, 'alias': alias, 'func': func, } return func return _wrap def _register_builtins(self): """ Adds definitions I like of CommandLine, TextArt, and Ignore """ @self.register_section(tag='CommandLine') def commandline(lines): new_lines = [] new_lines.append('.. rubric:: CommandLine') new_lines.append('') new_lines.append('.. code-block:: bash') new_lines.append('') new_lines.extend(lines[1:]) return new_lines @self.register_section(tag='SpecialExample', alias=['Benchmark', 'Sympy', 'Doctest']) def benchmark(lines): import textwrap new_lines = [] tag = lines[0].replace(':', '').strip() # new_lines.append(lines[0]) # TODO: it would be nice to change the tagline. # new_lines.append('') new_lines.append('.. rubric:: {}'.format(tag)) new_lines.append('') new_text = textwrap.dedent('\n'.join(lines[1:])) redone = new_text.split('\n') new_lines.extend(redone) # import ubelt as ub # print('new_lines = {}'.format(ub.repr2(new_lines, nl=1))) # new_lines.append('') return new_lines @self.register_section(tag='TextArt', alias=['Ascii']) def text_art(lines): new_lines = [] new_lines.append('.. rubric:: TextArt') new_lines.append('') new_lines.append('.. code-block:: bash') new_lines.append('') new_lines.extend(lines[1:]) return new_lines @self.register_section(tag='Ignore') def ignore(lines): return [] def process(self, lines): """ Example: >>> import ubelt as ub >>> self = GoogleStyleDocstringProcessor() >>> lines = ['Hello world', >>> '', >>> 'CommandLine:', >>> ' hi', >>> '', >>> 'CommandLine:', >>> '', >>> ' bye', >>> '', >>> 'TextArt:', >>> '', >>> ' 1', >>> ' 2', >>> '', >>> ' 345', >>> '', >>> 'Foobar:', >>> '', >>> 'TextArt:'] >>> new_lines = self.process(lines[:]) >>> print(chr(10).join(new_lines)) """ orig_lines = lines[:] new_lines = [] curr_mode = '__doc__' accum = [] def accept(): """ called when we finish reading a section """ if curr_mode == '__doc__': # Keep the lines as-is new_lines.extend(accum) else: # Process this section with the given function regitem = self.registry[curr_mode] func = regitem['func'] fixed = func(accum) new_lines.extend(fixed) # Reset the accumulator for the next section accum[:] = [] for line in orig_lines: found = None for regitem in self.registry.values(): if line.startswith(regitem['alias']): found = regitem['tag'] break if not found and line and not line.startswith(' '): # if the line startswith anything but a space, we are no longer # in the previous nested scope. NOTE: This assumption may not # be general, but it works for my code. found = '__doc__' if found: # New section is found, accept the previous one and start # accumulating the new one. accept() curr_mode = found accum.append(line) # Finialize the last section accept() lines[:] = new_lines # make sure there is a blank line at the end if lines and lines[-1]: lines.append('') return lines def process_docstring_callback(self, app, what_: str, name: str, obj: Any, options: Any, lines: List[str]) -> None: """ Callback to be registered to autodoc-process-docstring Custom process to transform docstring lines Remove "Ignore" blocks Args: app (sphinx.application.Sphinx): the Sphinx application object what (str): the type of the object which the docstring belongs to (one of "module", "class", "exception", "function", "method", "attribute") name (str): the fully qualified name of the object obj: the object itself options: the options given to the directive: an object with attributes inherited_members, undoc_members, show_inheritance and noindex that are true if the flag option of same name was given to the auto directive lines (List[str]): the lines of the docstring, see above References: https://www.sphinx-doc.org/en/1.5.1/_modules/sphinx/ext/autodoc.html https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html """ # print(f'name={name}') # print('BEFORE:') # import ubelt as ub # print('lines = {}'.format(ub.repr2(lines, nl=1))) self.process(lines) # docstr = '\n'.join(lines) # if 'Convert the Mask' in docstr: # import xdev # xdev.embed() # if 'keys in this dictionary ' in docstr: # import xdev # xdev.embed() RENDER_IMAGES = 1 if RENDER_IMAGES: # DEVELOPING if any('REQUIRES(--show)' in line for line in lines): # import xdev # xdev.embed() create_doctest_figure(app, obj, name, lines) FIX_EXAMPLE_FORMATTING = 1 if FIX_EXAMPLE_FORMATTING: for idx, line in enumerate(lines): if line == "Example:": lines[idx] = "**Example:**" lines.insert(idx + 1, "") REFORMAT_SECTIONS = 0 if REFORMAT_SECTIONS: REFORMAT_RETURNS = 0 REFORMAT_PARAMS = 0 docstr = SphinxDocstring(lines) if REFORMAT_PARAMS: for found in docstr.find_tagged_lines('Parameters'): print(found['text']) edit_slice = found['edit_slice'] # TODO: figure out how to do this. # # file = 'foo.rst' # import rstparse # rst = rstparse.Parser() # import io # rst.read(io.StringIO(found['text'])) # rst.parse() # for line in rst.lines: # print(line) # # found['text'] # import docutils # settings = docutils.frontend.OptionParser( # components=(docutils.parsers.rst.Parser,) # ).get_default_values() # document = docutils.utils.new_document('', settings) # from docutils.parsers import rst # rst.Parser().parse(found['text'], document) if REFORMAT_RETURNS: for found in docstr.find_tagged_lines('returns'): # FIXME: account for new slice with -2 offset edit_slice = found['edit_slice'] text = found['text'] new_lines = [] for para in text.split('\n\n'): indent = para[:len(para) - len(para.lstrip())] new_paragraph = indent + paragraph(para) new_lines.append(new_paragraph) new_lines.append('') new_lines = new_lines[:-1] lines[edit_slice] = new_lines # print('AFTER:') # print('lines = {}'.format(ub.repr2(lines, nl=1))) # if name == 'kwimage.Affine.translate': # import sys # sys.exit(1) class SphinxDocstring: """ Helper to parse and modify sphinx docstrings """ def __init__(docstr, lines): docstr.lines = lines # FORMAT THE RETURNS SECTION A BIT NICER import re tag_pat = re.compile(r'^:(\w*):') directive_pat = re.compile(r'^.. (\w*)::\s*(\w*)') # Split by sphinx types, mark the line offset where they start / stop sphinx_parts = [] for idx, line in enumerate(lines): tag_match = tag_pat.search(line) directive_match = directive_pat.search(line) if tag_match: tag = tag_match.groups()[0] sphinx_parts.append({ 'tag': tag, 'start_offset': idx, 'type': 'tag', }) elif directive_match: tag = directive_match.groups()[0] sphinx_parts.append({ 'tag': tag, 'start_offset': idx, 'type': 'directive', }) prev_offset = len(lines) for part in sphinx_parts[::-1]: part['end_offset'] = prev_offset prev_offset = part['start_offset'] docstr.sphinx_parts = sphinx_parts if 0: for line in lines: print(line) def find_tagged_lines(docstr, tag): for part in docstr.sphinx_parts[::-1]: if part['tag'] == tag: edit_slice = slice(part['start_offset'], part['end_offset']) return_section = docstr.lines[edit_slice] text = '\n'.join(return_section) found = { 'edit_slice': edit_slice, 'text': text, } yield found def paragraph(text): r""" Wraps multi-line strings and restructures the text to remove all newlines, heading, trailing, and double spaces. Useful for writing log messages Args: text (str): typically a multiline string Returns: str: the reduced text block """ import re out = re.sub(r'\s\s*', ' ', text).strip() return out def create_doctest_figure(app, obj, name, lines): """ The idea is that each doctest that produces a figure should generate that and then that figure should be part of the docs. """ import xdoctest import sys import types if isinstance(obj, types.ModuleType): module = obj else: module = sys.modules[obj.__module__] # TODO: read settings from pyproject.toml? if '--show' not in sys.argv: sys.argv.append('--show') if '--nointeract' not in sys.argv: sys.argv.append('--nointeract') modpath = module.__file__ # print(doctest.format_src()) import pathlib # HACK: write to the srcdir doc_outdir = pathlib.Path(app.outdir) doc_srcdir = pathlib.Path(app.srcdir) doc_static_outdir = doc_outdir / '_static' doc_static_srcdir = doc_srcdir / '_static' src_fig_dpath = (doc_static_srcdir / 'images') src_fig_dpath.mkdir(exist_ok=True, parents=True) out_fig_dpath = (doc_static_outdir / 'images') out_fig_dpath.mkdir(exist_ok=True, parents=True) # fig_dpath = (doc_outdir / 'autofigs' / name).mkdir(exist_ok=True) fig_num = 1 import kwplot kwplot.autompl(force='agg') plt = kwplot.autoplt() docstr = '\n'.join(lines) # TODO: The freeform parser does not work correctly here. # We need to parse out the sphinx (epdoc)? individual examples # so we can get different figures. But we can hack it for now. import re split_parts = re.split('({}\\s*\n)'.format(re.escape('.. rubric:: Example')), docstr) # split_parts = docstr.split('.. rubric:: Example') # import xdev # xdev.embed() def doctest_line_offsets(doctest): # Where the doctests starts and ends relative to the file start_line_offset = doctest.lineno - 1 last_part = doctest._parts[-1] last_line_offset = start_line_offset + last_part.line_offset + last_part.n_lines - 1 offsets = { 'start': start_line_offset, 'end': last_line_offset, 'stop': last_line_offset + 1, } return offsets # from xdoctest import utils # part_lines = utils.add_line_numbers(docstr.split('\n'), n_digits=3, start=0) # print('\n'.join(part_lines)) to_insert_fpaths = [] curr_line_offset = 0 for part in split_parts: num_lines = part.count('\n') doctests = list(xdoctest.core.parse_docstr_examples( part, modpath=modpath, callname=name, # style='google' )) # print(doctests) # doctests = list(xdoctest.core.parse_docstr_examples( # docstr, modpath=modpath, callname=name)) for doctest in doctests: if '--show' in part: ... # print('-- SHOW TEST---')/) # kwplot.close_figures() try: import pytest # NOQA except ImportError: pass try: from xdoctest.exceptions import Skipped except ImportError: # nocover # Define dummy skipped exception if pytest is not available class Skipped(Exception): pass try: doctest.mode = 'native' doctest.run(verbose=0, on_error='raise') ... except Skipped: print(f'Skip doctest={doctest}') except Exception as ex: print(f'ex={ex}') print(f'Error in doctest={doctest}') offsets = doctest_line_offsets(doctest) doctest_line_end = curr_line_offset + offsets['stop'] insert_line_index = doctest_line_end figures = kwplot.all_figures() for fig in figures: fig_num += 1 # path_name = path_sanatize(name) path_name = (name).replace('.', '_') fig_fpath = src_fig_dpath / f'fig_{path_name}_{fig_num:03d}.jpeg' fig.savefig(fig_fpath) print(f'Wrote figure: {fig_fpath}') to_insert_fpaths.append({ 'insert_line_index': insert_line_index, 'fpath': fig_fpath, }) for fig in figures: plt.close(fig) # kwplot.close_figures(figures) curr_line_offset += (num_lines) # if len(doctests) > 1: # doctests # import xdev # xdev.embed() INSERT_AT = 'end' INSERT_AT = 'inline' end_index = len(lines) # Reverse order for inserts import shutil for info in to_insert_fpaths[::-1]: src_abs_fpath = info['fpath'] rel_to_static_fpath = src_abs_fpath.relative_to(doc_static_srcdir) # dst_abs_fpath = doc_static_outdir / rel_to_static_fpath # dst_abs_fpath.parent.mkdir(parents=True, exist_ok=True) rel_to_root_fpath = src_abs_fpath.relative_to(doc_srcdir) dst_abs_fpath1 = doc_outdir / rel_to_root_fpath dst_abs_fpath1.parent.mkdir(parents=True, exist_ok=True) shutil.copy(src_abs_fpath, dst_abs_fpath1) dst_abs_fpath2 = doc_outdir / rel_to_static_fpath dst_abs_fpath2.parent.mkdir(parents=True, exist_ok=True) shutil.copy(src_abs_fpath, dst_abs_fpath2) dst_abs_fpath3 = doc_srcdir / rel_to_static_fpath dst_abs_fpath3.parent.mkdir(parents=True, exist_ok=True) shutil.copy(src_abs_fpath, dst_abs_fpath3) if INSERT_AT == 'inline': # Try to insert after test insert_index = info['insert_line_index'] elif INSERT_AT == 'end': insert_index = end_index else: raise KeyError(INSERT_AT) lines.insert(insert_index, '.. image:: {}'.format(rel_to_root_fpath)) # lines.insert(insert_index, '.. image:: {}'.format(rel_to_static_fpath)) lines.insert(insert_index, '') def setup(app): import sphinx app : sphinx.application.Sphinx = app app.add_domain(PatchedPythonDomain, override=True) docstring_processor = GoogleStyleDocstringProcessor() # https://stackoverflow.com/questions/26534184/can-sphinx-ignore-certain-tags-in-python-docstrings app.connect('autodoc-process-docstring', docstring_processor.process_docstring_callback) ### Hack for kwcoco: TODO: figure out a way for the user to configure this. HACK_FOR_KWCOCO = 0 if HACK_FOR_KWCOCO: import pathlib import shutil doc_outdir = pathlib.Path(app.outdir) doc_srcdir = pathlib.Path(app.srcdir) schema_src = (doc_srcdir / '../../kwcoco/coco_schema.json') shutil.copy(schema_src, doc_outdir / 'coco_schema.json') shutil.copy(schema_src, doc_srcdir / 'coco_schema.json') return app line_profiler-4.1.2/docs/source/index.rst000066400000000000000000000004461452103132100204340ustar00rootroot00000000000000.. The __init__ files contains the top-level documentation overview .. automodule:: line_profiler.__init__ :show-inheritance: .. toctree:: :maxdepth: 8 :caption: Package Layout line_profiler kernprof Indices and tables ================== * :ref:`genindex` * :ref:`modindex` line_profiler-4.1.2/docs/source/kernprof.rst000066400000000000000000000003071452103132100211470ustar00rootroot00000000000000.. .. manually created (not sure how to get automodule to do it) kernprof module =============== .. automodule:: kernprof :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.__main__.rst000066400000000000000000000002711452103132100236710ustar00rootroot00000000000000line\_profiler.\_\_main\_\_ module ================================== .. automodule:: line_profiler.__main__ :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler._line_profiler.rst000066400000000000000000000003071452103132100251410ustar00rootroot00000000000000line\_profiler.\_line\_profiler module ====================================== .. automodule:: line_profiler._line_profiler :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.autoprofile.ast_profle_transformer.rst000066400000000000000000000004031452103132100312560ustar00rootroot00000000000000line\_profiler.autoprofile.ast\_profle\_transformer module ========================================================== .. automodule:: line_profiler.autoprofile.ast_profle_transformer :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.autoprofile.ast_tree_profiler.rst000066400000000000000000000003641452103132100302140ustar00rootroot00000000000000line\_profiler.autoprofile.ast\_tree\_profiler module ===================================================== .. automodule:: line_profiler.autoprofile.ast_tree_profiler :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.autoprofile.autoprofile.rst000066400000000000000000000003361452103132100270340ustar00rootroot00000000000000line\_profiler.autoprofile.autoprofile module ============================================= .. automodule:: line_profiler.autoprofile.autoprofile :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.autoprofile.line_profiler_utils.rst000066400000000000000000000003721452103132100305540ustar00rootroot00000000000000line\_profiler.autoprofile.line\_profiler\_utils module ======================================================= .. automodule:: line_profiler.autoprofile.line_profiler_utils :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.autoprofile.profmod_extractor.rst000066400000000000000000000003621452103132100302430ustar00rootroot00000000000000line\_profiler.autoprofile.profmod\_extractor module ==================================================== .. automodule:: line_profiler.autoprofile.profmod_extractor :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.autoprofile.rst000066400000000000000000000010501452103132100244760ustar00rootroot00000000000000line\_profiler.autoprofile package ================================== Submodules ---------- .. toctree:: :maxdepth: 4 line_profiler.autoprofile.ast_profle_transformer line_profiler.autoprofile.ast_tree_profiler line_profiler.autoprofile.autoprofile line_profiler.autoprofile.line_profiler_utils line_profiler.autoprofile.profmod_extractor line_profiler.autoprofile.util_static Module contents --------------- .. automodule:: line_profiler.autoprofile :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.autoprofile.util_static.rst000066400000000000000000000003401452103132100270220ustar00rootroot00000000000000line\_profiler.autoprofile.util\_static module ============================================== .. automodule:: line_profiler.autoprofile.util_static :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.explicit_profiler.rst000066400000000000000000000003161452103132100256740ustar00rootroot00000000000000line\_profiler.explicit\_profiler module ======================================== .. automodule:: line_profiler.explicit_profiler :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.ipython_extension.rst000066400000000000000000000003161452103132100257370ustar00rootroot00000000000000line\_profiler.ipython\_extension module ======================================== .. automodule:: line_profiler.ipython_extension :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.line_profiler.rst000066400000000000000000000003021452103132100247750ustar00rootroot00000000000000line\_profiler.line\_profiler module ==================================== .. automodule:: line_profiler.line_profiler :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/line_profiler.rst000066400000000000000000000007431452103132100221560ustar00rootroot00000000000000line\_profiler package ====================== Subpackages ----------- .. toctree:: :maxdepth: 4 line_profiler.autoprofile Submodules ---------- .. toctree:: :maxdepth: 4 line_profiler.__main__ line_profiler._line_profiler line_profiler.explicit_profiler line_profiler.ipython_extension line_profiler.line_profiler Module contents --------------- .. automodule:: line_profiler :members: :undoc-members: :show-inheritance: :private-members: line_profiler-4.1.2/docs/source/modules.rst000066400000000000000000000001141452103132100207650ustar00rootroot00000000000000line_profiler ============= .. toctree:: :maxdepth: 4 line_profiler line_profiler-4.1.2/kernprof.py000077500000000000000000000353041452103132100165470ustar00rootroot00000000000000#!/usr/bin/env python """ Script to conveniently run profilers on code in a variety of circumstances. To profile a script, decorate the functions of interest with ``@profile`` .. code:: bash echo "if 1: @profile def main(): 1 + 1 main() " > script_to_profile.py NOTE: New in 4.1.0: Instead of relying on injecting ``profile`` into the builtins you can now ``import line_profiler`` and use ``line_profiler.profile`` to decorate your functions. This allows the script to remain functional even if it is not actively profiled. See :py:mod:`line_profiler` for details. Then run the script using kernprof: .. code:: bash kernprof -b script_to_profile.py By default this runs with the default :py:mod:`cProfile` profiler and does not require compiled modules. Instructions to view the results will be given in the output. Alternatively, adding ``-v`` to the command line will write results to stdout. To enable line-by-line profiling, then :py:mod:`line_profiler` must be available and compiled. Add the ``-l`` argument to the kernprof invocation. .. code:: bash kernprof -lb script_to_profile.py For more details and options, refer to the CLI help. To view kernprof help run: .. code:: bash kenprof --help which displays: .. code:: usage: kernprof [-h] [-V] [-l] [-b] [-o OUTFILE] [-s SETUP] [-v] [-r] [-u UNIT] [-z] [-i [OUTPUT_INTERVAL]] [-p PROF_MOD] [--prof-imports] script ... Run and profile a python script. positional arguments: script The python script file to run args Optional script arguments options: -h, --help show this help message and exit -V, --version show program's version number and exit -l, --line-by-line Use the line-by-line profiler instead of cProfile. Implies --builtin. -b, --builtin Put 'profile' in the builtins. Use 'profile.enable()'/'.disable()', '@profile' to decorate functions, or 'with profile:' to profile a section of code. -o OUTFILE, --outfile OUTFILE Save stats to (default: 'scriptname.lprof' with --line-by-line, 'scriptname.prof' without) -s SETUP, --setup SETUP Code to execute before the code to profile -v, --view View the results of the profile in addition to saving it -r, --rich Use rich formatting if viewing output -u UNIT, --unit UNIT Output unit (in seconds) in which the timing info is displayed (default: 1e-6) -z, --skip-zero Hide functions which have not been called -i [OUTPUT_INTERVAL], --output-interval [OUTPUT_INTERVAL] Enables outputting of cumulative profiling results to file every n seconds. Uses the threading module. Minimum value is 1 (second). Defaults to disabled. -p PROF_MOD, --prof-mod PROF_MOD List of modules, functions and/or classes to profile specified by their name or path. List is comma separated, adding the current script path profiles full script. Only works with line_profiler -l, --line-by-line --prof-imports If specified, modules specified to `--prof-mod` will also autoprofile modules that they import. Only works with line_profiler -l, --line-by-line """ import builtins import functools import os import sys import threading import asyncio # NOQA import concurrent.futures # NOQA import time from argparse import ArgumentError, ArgumentParser # NOTE: This version needs to be manually maintained in # line_profiler/line_profiler.py and line_profiler/__init__.py as well __version__ = '4.1.2' # Guard the import of cProfile such that 3.x people # without lsprof can still use this script. try: from cProfile import Profile except ImportError: try: from lsprof import Profile except ImportError: from profile import Profile def execfile(filename, globals=None, locals=None): """ Python 3.x doesn't have 'execfile' builtin """ with open(filename, 'rb') as f: exec(compile(f.read(), filename, 'exec'), globals, locals) # ===================================== CO_GENERATOR = 0x0020 def is_generator(f): """ Return True if a function is a generator. """ isgen = (f.__code__.co_flags & CO_GENERATOR) != 0 return isgen class ContextualProfile(Profile): """ A subclass of Profile that adds a context manager for Python 2.5 with: statements and a decorator. """ def __init__(self, *args, **kwds): super().__init__(*args, **kwds) self.enable_count = 0 def enable_by_count(self, subcalls=True, builtins=True): """ Enable the profiler if it hasn't been enabled before. """ if self.enable_count == 0: self.enable(subcalls=subcalls, builtins=builtins) self.enable_count += 1 def disable_by_count(self): """ Disable the profiler if the number of disable requests matches the number of enable requests. """ if self.enable_count > 0: self.enable_count -= 1 if self.enable_count == 0: self.disable() def __call__(self, func): """ Decorate a function to start the profiler on function entry and stop it on function exit. """ # FIXME: refactor this into a utility function so that both it and # line_profiler can use it. if is_generator(func): wrapper = self.wrap_generator(func) else: wrapper = self.wrap_function(func) return wrapper # FIXME: refactor this stuff so that both LineProfiler and # ContextualProfile can use the same implementation. def wrap_generator(self, func): """ Wrap a generator to profile it. """ @functools.wraps(func) def wrapper(*args, **kwds): g = func(*args, **kwds) # The first iterate will not be a .send() self.enable_by_count() try: item = next(g) except StopIteration: return finally: self.disable_by_count() input = (yield item) # But any following one might be. while True: self.enable_by_count() try: item = g.send(input) except StopIteration: return finally: self.disable_by_count() input = (yield item) return wrapper def wrap_function(self, func): """ Wrap a function to profile it. """ @functools.wraps(func) def wrapper(*args, **kwds): self.enable_by_count() try: result = func(*args, **kwds) finally: self.disable_by_count() return result return wrapper def __enter__(self): self.enable_by_count() def __exit__(self, exc_type, exc_val, exc_tb): self.disable_by_count() class RepeatedTimer(object): """ Background timer for outputting file every n seconds. Adapted from [SO474528]_. References: .. [SO474528] https://stackoverflow.com/questions/474528/execute-function-every-x-seconds/40965385#40965385 """ def __init__(self, interval, dump_func, outfile): self._timer = None self.interval = interval self.dump_func = dump_func self.outfile = outfile self.is_running = False self.next_call = time.time() self.start() def _run(self): self.is_running = False self.start() self.dump_func(self.outfile) def start(self): if not self.is_running: self.next_call += self.interval self._timer = threading.Timer(self.next_call - time.time(), self._run) self._timer.start() self.is_running = True def stop(self): self._timer.cancel() self.is_running = False def find_script(script_name): """ Find the script. If the input is not a file, then $PATH will be searched. """ if os.path.isfile(script_name): return script_name path = os.getenv('PATH', os.defpath).split(os.pathsep) for dir in path: if dir == '': continue fn = os.path.join(dir, script_name) if os.path.isfile(fn): return fn sys.stderr.write('Could not find script %s\n' % script_name) raise SystemExit(1) def _python_command(): """ Return a command that corresponds to :py:obj:`sys.executable`. """ import shutil if shutil.which('python') == sys.executable: return 'python' elif shutil.which('python3') == sys.executable: return 'python3' else: return sys.executable def main(args=None): """ Runs the command line interface """ def positive_float(value): val = float(value) if val <= 0: raise ArgumentError return val parser = ArgumentParser(description='Run and profile a python script.') parser.add_argument('-V', '--version', action='version', version=__version__) parser.add_argument('-l', '--line-by-line', action='store_true', help='Use the line-by-line profiler instead of cProfile. Implies --builtin.') parser.add_argument('-b', '--builtin', action='store_true', help="Put 'profile' in the builtins. Use 'profile.enable()'/'.disable()', " "'@profile' to decorate functions, or 'with profile:' to profile a " 'section of code.') parser.add_argument('-o', '--outfile', help="Save stats to (default: 'scriptname.lprof' with " "--line-by-line, 'scriptname.prof' without)") parser.add_argument('-s', '--setup', help='Code to execute before the code to profile') parser.add_argument('-v', '--view', action='store_true', help='View the results of the profile in addition to saving it') parser.add_argument('-r', '--rich', action='store_true', help='Use rich formatting if viewing output') parser.add_argument('-u', '--unit', default='1e-6', type=positive_float, help='Output unit (in seconds) in which the timing info is ' 'displayed (default: 1e-6)') parser.add_argument('-z', '--skip-zero', action='store_true', help="Hide functions which have not been called") parser.add_argument('-i', '--output-interval', type=int, default=0, const=0, nargs='?', help="Enables outputting of cumulative profiling results to file every n seconds. Uses the threading module. " "Minimum value is 1 (second). Defaults to disabled.") parser.add_argument('-p', '--prof-mod', type=str, default='', help="List of modules, functions and/or classes to profile specified by their name or path. " "List is comma separated, adding the current script path profiles full script. " "Only works with line_profiler -l, --line-by-line") parser.add_argument('--prof-imports', action='store_true', help="If specified, modules specified to `--prof-mod` will also autoprofile modules that they import. " "Only works with line_profiler -l, --line-by-line") parser.add_argument('script', help='The python script file to run') parser.add_argument('args', nargs='...', help='Optional script arguments') options = parser.parse_args(args) if not options.outfile: extension = 'lprof' if options.line_by_line else 'prof' options.outfile = '%s.%s' % (os.path.basename(options.script), extension) sys.argv = [options.script] + options.args if options.setup is not None: # Run some setup code outside of the profiler. This is good for large # imports. setup_file = find_script(options.setup) __file__ = setup_file __name__ = '__main__' # Make sure the script's directory is on sys.path instead of just # kernprof.py's. sys.path.insert(0, os.path.dirname(setup_file)) ns = locals() execfile(setup_file, ns, ns) if options.line_by_line: import line_profiler prof = line_profiler.LineProfiler() options.builtin = True else: prof = ContextualProfile() # If line_profiler is installed, then overwrite the explicit decorator try: import line_profiler except ImportError: ... else: line_profiler.profile._kernprof_overwrite(prof) if options.builtin: builtins.__dict__['profile'] = prof script_file = find_script(options.script) __file__ = script_file __name__ = '__main__' # Make sure the script's directory is on sys.path instead of just # kernprof.py's. sys.path.insert(0, os.path.dirname(script_file)) if options.output_interval: rt = RepeatedTimer(max(options.output_interval, 1), prof.dump_stats, options.outfile) original_stdout = sys.stdout if options.output_interval: rt = RepeatedTimer(max(options.output_interval, 1), prof.dump_stats, options.outfile) try: try: execfile_ = execfile ns = locals() if options.prof_mod and options.line_by_line: from line_profiler.autoprofile import autoprofile prof_mod = options.prof_mod.split(',') autoprofile.run(script_file, ns, prof_mod=prof_mod, profile_imports=options.prof_imports) elif options.builtin: execfile(script_file, ns, ns) else: prof.runctx('execfile_(%r, globals())' % (script_file,), ns, ns) except (KeyboardInterrupt, SystemExit): pass finally: if options.output_interval: rt.stop() prof.dump_stats(options.outfile) print('Wrote profile results to %s' % options.outfile) if options.view: if isinstance(prof, ContextualProfile): prof.print_stats() else: prof.print_stats(output_unit=options.unit, stripzeros=options.skip_zero, rich=options.rich, stream=original_stdout) else: print('Inspect results with:') py_exe = _python_command() if isinstance(prof, ContextualProfile): print(f'{py_exe} -m pstats "{options.outfile}"') else: print(f'{py_exe} -m line_profiler -rmt "{options.outfile}"') if __name__ == '__main__': main(sys.argv[1:]) line_profiler-4.1.2/line_profiler/000077500000000000000000000000001452103132100171705ustar00rootroot00000000000000line_profiler-4.1.2/line_profiler/CMakeLists.txt000066400000000000000000000025661452103132100217410ustar00rootroot00000000000000set(cython_source "${CMAKE_CURRENT_SOURCE_DIR}/_line_profiler.pyx" "${CMAKE_CURRENT_SOURCE_DIR}/python25.pxd" ) set(module_name "_line_profiler") # Translate Cython into C/C++ add_cython_target(${module_name} "${cython_source}" C OUTPUT_VAR sources) # Add any other non-cython dependencies to the sources list(APPEND sources "${CMAKE_CURRENT_SOURCE_DIR}/unset_trace.c" "${CMAKE_CURRENT_SOURCE_DIR}/timers.c" ) message(STATUS "[OURS] sources = ${sources}") # Create C++ library. Specify include dirs and link libs as normal add_library(${module_name} MODULE ${sources}) target_include_directories(${module_name} PUBLIC ${PYTHON_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR} # for the pure c files defined here ) # Transform the C++ library into an importable python module python_extension_module(${module_name}) # Install the C++ module to the correct relative location # (this will be an inplace build if you use `pip install -e`) #file(RELATIVE_PATH _install_dest "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") #set(_install_dest ".") #message(STATUS "_install_dest = ${_install_dest}") #install(TARGETS ${module_name} LIBRARY DESTINATION "${_install_dest}") file(RELATIVE_PATH _install_dest "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") message(STATUS "[OURS] _install_dest = ${_install_dest}") install(TARGETS ${module_name} LIBRARY DESTINATION "${_install_dest}/") line_profiler-4.1.2/line_profiler/__init__.py000066400000000000000000000102061452103132100213000ustar00rootroot00000000000000""" Line Profiler ============= The line_profiler module for doing line-by-line profiling of functions +---------------+--------------------------------------------+ | Github | https://github.com/pyutils/line_profiler | +---------------+--------------------------------------------+ | Pypi | https://pypi.org/project/line_profiler | +---------------+--------------------------------------------+ | ReadTheDocs | https://kernprof.readthedocs.io/en/latest/ | +---------------+--------------------------------------------+ Installation ============ Releases of :py:mod:`line_profiler` and :py:mod:`kernprof` can be installed using pip .. code:: bash pip install line_profiler The package also provides extras for optional dependencies, which can be installed via: .. code:: bash pip install line_profiler[all] Line Profiler Basic Usage ========================= To demonstrate line profiling, we first need to generate a Python script to profile. Write the following code to a file called ``demo_primes.py``. .. code:: python from line_profiler import profile @profile def is_prime(n): ''' Check if the number "n" is prime, with n > 1. Returns a boolean, True if n is prime. ''' max_val = n ** 0.5 stop = int(max_val + 1) for i in range(2, stop): if n % i == 0: return False return True @profile def find_primes(size): primes = [] for n in range(size): flag = is_prime(n) if flag: primes.append(n) return primes @profile def main(): print('start calculating') primes = find_primes(100000) print(f'done calculating. Found {len(primes)} primes.') if __name__ == '__main__': main() In this script we explicitly import the ``profile`` function from ``line_profiler``, and then we decorate function of interest with ``@profile``. By default nothing is profiled when running the script. .. code:: bash python demo_primes.py The output will be .. code:: start calculating done calculating. Found 9594 primes. The quickest way to enable profiling is to set the environment variable ``LINE_PROFILE=1`` and running your script as normal. .. code:: bash LINE_PROFILE=1 python demo_primes.py This will output 3 files: profile_output.txt, profile_output_.txt, and profile_output.lprof and stdout will look something like: .. code:: start calculating done calculating. Found 9594 primes. Timer unit: 1e-09 s 0.65 seconds - demo_primes.py:4 - is_prime 1.47 seconds - demo_primes.py:19 - find_primes 1.51 seconds - demo_primes.py:29 - main Wrote profile results to profile_output.txt Wrote profile results to profile_output_2023-08-12T193302.txt Wrote profile results to profile_output.lprof To view details run: python -m line_profiler -rtmz profile_output.lprof For more control over the outputs, run your script using :py:mod:`kernprof`. The following invocation will run your script, dump results to ``demo_primes.py.lprof``, and display results. .. code:: bash python -m kernprof -lvr demo_primes.py Note: the ``-r`` flag will use "rich-output" if you have the :py:mod:`rich` module installed. See Also: * autoprofiling usage in: :py:mod:`line_profiler.autoprofile` """ # Note: there are better ways to generate primes # https://github.com/Sylhare/nprime __submodules__ = [ 'line_profiler', 'ipython_extension', ] __autogen__ = """ mkinit ./line_profiler/__init__.py --relative mkinit ./line_profiler/__init__.py --relative -w """ # from .line_profiler import __version__ # NOTE: This needs to be in sync with ../kernprof.py and line_profiler.py __version__ = '4.1.2' from .line_profiler import (LineProfiler, load_ipython_extension, load_stats, main, show_func, show_text,) from .explicit_profiler import profile __all__ = ['LineProfiler', 'line_profiler', 'load_ipython_extension', 'load_stats', 'main', 'show_func', 'show_text', '__version__', 'profile'] line_profiler-4.1.2/line_profiler/__main__.py000066400000000000000000000001071452103132100212600ustar00rootroot00000000000000from .line_profiler import main if __name__ == '__main__': main() line_profiler-4.1.2/line_profiler/__main__.pyi000066400000000000000000000000011452103132100214220ustar00rootroot00000000000000 line_profiler-4.1.2/line_profiler/_line_profiler.pyx000066400000000000000000000373531452103132100227350ustar00rootroot00000000000000# cython: language_level=3 # cython: infer_types=True # cython: legacy_implicit_noexcept=True # distutils: language=c++ # distutils: include_dirs = python25.pxd r""" This is the Cython backend used in :py:mod:`line_profiler.line_profiler`. Ignore: # Standalone compile instructions for developers # Assuming the cwd is the repo root. cythonize --annotate --inplace \ ./line_profiler/_line_profiler.pyx \ ./line_profiler/timers.c \ ./line_profiler/unset_trace.c """ from .python25 cimport PyFrameObject, PyObject, PyStringObject from sys import byteorder import sys cimport cython from cpython.version cimport PY_VERSION_HEX from libc.stdint cimport int64_t from libcpp.unordered_map cimport unordered_map import threading # long long int is at least 64 bytes assuming c99 ctypedef unsigned long long int uint64 ctypedef long long int int64 # FIXME: there might be something special we have to do here for Python 3.11 cdef extern from "frameobject.h": """ inline PyObject* get_frame_code(PyFrameObject* frame) { #if PY_VERSION_HEX < 0x030B0000 Py_INCREF(frame->f_code->co_code); return frame->f_code->co_code; #else PyCodeObject* code = PyFrame_GetCode(frame); PyObject* ret = PyCode_GetCode(code); Py_DECREF(code); return ret; #endif } """ cdef object get_frame_code(PyFrameObject* frame) ctypedef int (*Py_tracefunc)(object self, PyFrameObject *py_frame, int what, PyObject *arg) cdef extern from "Python.h": """ // CPython 3.11 broke some stuff by moving PyFrameObject :( #if PY_VERSION_HEX >= 0x030b00a6 #ifndef Py_BUILD_CORE #define Py_BUILD_CORE 1 #endif #include "internal/pycore_frame.h" #include "cpython/code.h" #include "pyframe.h" #endif """ ctypedef struct PyFrameObject ctypedef struct PyCodeObject ctypedef long long PY_LONG_LONG cdef bint PyCFunction_Check(object obj) cdef int PyCode_Addr2Line(PyCodeObject *co, int byte_offset) cdef void PyEval_SetProfile(Py_tracefunc func, object arg) cdef void PyEval_SetTrace(Py_tracefunc func, object arg) ctypedef object (*PyCFunction)(object self, object args) ctypedef struct PyMethodDef: char *ml_name PyCFunction ml_meth int ml_flags char *ml_doc ctypedef struct PyCFunctionObject: PyMethodDef *m_ml PyObject *m_self PyObject *m_module # They're actually #defines, but whatever. cdef int PyTrace_CALL cdef int PyTrace_EXCEPTION cdef int PyTrace_LINE cdef int PyTrace_RETURN cdef int PyTrace_C_CALL cdef int PyTrace_C_EXCEPTION cdef int PyTrace_C_RETURN cdef int PyFrame_GetLineNumber(PyFrameObject *frame) cdef extern from "timers.c": PY_LONG_LONG hpTimer() double hpTimerUnit() cdef extern from "unset_trace.c": void unset_trace() cdef struct LineTime: int64 code int lineno PY_LONG_LONG total_time long nhits cdef struct LastTime: int f_lineno PY_LONG_LONG time cdef inline int64 compute_line_hash(uint64 block_hash, uint64 linenum): """ Compute the hash used to store each line timing in an unordered_map. This is fairly simple, and could use some improvement since linenum isn't technically random, however it seems to be good enough and fast enough for any practical purposes. """ # linenum doesn't need to be int64 but it's really a temporary value # so it doesn't matter return block_hash ^ linenum def label(code): """ Return a (filename, first_lineno, func_name) tuple for a given code object. This is the same labelling as used by the cProfile module in Python 2.5. """ if isinstance(code, str): return ('~', 0, code) # built-in functions ('~' sorts at the end) else: return (code.co_filename, code.co_firstlineno, code.co_name) cpdef _code_replace(func, co_code): """ Implements CodeType.replace for Python < 3.8 """ try: code = func.__code__ except AttributeError: code = func.__func__.__code__ if hasattr(code, 'replace'): # python 3.8+ code = code.replace(co_code=co_code) else: # python <3.8 co = code code = type(code)(co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, co.co_stacksize, co.co_flags, co_code, co.co_consts, co.co_names, co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, co.co_cellvars) return code # Note: this is a regular Python class to allow easy pickling. class LineStats(object): """ Object to encapsulate line-profile statistics. Attributes: timings (dict): Mapping from (filename, first_lineno, function_name) of the profiled function to a list of (lineno, nhits, total_time) tuples for each profiled line. total_time is an integer in the native units of the timer. unit (float): The number of seconds per timer unit. """ def __init__(self, timings, unit): self.timings = timings self.unit = unit cdef class LineProfiler: """ Time the execution of lines of Python code. This is the Cython base class for :class:`line_profiler.line_profiler.LineProfiler`. Example: >>> import copy >>> import line_profiler >>> # Create a LineProfiler instance >>> self = line_profiler.LineProfiler() >>> # Wrap a function >>> copy_fn = self(copy.copy) >>> # Call the function >>> copy_fn(self) >>> # Inspect internal properties >>> self.functions >>> self.c_last_time >>> self.c_code_map >>> self.code_map >>> self.last_time >>> # Print stats >>> self.print_stats() """ cdef unordered_map[int64, unordered_map[int64, LineTime]] _c_code_map # Mapping between thread-id and map of LastTime cdef unordered_map[int64, unordered_map[int64, LastTime]] _c_last_time cdef public list functions cdef public dict code_hash_map, dupes_map cdef public double timer_unit cdef public object threaddata def __init__(self, *functions): self.functions = [] self.code_hash_map = {} self.dupes_map = {} self.timer_unit = hpTimerUnit() self.threaddata = threading.local() for func in functions: self.add_function(func) cpdef add_function(self, func): """ Record line profiling information for the given Python function. """ if hasattr(func, "__wrapped__"): import warnings warnings.warn( "Adding a function with a __wrapped__ attribute. You may want " "to profile the wrapped function by adding %s.__wrapped__ " "instead." % (func.__name__,) ) try: code = func.__code__ except AttributeError: try: code = func.__func__.__code__ except AttributeError: import warnings warnings.warn("Could not extract a code object for the object %r" % (func,)) return if code.co_code in self.dupes_map: self.dupes_map[code.co_code] += [code] # code hash already exists, so there must be a duplicate function. add no-op # co_code = code.co_code + (9).to_bytes(1, byteorder=byteorder) * (len(self.dupes_map[code.co_code])) """ # Code to lookup the NOP opcode, which we will just hard code here # instead of looking it up. Perhaps do a global lookup in the # future. NOP_VALUE: int = opcode.opmap['NOP'] """ NOP_VALUE: int = 9 # Op code should be 2 bytes as stated in # https://docs.python.org/3/library/dis.html # if sys.version_info[0:2] >= (3, 11): NOP_BYTES = NOP_VALUE.to_bytes(2, byteorder=byteorder) # else: # NOP_BYTES = NOP_VALUE.to_bytes(1, byteorder=byteorder) co_padding = NOP_BYTES * (len(self.dupes_map[code.co_code]) + 1) co_code = code.co_code + co_padding CodeType = type(code) code = _code_replace(func, co_code=co_code) try: func.__code__ = code except AttributeError as e: func.__func__.__code__ = code else: self.dupes_map[code.co_code] = [code] # TODO: Since each line can be many bytecodes, this is kinda inefficient # See if this can be sped up by not needing to iterate over every byte for offset, byte in enumerate(code.co_code): code_hash = compute_line_hash(hash((code.co_code)), PyCode_Addr2Line(code, offset)) if not self._c_code_map.count(code_hash): try: self.code_hash_map[code].append(code_hash) except KeyError: self.code_hash_map[code] = [code_hash] self._c_code_map[code_hash] self.functions.append(func) property enable_count: def __get__(self): if not hasattr(self.threaddata, 'enable_count'): self.threaddata.enable_count = 0 return self.threaddata.enable_count def __set__(self, value): self.threaddata.enable_count = value def enable_by_count(self): """ Enable the profiler if it hasn't been enabled before. """ if self.enable_count == 0: self.enable() self.enable_count += 1 def disable_by_count(self): """ Disable the profiler if the number of disable requests matches the number of enable requests. """ if self.enable_count > 0: self.enable_count -= 1 if self.enable_count == 0: self.disable() def __enter__(self): self.enable_by_count() def __exit__(self, exc_type, exc_val, exc_tb): self.disable_by_count() def enable(self): PyEval_SetTrace(python_trace_callback, self) @property def c_code_map(self): """ A Python view of the internal C lookup table. """ return self._c_code_map @property def c_last_time(self): return (self._c_last_time)[threading.get_ident()] @property def code_map(self): """ line_profiler 4.0 no longer directly maintains code_map, but this will construct something similar for backwards compatibility. """ c_code_map = self.c_code_map code_hash_map = self.code_hash_map py_code_map = {} for code, code_hashes in code_hash_map.items(): py_code_map.setdefault(code, {}) for code_hash in code_hashes: c_entries = c_code_map[code_hash] py_entries = {} for key, c_entry in c_entries.items(): py_entry = c_entry.copy() py_entry['code'] = code py_entries[key] = py_entry py_code_map[code].update(py_entries) return py_code_map @property def last_time(self): """ line_profiler 4.0 no longer directly maintains last_time, but this will construct something similar for backwards compatibility. """ c_last_time = (self._c_last_time)[threading.get_ident()] code_hash_map = self.code_hash_map py_last_time = {} for code, code_hashes in code_hash_map.items(): for code_hash in code_hashes: if code_hash in c_last_time: py_last_time[code] = c_last_time[code_hash] return py_last_time cpdef disable(self): self._c_last_time[threading.get_ident()].clear() unset_trace() def get_stats(self): """ Return a LineStats object containing the timings. """ cdef dict cmap = self._c_code_map stats = {} for code in self.code_hash_map: entries = [] for entry in self.code_hash_map[code]: entries += list(cmap[entry].values()) key = label(code) # Merge duplicate line numbers, which occur for branch entrypoints like `if` nhits_by_lineno = {} total_time_by_lineno = {} for line_dict in entries: _, lineno, total_time, nhits = line_dict.values() nhits_by_lineno[lineno] = nhits_by_lineno.setdefault(lineno, 0) + nhits total_time_by_lineno[lineno] = total_time_by_lineno.setdefault(lineno, 0) + total_time entries = [(lineno, nhits, total_time_by_lineno[lineno]) for lineno, nhits in nhits_by_lineno.items()] entries.sort() # NOTE: v4.x may produce more than one entry per line. For example: # 1: for x in range(10): # 2: pass # will produce a 1-hit entry on line 1, and 10-hit entries on lines 1 and 2 # This doesn't affect `print_stats`, because it uses the last entry for a given line (line number is # used a dict key so earlier entries are overwritten), but to keep compatability with other tools, # let's only keep the last entry for each line # Remove all but the last entry for each line entries = list({e[0]: e for e in entries}.values()) stats[key] = entries return LineStats(stats, self.timer_unit) @cython.boundscheck(False) @cython.wraparound(False) cdef extern int python_trace_callback(object self_, PyFrameObject *py_frame, int what, PyObject *arg): """ The PyEval_SetTrace() callback. References: https://github.com/python/cpython/blob/de2a4036/Include/cpython/pystate.h#L16 """ cdef LineProfiler self cdef object code cdef LineTime entry cdef LastTime old cdef int key cdef PY_LONG_LONG time cdef int64 code_hash cdef int64 block_hash cdef unordered_map[int64, LineTime] line_entries cdef uint64 linenum self = self_ if what == PyTrace_LINE or what == PyTrace_RETURN: # Normally we'd need to DECREF the return from get_frame_code, but Cython does that for us block_hash = hash(get_frame_code(py_frame)) linenum = PyFrame_GetLineNumber(py_frame) code_hash = compute_line_hash(block_hash, linenum) if self._c_code_map.count(code_hash): time = hpTimer() ident = threading.get_ident() if self._c_last_time[ident].count(block_hash): old = self._c_last_time[ident][block_hash] line_entries = self._c_code_map[code_hash] key = old.f_lineno if not line_entries.count(key): self._c_code_map[code_hash][key] = LineTime(code_hash, key, 0, 0) self._c_code_map[code_hash][key].nhits += 1 self._c_code_map[code_hash][key].total_time += time - old.time if what == PyTrace_LINE: # Get the time again. This way, we don't record much time wasted # in this function. self._c_last_time[ident][block_hash] = LastTime(linenum, hpTimer()) elif self._c_last_time[ident].count(block_hash): # We are returning from a function, not executing a line. Delete # the last_time record. It may have already been deleted if we # are profiling a generator that is being pumped past its end. self._c_last_time[ident].erase(self._c_last_time[ident].find(block_hash)) return 0 line_profiler-4.1.2/line_profiler/autoprofile/000077500000000000000000000000001452103132100215215ustar00rootroot00000000000000line_profiler-4.1.2/line_profiler/autoprofile/__init__.py000066400000000000000000000106041452103132100236330ustar00rootroot00000000000000""" Auto-Profiling ============== In cases where you want to decorate every function in a script, module, or package decorating every function with ``@profile`` can be tedious. To make this easier "auto-profiling" was introduced to ``line_profiler`` in Version 4.1.0. The "auto-profile" feature allows users to specify a script or module. This is done by passing the name of script or module to kernprof. To demonstrate auto-profiling, we first need to generate a Python script to profile. Write the following code to a file called ``demo_primes2.py``. .. code:: python def is_prime(n): ''' Check if the number "n" is prime, with n > 1. Returns a boolean, True if n is prime. ''' max_val = n ** 0.5 stop = int(max_val + 1) for i in range(2, stop): if n % i == 0: return False return True def find_primes(size): primes = [] for n in range(size): flag = is_prime(n) if flag: primes.append(n) return primes def main(): print('start calculating') primes = find_primes(100000) print(f'done calculating. Found {len(primes)} primes.') if __name__ == '__main__': main() Note that this is the nearly the same "primes" example from the "Basic Usage" section in :py:mod:`line_profiler`, but none of the functions are decorated with ``@profile``. To run this script with auto-profiling we invoke kernprof with ``-p`` or ``--prof-mod`` and pass the names of the script. When running with ``-p`` we must also run with ``-l`` to enable line profiling. In this example we include ``-v`` as well to display the output after we run it. .. code:: bash python -m kernprof -lv -p demo_primes2.py demo_primes2.py The output will look like this: .. code:: start calculating done calculating. Found 9594 primes. Wrote profile results to demo_primes2.py.lprof Timer unit: 1e-06 s Total time: 0.677348 s File: demo_primes2.py Function: is_prime at line 4 Line # Hits Time Per Hit % Time Line Contents ============================================================== 4 def is_prime(n): 5 ''' 6 Check if the number "n" is prime, with n > 1. 7 8 Returns a boolean, True if n is prime. 9 ''' 10 100000 19921.6 0.2 2.9 math.floor(1.3) 11 100000 17743.6 0.2 2.6 max_val = n ** 0.5 12 100000 23962.7 0.2 3.5 stop = int(max_val + 1) 13 2745693 262005.7 0.1 38.7 for i in range(2, stop): 14 2655287 342216.1 0.1 50.5 if n % i == 0: 15 90406 10401.4 0.1 1.5 return False 16 9594 1097.2 0.1 0.2 return True Total time: 1.56657 s File: demo_primes2.py Function: find_primes at line 19 Line # Hits Time Per Hit % Time Line Contents ============================================================== 19 def find_primes(size): 20 1 0.3 0.3 0.0 primes = [] 21 100000 11689.5 0.1 0.7 for n in range(size): 22 100000 1541848.0 15.4 98.4 flag = is_prime(n) 23 90406 10260.0 0.1 0.7 if flag: 24 9594 2775.9 0.3 0.2 primes.append(n) 25 1 0.2 0.2 0.0 return primes Total time: 1.61013 s File: demo_primes2.py Function: main at line 28 Line # Hits Time Per Hit % Time Line Contents ============================================================== 28 def main(): 29 1 17.6 17.6 0.0 print('start calculating') 30 1 1610081.3 2e+06 100.0 primes = find_primes(100000) 31 1 26.6 26.6 0.0 print(f'done calculating. Found {len(primes)} primes.') """ line_profiler-4.1.2/line_profiler/autoprofile/ast_profle_transformer.py000066400000000000000000000134411452103132100266560ustar00rootroot00000000000000import ast def ast_create_profile_node(modname, profiler_name='profile', attr='add_imported_function_or_module'): """Create an abstract syntax tree node that adds an object to the profiler to be profiled. An abstract syntax tree node is created which calls the attr method from profile and passes modname to it. At runtime, this adds the object to the profiler so it can be profiled. This node must be added after the first instance of modname in the AST and before it is used. The node will look like: >>> # xdoctest: +SKIP >>> import foo.bar >>> profile.add_imported_function_or_module(foo.bar) Args: script_file (str): path to script being profiled. prof_mod (List[str]): list of imports to profile in script. passing the path to script will profile the whole script. the objects can be specified using its dotted path or full path (if applicable). Returns: (_ast.Expr): expr AST node that adds modname to profiler. """ func = ast.Attribute(value=ast.Name(id=profiler_name, ctx=ast.Load()), attr=attr, ctx=ast.Load()) names = modname.split('.') value = ast.Name(id=names[0], ctx=ast.Load()) for name in names[1:]: value = ast.Attribute(attr=name, ctx=ast.Load(), value=value) expr = ast.Expr(value=ast.Call(func=func, args=[value], keywords=[])) return expr class AstProfileTransformer(ast.NodeTransformer): """Transform an abstract syntax tree adding profiling to all of its objects. Adds profiler decorators on all functions & methods that are not already decorated with the profiler. If profile_imports is True, a profiler method call to profile is added to all imports immediately after the import. """ def __init__(self, profile_imports=False, profiled_imports=None, profiler_name='profile'): """Initializes the AST transformer with the profiler name. Args: profile_imports (bool): If True, profile all imports. profiled_imports (List[str]): list of dotted paths of imports to skip that have already been added to profiler. profiler_name (str): the profiler name used as decorator and for the method call to add to the object to the profiler. """ self._profile_imports = bool(profile_imports) self._profiled_imports = profiled_imports if profiled_imports is not None else [] self._profiler_name = profiler_name def visit_FunctionDef(self, node): """Decorate functions/methods with profiler. Checks if the function/method already has a profile_name decorator, if not, it will append profile_name to the end of the node's decorator list. The decorator is added to the end of the list to avoid conflicts with other decorators e.g. @staticmethod. Args: (_ast.FunctionDef): node function/method in the AST Returns: (_ast.FunctionDef): node function/method with profiling decorator """ decor_ids = set() for decor in node.decorator_list: try: decor_ids.add(decor.id) except AttributeError: ... if self._profiler_name not in decor_ids: node.decorator_list.append(ast.Name(id=self._profiler_name, ctx=ast.Load())) return self.generic_visit(node) def _visit_import(self, node): """Add a node that profiles an import If profile_imports is True and the import is not in profiled_imports, a node which calls the profiler method, which adds the object to the profiler, is added immediately after the import. Args: node (Union[_ast.Import,_ast.ImportFrom]): import in the AST Returns: (Union[Union[_ast.Import,_ast.ImportFrom],List[Union[_ast.Import,_ast.ImportFrom,_ast.Expr]]]): node if profile_imports is False: returns the import node if profile_imports is True: returns list containing the import node and the profiling node """ if not self._profile_imports: return self.generic_visit(node) visited = [self.generic_visit(node)] for names in node.names: node_name = names.name if names.asname is None else names.asname if node_name in self._profiled_imports: continue self._profiled_imports.append(node_name) expr = ast_create_profile_node(node_name) visited.append(expr) return visited def visit_Import(self, node): """Add a node that profiles an object imported using the "import foo" sytanx Args: node (_ast.Import): import in the AST Returns: (Union[_ast.Import,List[Union[_ast.Import,_ast.Expr]]]): node if profile_imports is False: returns the import node if profile_imports is True: returns list containing the import node and the profiling node """ return self._visit_import(node) def visit_ImportFrom(self, node): """Add a node that profiles an object imported using the "from foo import bar" syntax Args: node (_ast.ImportFrom): import in the AST Returns: (Union[_ast.ImportFrom,List[Union[_ast.ImportFrom,_ast.Expr]]]): node if profile_imports is False: returns the import node if profile_imports is True: returns list containing the import node and the profiling node """ return self._visit_import(node) line_profiler-4.1.2/line_profiler/autoprofile/ast_profle_transformer.pyi000066400000000000000000000015201452103132100270220ustar00rootroot00000000000000from typing import List import _ast import ast from typing import Union def ast_create_profile_node(modname, profiler_name: str = ..., attr: str = ...) -> (_ast.Expr): ... class AstProfileTransformer(ast.NodeTransformer): def __init__(self, profile_imports: bool = False, profiled_imports: List[str] | None = None, profiler_name: str = 'profile') -> None: ... def visit_FunctionDef(self, node) -> (_ast.FunctionDef): ... def visit_Import( self, node: _ast.Import ) -> (Union[_ast.Import, List[Union[_ast.Import, _ast.Expr]]]): ... def visit_ImportFrom( self, node: _ast.ImportFrom ) -> (Union[_ast.ImportFrom, List[Union[_ast.ImportFrom, _ast.Expr]]]): ... line_profiler-4.1.2/line_profiler/autoprofile/ast_tree_profiler.py000066400000000000000000000152071452103132100256100ustar00rootroot00000000000000import ast import os from .ast_profle_transformer import (AstProfileTransformer, ast_create_profile_node) from .profmod_extractor import ProfmodExtractor __docstubs__ = """ from .ast_profle_transformer import AstProfileTransformer from .profmod_extractor import ProfmodExtractor """ class AstTreeProfiler: """Create an abstract syntax tree of a script and add profiling to it. Reads a script file and generates an abstract syntax tree, then adds nodes and/or decorators to the AST that adds the specified functions/methods, classes & modules in prof_mod to the profiler to be profiled. """ def __init__(self, script_file, prof_mod, profile_imports, ast_transformer_class_handler=AstProfileTransformer, profmod_extractor_class_handler=ProfmodExtractor): """Initializes the AST tree profiler instance with the script file path Args: script_file (str): path to script being profiled. prof_mod (List[str]): list of imports to profile in script. passing the path to script will profile the whole script. the objects can be specified using its dotted path or full path (if applicable). profile_imports (bool): if True, when auto-profiling whole script, profile all imports aswell. ast_transformer_class_handler (Type): the AstProfileTransformer class that handles profiling the whole script. profmod_extractor_class_handler (Type): the ProfmodExtractor class that handles mapping prof_mod to objects in the script. """ self._script_file = script_file self._prof_mod = prof_mod self._profile_imports = profile_imports self._ast_transformer_class_handler = ast_transformer_class_handler self._profmod_extractor_class_handler = profmod_extractor_class_handler @staticmethod def _check_profile_full_script(script_file, prof_mod): """Check whether whole script should be profiled. Checks whether path to script has been passed to prof_mod indicating that the whole script should be profiled Args: script_file (str): path to script being profiled. prof_mod (List[str]): list of imports to profile in script. passing the path to script will profile the whole script. the objects can be specified using its dotted path or full path (if applicable). Returns: (bool): profile_full_script if True, profile whole script. """ script_file_realpath = os.path.realpath(script_file) profile_full_script = script_file_realpath in map(os.path.realpath, prof_mod) return profile_full_script @staticmethod def _get_script_ast_tree(script_file): """Generate an abstract syntax from a script file. Args: script_file (str): path to script being profiled. Returns: tree (_ast.Module): abstract syntax tree of the script. """ with open(script_file, 'r') as f: script_text = f.read() tree = ast.parse(script_text, filename=script_file) return tree def _profile_ast_tree(self, tree, tree_imports_to_profile_dict, profile_full_script=False, profile_imports=False): """Add profiling to an abstract syntax tree. Adds nodes to the AST that adds the specified objects to the profiler. If profile_full_script is True, all functions/methods, classes & modules in the script have a node added to the AST to add them to the profiler. If profile_imports is True as well as profile_full_script, all imports are have a node added to the AST to add them to the profiler. Args: tree (_ast.Module): abstract syntax tree to be profiled. tree_imports_to_profile_dict (Dict[int,str]): dict of imports to profile key (int): index of import in AST value (str): alias (or name if no alias used) of import profile_full_script (bool): if True, profile whole script. profile_imports (bool): if True, and profile_full_script is True, profile all imports aswell. Returns: (_ast.Module): tree abstract syntax tree with profiling. """ profiled_imports = [] argsort_tree_indexes = sorted(list(tree_imports_to_profile_dict), reverse=True) for tree_index in argsort_tree_indexes: name = tree_imports_to_profile_dict[tree_index] expr = ast_create_profile_node(name) tree.body.insert(tree_index + 1, expr) profiled_imports.append(name) if profile_full_script: tree = self._ast_transformer_class_handler(profile_imports=profile_imports, profiled_imports=profiled_imports).visit(tree) ast.fix_missing_locations(tree) return tree def profile(self): """Create an abstract syntax tree of a script and add profiling to it. Reads a script file and generates an abstract syntax tree. Then matches imports in the script's AST with the names in prof_mod. The matched imports are added to the profiler for profiling. If path to script is found in prof_mod, all functions/methods, classes & modules are added to the profiler. If profile_imports is True as well as path to script in prof_mod, all the imports in the script are added to the profiler. Returns: (_ast.Module): tree abstract syntax tree with profiling. """ profile_full_script = self._check_profile_full_script(self._script_file, self._prof_mod) tree = self._get_script_ast_tree(self._script_file) tree_imports_to_profile_dict = self._profmod_extractor_class_handler( tree, self._script_file, self._prof_mod).run() tree_profiled = self._profile_ast_tree(tree, tree_imports_to_profile_dict, profile_full_script=profile_full_script, profile_imports=self._profile_imports) return tree_profiled line_profiler-4.1.2/line_profiler/autoprofile/ast_tree_profiler.pyi000066400000000000000000000010631452103132100257540ustar00rootroot00000000000000from typing import List from typing import Type import _ast from .ast_profle_transformer import AstProfileTransformer from .profmod_extractor import ProfmodExtractor __docstubs__: str class AstTreeProfiler: def __init__( self, script_file: str, prof_mod: List[str], profile_imports: bool, ast_transformer_class_handler: Type = AstProfileTransformer, profmod_extractor_class_handler: Type = ProfmodExtractor) -> None: ... def profile(self) -> (_ast.Module): ... line_profiler-4.1.2/line_profiler/autoprofile/autoprofile.py000066400000000000000000000054151452103132100244310ustar00rootroot00000000000000""" AutoProfile Script Demo ======================= The following demo is end-to-end bash code that writes a demo script and profiles it with autoprofile. .. code:: bash # Write demo python script to disk python -c "if 1: import textwrap text = textwrap.dedent( ''' def plus(a, b): return a + b def fib(n): a, b = 0, 1 while a < n: a, b = b, plus(a, b) def main(): import math import time start = time.time() print('start calculating') while time.time() - start < 1: fib(10) math.factorial(1000) print('done calculating') main() ''' ).strip() with open('demo.py', 'w') as file: file.write(text) " echo "---" echo "## Profile With AutoProfile" python -m kernprof -p demo.py -l demo.py python -m line_profiler -rmt demo.py.lprof """ import types from .ast_tree_profiler import AstTreeProfiler from .line_profiler_utils import add_imported_function_or_module PROFILER_LOCALS_NAME = 'prof' def _extend_line_profiler_for_profiling_imports(prof): """Allow profiler to handle functions/methods, classes & modules with a single call. Add a method to LineProfiler that can identify whether the object is a function/method, class or module and handle it's profiling accordingly. Mainly used for profiling objects that are imported. (Workaround to keep changes needed by autoprofile separate from base LineProfiler) Args: prof (LineProfiler): instance of LineProfiler. """ prof.add_imported_function_or_module = types.MethodType(add_imported_function_or_module, prof) def run(script_file, ns, prof_mod, profile_imports=False): """Automatically profile a script and run it. Profile functions, classes & modules specified in prof_mod without needing to add @profile decorators. Args: script_file (str): path to script being profiled. ns (dict): "locals" from kernprof scope. prof_mod (List[str]): list of imports to profile in script. passing the path to script will profile the whole script. the objects can be specified using its dotted path or full path (if applicable). profile_imports (bool): if True, when auto-profiling whole script, profile all imports aswell. """ tree_profiled = AstTreeProfiler(script_file, prof_mod, profile_imports).profile() _extend_line_profiler_for_profiling_imports(ns[PROFILER_LOCALS_NAME]) exec(compile(tree_profiled, script_file, 'exec'), ns, ns) line_profiler-4.1.2/line_profiler/autoprofile/autoprofile.pyi000066400000000000000000000002661452103132100246010ustar00rootroot00000000000000from typing import List PROFILER_LOCALS_NAME: str def run(script_file: str, ns: dict, prof_mod: List[str], profile_imports: bool = False) -> None: ... line_profiler-4.1.2/line_profiler/autoprofile/line_profiler_utils.py000066400000000000000000000013451452103132100261470ustar00rootroot00000000000000import inspect def add_imported_function_or_module(self, item): """Method to add an object to LineProfiler to be profiled. This method is used to extend an instance of LineProfiler so it can identify whether an object is function/method, class or module and handle it's profiling accordingly. Args: item (Callable | Type | ModuleType): object to be profiled. """ if inspect.isfunction(item): self.add_function(item) elif inspect.isclass(item): for k, v in item.__dict__.items(): if inspect.isfunction(v): self.add_function(v) elif inspect.ismodule(item): self.add_module(item) else: return self.enable_by_count() line_profiler-4.1.2/line_profiler/autoprofile/line_profiler_utils.pyi000066400000000000000000000002731452103132100263170ustar00rootroot00000000000000from typing import Callable from typing import Type from types import ModuleType def add_imported_function_or_module( self, item: Callable | Type | ModuleType) -> None: ... line_profiler-4.1.2/line_profiler/autoprofile/profmod_extractor.py000066400000000000000000000234621452103132100256430ustar00rootroot00000000000000import ast import os import sys from .util_static import (modname_to_modpath, modpath_to_modname, package_modpaths) class ProfmodExtractor: """Map prof_mod to imports in an abstract syntax tree. Takes the paths and dotted paths in prod_mod and finds their respective imports in an abstract syntax tree. """ def __init__(self, tree, script_file, prof_mod): """Initializes the AST tree profiler instance with the AST, script file path and prof_mod Args: tree (_ast.Module): abstract syntax tree to fetch imports from. script_file (str): path to script being profiled. prof_mod (List[str]): list of imports to profile in script. passing the path to script will profile the whole script. the objects can be specified using its dotted path or full path (if applicable). """ self._tree = tree self._script_file = script_file self._prof_mod = prof_mod @staticmethod def _is_path(text): """Check whether a string is a path. Checks if a string contains a slash or ends with .py indicating it is a path. Args: text (str): string to check whether it is a path or not Returns: ret (bool): bool indicating whether the string is a path or not """ ret = ('/' in text.replace('\\', '/')) or text.endswith('.py') return ret @classmethod def _get_modnames_to_profile_from_prof_mod(cls, script_file, prof_mod): """Grab the valid paths and all dotted paths in prof_mod and their subpackages and submodules, in the form of dotted paths. First all items in prof_mod are converted to a valid path. if unable to convert, check if the item is an invalid path and skip it, else assume it is an installed package. The valid paths are then converted to dotted paths. The converted dotted paths along with the items assumed to be installed packages are added a list of modnames_to_profile. Then all subpackages and submodules under each valid path is fetched, converted to dotted path and also added to the list. if script_file is in prof_mod it is skipped to avoid name collision with othe imports, it will be processed elsewhere in the autoprofile pipeline. Args: script_file (str): path to script being profiled. prof_mod (List[str]): list of imports to profile in script. passing the path to script will profile the whole script. the objects can be specified using its dotted path or full path (if applicable). Returns: modnames_to_profile (List[str]): list of dotted paths to profile. """ script_directory = os.path.realpath(os.path.dirname(script_file)) """add script folder to modname_to_modpath sys_path to allow it to resolve modpaths""" new_sys_path = [script_directory] + sys.path script_file_realpath = os.path.realpath(script_file) modnames_to_profile = [] for mod in prof_mod: if script_file_realpath == os.path.realpath(mod): """ skip script_file as it will add the script's name without its extension which could have the same name as another import or function leading to unwanted profiling """ continue """ convert the item in prof_mod into a valid path. if it fails, the item may point to an installed module rather than local script so we check if the item is path and whether that path exists, else skip the item. """ modpath = modname_to_modpath(mod, sys_path=new_sys_path) if modpath is None: """if cannot convert to modpath, check if already path and if invalid""" if not os.path.exists(mod): if cls._is_path(mod): """modpath does not exist, so skip""" continue modnames_to_profile.append(mod) continue """assume item is and installed package. modpath_to_modname will have no effect""" modpath = mod """convert path to dotted path and add it to list to be profiled""" try: modname = modpath_to_modname(modpath) except ValueError: continue if modname not in modnames_to_profile: modnames_to_profile.append(modname) """ recursively fetch all subpackages and submodules, convert them to dotted paths and add them to list to be profiled """ for submod_path in package_modpaths(modpath): submod_name = modpath_to_modname(submod_path) if submod_name not in modnames_to_profile: modnames_to_profile.append(submod_name) return modnames_to_profile @staticmethod def _ast_get_imports_from_tree(tree): """Get all imports in an abstract syntax tree. Args: tree (_ast.Module): abstract syntax tree to fetch imports from. Returns: module_dict_list (List[Dict[str,Union[str,int]]]): list of dicts of all imports in the tree, containing: name (str): the real name of the import. e.g. foo from "import foo as bar" alias (str): the alias of an import if applicable. e.g. bar from "import foo as bar" tree_index (int): the index of the import as found in the tree """ module_dict_list = [] modname_list = [] for idx, node in enumerate(tree.body): if isinstance(node, ast.Import): for name in node.names: modname = name.name if modname not in modname_list: alias = name.asname module_dict = { 'name': modname, 'alias': alias, 'tree_index': idx, } module_dict_list.append(module_dict) modname_list.append(modname) elif isinstance(node, ast.ImportFrom): for name in node.names: modname = node.module + '.' + name.name if modname not in modname_list: alias = name.asname or name.name module_dict = { 'name': modname, 'alias': alias, 'tree_index': idx, } module_dict_list.append(module_dict) modname_list.append(modname) return module_dict_list @staticmethod def _find_modnames_in_tree_imports(modnames_to_profile, module_dict_list): """Map modnames to imports from an abstract sytax tree. Find imports in modue_dict_list, created from an abstract syntax tree, that match dotted paths in modnames_to_profile. When a submodule is imported, both the submodule and the parent module are checked whether they are in modnames_to_profile. As the user can ask to profile "foo" when only "from foo import bar" is imported, so both foo and bar are checked. The real import name of an import is used to map to the dotted paths. The import's alias is stored in the output dict. Args: modnames_to_profile (List[str]): list of dotted paths to profile. module_dict_list (List[Dict[str,Union[str,int]]]): list of dicts of all imports in the tree. Returns: modnames_found_in_tree (Dict[int,str]): dict of imports found key (int): index of import in AST value (str): alias (or name if no alias used) of import """ modnames_found_in_tree = {} modname_added_list = [] for i, module_dict in enumerate(module_dict_list): modname = module_dict['name'] if modname in modname_added_list: continue """check if either the parent module or submodule are in modnames_to_profile""" if modname not in modnames_to_profile and modname.rsplit('.', 1)[0] not in modnames_to_profile: continue name = module_dict['alias'] or modname modname_added_list.append(modname) modnames_found_in_tree[module_dict['tree_index']] = name return modnames_found_in_tree def run(self): """Map prof_mod to imports in an abstract syntax tree. Takes the paths and dotted paths in prod_mod and finds their respective imports in an abstract syntax tree, returning their alias and the index they appear in the AST. Returns: (Dict[int,str]): tree_imports_to_profile_dict dict of imports to profile key (int): index of import in AST value (str): alias (or name if no alias used) of import """ modnames_to_profile = self._get_modnames_to_profile_from_prof_mod(self._script_file, self._prof_mod) module_dict_list = self._ast_get_imports_from_tree(self._tree) tree_imports_to_profile_dict = self._find_modnames_in_tree_imports(modnames_to_profile, module_dict_list) return tree_imports_to_profile_dict line_profiler-4.1.2/line_profiler/autoprofile/profmod_extractor.pyi000066400000000000000000000004021452103132100260010ustar00rootroot00000000000000import _ast from typing import List from typing import Dict class ProfmodExtractor: def __init__(self, tree: _ast.Module, script_file: str, prof_mod: List[str]) -> None: ... def run(self) -> (Dict[int, str]): ... line_profiler-4.1.2/line_profiler/autoprofile/util_static.py000066400000000000000000000516131452103132100244250ustar00rootroot00000000000000""" This file was autogenerated based on code in :py:mod:`ubelt` and :py:mod:`xdoctest` via dev/maintain/port_utilities.py in the line_profiler repo. """ from os.path import abspath from os.path import dirname from os.path import exists from os.path import expanduser from os.path import relpath from os.path import splitext from os.path import basename from os.path import isdir from os.path import join import os from os.path import split from os.path import isfile from os.path import realpath import sys # from xdoctest import utils def package_modpaths( pkgpath, with_pkg=False, with_mod=True, followlinks=True, recursive=True, with_libs=False, check=True, ): r""" Finds sub-packages and sub-modules belonging to a package. Args: pkgpath (str): path to a module or package with_pkg (bool): if True includes package __init__ files (default = False) with_mod (bool): if True includes module files (default = True) exclude (list): ignores any module that matches any of these patterns recursive (bool): if False, then only child modules are included with_libs (bool): if True then compiled shared libs will be returned as well check (bool): if False, then then pkgpath is considered a module even if it does not contain an __init__ file. Yields: str: module names belonging to the package References: http://stackoverflow.com/questions/1707709/list-modules-in-py-package Example: >>> from xdoctest.static_analysis import * >>> pkgpath = modname_to_modpath('xdoctest') >>> paths = list(package_modpaths(pkgpath)) >>> print('\n'.join(paths)) >>> names = list(map(modpath_to_modname, paths)) >>> assert 'xdoctest.core' in names >>> assert 'xdoctest.__main__' in names >>> assert 'xdoctest' not in names >>> print('\n'.join(names)) """ if isfile(pkgpath): (yield pkgpath) else: if with_pkg: root_path = join(pkgpath, "__init__.py") if (not check) or exists(root_path): (yield root_path) valid_exts = [".py"] if with_libs: valid_exts += _platform_pylib_exts() for dpath, dnames, fnames in os.walk(pkgpath, followlinks=followlinks): ispkg = exists(join(dpath, "__init__.py")) if ispkg or (not check): check = True if with_mod: for fname in fnames: if splitext(fname)[1] in valid_exts: if fname != "__init__.py": path = join(dpath, fname) (yield path) if with_pkg: for dname in dnames: path = join(dpath, dname, "__init__.py") if exists(path): (yield path) else: del dnames[:] if not recursive: break def _parse_static_node_value(node): """ Extract a constant value from a node if possible """ import ast from collections import OrderedDict if isinstance(node, ast.Num): value = node.n elif isinstance(node, ast.Str): value = node.s elif isinstance(node, ast.List): value = list(map(_parse_static_node_value, node.elts)) elif isinstance(node, ast.Tuple): value = tuple(map(_parse_static_node_value, node.elts)) elif isinstance(node, ast.Dict): keys = map(_parse_static_node_value, node.keys) values = map(_parse_static_node_value, node.values) value = OrderedDict(zip(keys, values)) elif isinstance(node, ast.NameConstant): value = node.value else: print(node.__dict__) raise TypeError( """Cannot parse a static value from non-static node of type: {!r}""".format( type(node) ) ) return value def _extension_module_tags(): """ Returns valid tags an extension module might have Returns: List[str] """ import sysconfig tags = [] tags.append(sysconfig.get_config_var("SOABI")) tags.append("abi3") tags = [t for t in tags if t] return tags def _static_parse(varname, fpath): """ Statically parse the a constant variable from a python file Example: >>> # xdoctest: +SKIP("ubelt dependency") >>> dpath = ub.Path.appdir('tests/import/staticparse').ensuredir() >>> fpath = (dpath / 'foo.py') >>> fpath.write_text('a = {1: 2}') >>> assert _static_parse('a', fpath) == {1: 2} >>> fpath.write_text('a = 2') >>> assert _static_parse('a', fpath) == 2 >>> fpath.write_text('a = "3"') >>> assert _static_parse('a', fpath) == "3" >>> fpath.write_text('a = ["3", 5, 6]') >>> assert _static_parse('a', fpath) == ["3", 5, 6] >>> fpath.write_text('a = ("3", 5, 6)') >>> assert _static_parse('a', fpath) == ("3", 5, 6) >>> fpath.write_text('b = 10' + chr(10) + 'a = None') >>> assert _static_parse('a', fpath) is None >>> import pytest >>> with pytest.raises(TypeError): >>> fpath.write_text('a = list(range(10))') >>> assert _static_parse('a', fpath) is None >>> with pytest.raises(AttributeError): >>> fpath.write_text('a = list(range(10))') >>> assert _static_parse('c', fpath) is None """ import ast if not exists(fpath): raise ValueError("fpath={!r} does not exist".format(fpath)) with open(fpath, "r") as file_: sourcecode = file_.read() pt = ast.parse(sourcecode) class StaticVisitor(ast.NodeVisitor): def visit_Assign(self, node): for target in node.targets: if getattr(target, "id", None) == varname: self.static_value = _parse_static_node_value(node.value) visitor = StaticVisitor() visitor.visit(pt) try: value = visitor.static_value except AttributeError: value = "Unknown {}".format(varname) raise AttributeError(value) return value def _platform_pylib_exts(): """ Returns .so, .pyd, or .dylib depending on linux, win or mac. On python3 return the previous with and without abi (e.g. .cpython-35m-x86_64-linux-gnu) flags. On python2 returns with and without multiarch. Returns: tuple """ import sysconfig valid_exts = [] base_ext = "." + sysconfig.get_config_var("EXT_SUFFIX").split(".")[(-1)] for tag in _extension_module_tags(): valid_exts.append((("." + tag) + base_ext)) valid_exts.append(base_ext) return tuple(valid_exts) def _syspath_modname_to_modpath(modname, sys_path=None, exclude=None): """ syspath version of modname_to_modpath Args: modname (str): name of module to find sys_path (None | List[str | PathLike]): The paths to search for the module. If unspecified, defaults to ``sys.path``. exclude (List[str | PathLike] | None): If specified prevents these directories from being searched. Defaults to None. Returns: str: path to the module. Note: This is much slower than the pkgutil mechanisms. There seems to be a change to the editable install mechanism: https://github.com/pypa/setuptools/issues/3548 Trying to find more docs about it. TODO: add a test where we make an editable install, regular install, standalone install, and check that we always find the right path. Example: >>> print(_syspath_modname_to_modpath('xdoctest.static_analysis')) ...static_analysis.py >>> print(_syspath_modname_to_modpath('xdoctest')) ...xdoctest >>> # xdoctest: +REQUIRES(CPython) >>> print(_syspath_modname_to_modpath('_ctypes')) ..._ctypes... >>> assert _syspath_modname_to_modpath('xdoctest', sys_path=[]) is None >>> assert _syspath_modname_to_modpath('xdoctest.static_analysis', sys_path=[]) is None >>> assert _syspath_modname_to_modpath('_ctypes', sys_path=[]) is None >>> assert _syspath_modname_to_modpath('this', sys_path=[]) is None Example: >>> # test what happens when the module is not visible in the path >>> modname = 'xdoctest.static_analysis' >>> modpath = _syspath_modname_to_modpath(modname) >>> exclude = [split_modpath(modpath)[0]] >>> found = _syspath_modname_to_modpath(modname, exclude=exclude) >>> if found is not None: >>> # Note: the basic form of this test may fail if there are >>> # multiple versions of the package installed. Try and fix that. >>> other = split_modpath(found)[0] >>> assert other not in exclude >>> exclude.append(other) >>> found = _syspath_modname_to_modpath(modname, exclude=exclude) >>> if found is not None: >>> raise AssertionError( >>> 'should not have found {}.'.format(found) + >>> ' because we excluded: {}.'.format(exclude) + >>> ' cwd={} '.format(os.getcwd()) + >>> ' sys.path={} '.format(sys.path) >>> ) """ import glob def _isvalid(modpath, base): subdir = dirname(modpath) while subdir and (subdir != base): if not exists(join(subdir, "__init__.py")): return False subdir = dirname(subdir) return True _fname_we = modname.replace(".", os.path.sep) candidate_fnames = [(_fname_we + ".py")] candidate_fnames += [(_fname_we + ext) for ext in _platform_pylib_exts()] if sys_path is None: sys_path = sys.path candidate_dpaths = [("." if (p == "") else p) for p in sys_path] if exclude: def normalize(p): if sys.platform.startswith("win32"): return realpath(p).lower() else: return realpath(p) real_exclude = {normalize(p) for p in exclude} candidate_dpaths = [ p for p in candidate_dpaths if (normalize(p) not in real_exclude) ] def check_dpath(dpath): modpath = join(dpath, _fname_we) if exists(modpath): if isfile(join(modpath, "__init__.py")): if _isvalid(modpath, dpath): return modpath for fname in candidate_fnames: modpath = join(dpath, fname) if isfile(modpath): if _isvalid(modpath, dpath): return modpath _pkg_name = _fname_we.split(os.path.sep)[0] _pkg_name_hypen = _pkg_name.replace("_", "-") _egglink_fname1 = _pkg_name + ".egg-link" _egglink_fname2 = _pkg_name_hypen + ".egg-link" _editable_fname_pth_pat = ("__editable__." + _pkg_name) + "-*.pth" _editable_fname_finder_py_pat = ("__editable___" + _pkg_name) + "_*finder.py" found_modpath = None for dpath in candidate_dpaths: modpath = check_dpath(dpath) if modpath: found_modpath = modpath break new_editable_finder_paths = sorted( glob.glob(join(dpath, _editable_fname_finder_py_pat)) ) if new_editable_finder_paths: for finder_fpath in new_editable_finder_paths: mapping = _static_parse("MAPPING", finder_fpath) try: target = dirname(mapping[_pkg_name]) except KeyError: ... else: if (not exclude) or (normalize(target) not in real_exclude): modpath = check_dpath(target) if modpath: found_modpath = modpath break if found_modpath is not None: break new_editable_pth_paths = sorted(glob.glob(join(dpath, _editable_fname_pth_pat))) if new_editable_pth_paths: import pathlib for editable_pth in new_editable_pth_paths: editable_pth = pathlib.Path(editable_pth) target = editable_pth.read_text().strip().split("\n")[(-1)] if (not exclude) or (normalize(target) not in real_exclude): modpath = check_dpath(target) if modpath: found_modpath = modpath break if found_modpath is not None: break linkpath1 = join(dpath, _egglink_fname1) linkpath2 = join(dpath, _egglink_fname2) linkpath = None if isfile(linkpath1): linkpath = linkpath1 elif isfile(linkpath2): linkpath = linkpath2 if linkpath is not None: with open(linkpath, "r") as file: target = file.readline().strip() if (not exclude) or (normalize(target) not in real_exclude): modpath = check_dpath(target) if modpath: found_modpath = modpath break return found_modpath def modname_to_modpath(modname, hide_init=True, hide_main=False, sys_path=None): """ Finds the path to a python module from its name. Determines the path to a python module without directly import it Converts the name of a module (__name__) to the path (__file__) where it is located without importing the module. Returns None if the module does not exist. Args: modname (str): The name of a module in ``sys_path``. hide_init (bool): if False, __init__.py will be returned for packages. Defaults to True. hide_main (bool): if False, and ``hide_init`` is True, __main__.py will be returned for packages, if it exists. Defaults to False. sys_path (None | List[str | PathLike]): The paths to search for the module. If unspecified, defaults to ``sys.path``. Returns: str | None: modpath - path to the module, or None if it doesn't exist Example: >>> modname = 'xdoctest.__main__' >>> modpath = modname_to_modpath(modname, hide_main=False) >>> assert modpath.endswith('__main__.py') >>> modname = 'xdoctest' >>> modpath = modname_to_modpath(modname, hide_init=False) >>> assert modpath.endswith('__init__.py') >>> # xdoctest: +REQUIRES(CPython) >>> modpath = basename(modname_to_modpath('_ctypes')) >>> assert 'ctypes' in modpath """ modpath = _syspath_modname_to_modpath(modname, sys_path) if modpath is None: return None modpath = normalize_modpath(modpath, hide_init=hide_init, hide_main=hide_main) return modpath def split_modpath(modpath, check=True): """ Splits the modpath into the dir that must be in PYTHONPATH for the module to be imported and the modulepath relative to this directory. Args: modpath (str): module filepath check (bool): if False, does not raise an error if modpath is a directory and does not contain an ``__init__.py`` file. Returns: Tuple[str, str]: (directory, rel_modpath) Raises: ValueError: if modpath does not exist or is not a package Example: >>> from xdoctest import static_analysis >>> modpath = static_analysis.__file__.replace('.pyc', '.py') >>> modpath = abspath(modpath) >>> dpath, rel_modpath = split_modpath(modpath) >>> recon = join(dpath, rel_modpath) >>> assert recon == modpath >>> assert rel_modpath == join('xdoctest', 'static_analysis.py') """ modpath_ = abspath(expanduser(modpath)) if check: if not exists(modpath_): if not exists(modpath): raise ValueError("modpath={} does not exist".format(modpath)) raise ValueError("modpath={} is not a module".format(modpath)) if isdir(modpath_) and (not exists(join(modpath, "__init__.py"))): raise ValueError("modpath={} is not a module".format(modpath)) (full_dpath, fname_ext) = split(modpath_) _relmod_parts = [fname_ext] dpath = full_dpath while exists(join(dpath, "__init__.py")): (dpath, dname) = split(dpath) _relmod_parts.append(dname) relmod_parts = _relmod_parts[::(-1)] rel_modpath = os.path.sep.join(relmod_parts) return (dpath, rel_modpath) def normalize_modpath(modpath, hide_init=True, hide_main=False): """ Normalizes __init__ and __main__ paths. Args: modpath (str | PathLike): path to a module hide_init (bool): if True, always return package modules as __init__.py files otherwise always return the dpath. Defaults to True. hide_main (bool): if True, always strip away main files otherwise ignore __main__.py. Defaults to False. Returns: str | PathLike: a normalized path to the module Note: Adds __init__ if reasonable, but only removes __main__ by default Example: >>> from xdoctest import static_analysis as module >>> modpath = module.__file__ >>> assert normalize_modpath(modpath) == modpath.replace('.pyc', '.py') >>> dpath = dirname(modpath) >>> res0 = normalize_modpath(dpath, hide_init=0, hide_main=0) >>> res1 = normalize_modpath(dpath, hide_init=0, hide_main=1) >>> res2 = normalize_modpath(dpath, hide_init=1, hide_main=0) >>> res3 = normalize_modpath(dpath, hide_init=1, hide_main=1) >>> assert res0.endswith('__init__.py') >>> assert res1.endswith('__init__.py') >>> assert not res2.endswith('.py') >>> assert not res3.endswith('.py') """ if hide_init: if basename(modpath) == "__init__.py": modpath = dirname(modpath) hide_main = True else: modpath_with_init = join(modpath, "__init__.py") if exists(modpath_with_init): modpath = modpath_with_init if hide_main: if basename(modpath) == "__main__.py": parallel_init = join(dirname(modpath), "__init__.py") if exists(parallel_init): modpath = dirname(modpath) return modpath def modpath_to_modname( modpath, hide_init=True, hide_main=False, check=True, relativeto=None ): """ Determines importable name from file path Converts the path to a module (__file__) to the importable python name (__name__) without importing the module. The filename is converted to a module name, and parent directories are recursively included until a directory without an __init__.py file is encountered. Args: modpath (str): module filepath hide_init (bool, default=True): removes the __init__ suffix hide_main (bool, default=False): removes the __main__ suffix check (bool, default=True): if False, does not raise an error if modpath is a dir and does not contain an __init__ file. relativeto (str | None, default=None): if specified, all checks are ignored and this is considered the path to the root module. TODO: - [ ] Does this need modification to support PEP 420? https://www.python.org/dev/peps/pep-0420/ Returns: str: modname Raises: ValueError: if check is True and the path does not exist Example: >>> from xdoctest import static_analysis >>> modpath = static_analysis.__file__.replace('.pyc', '.py') >>> modpath = modpath.replace('.pyc', '.py') >>> modname = modpath_to_modname(modpath) >>> assert modname == 'xdoctest.static_analysis' Example: >>> import xdoctest >>> assert modpath_to_modname(xdoctest.__file__.replace('.pyc', '.py')) == 'xdoctest' >>> assert modpath_to_modname(dirname(xdoctest.__file__.replace('.pyc', '.py'))) == 'xdoctest' Example: >>> # xdoctest: +REQUIRES(CPython) >>> modpath = modname_to_modpath('_ctypes') >>> modname = modpath_to_modname(modpath) >>> assert modname == '_ctypes' Example: >>> modpath = '/foo/libfoobar.linux-x86_64-3.6.so' >>> modname = modpath_to_modname(modpath, check=False) >>> assert modname == 'libfoobar' """ if check and (relativeto is None): if not exists(modpath): raise ValueError("modpath={} does not exist".format(modpath)) modpath_ = abspath(expanduser(modpath)) modpath_ = normalize_modpath(modpath_, hide_init=hide_init, hide_main=hide_main) if relativeto: dpath = dirname(abspath(expanduser(relativeto))) rel_modpath = relpath(modpath_, dpath) else: (dpath, rel_modpath) = split_modpath(modpath_, check=check) modname = splitext(rel_modpath)[0] if "." in modname: (modname, abi_tag) = modname.split(".", 1) modname = modname.replace("/", ".") modname = modname.replace("\\", ".") return modname line_profiler-4.1.2/line_profiler/autoprofile/util_static.pyi000066400000000000000000000022011452103132100245630ustar00rootroot00000000000000from typing import List from os import PathLike from typing import Tuple from collections.abc import Generator from typing import Any def package_modpaths(pkgpath, with_pkg: bool = ..., with_mod: bool = ..., followlinks: bool = ..., recursive: bool = ..., with_libs: bool = ..., check: bool = ...) -> Generator[Any, None, None]: ... def modname_to_modpath( modname: str, hide_init: bool = True, hide_main: bool = False, sys_path: None | List[str | PathLike] = None) -> str | None: ... def split_modpath(modpath: str, check: bool = True) -> Tuple[str, str]: ... def normalize_modpath(modpath: str | PathLike, hide_init: bool = True, hide_main: bool = False) -> str | PathLike: ... def modpath_to_modname(modpath: str, hide_init: bool = True, hide_main: bool = False, check: bool = True, relativeto: str | None = None) -> str: ... line_profiler-4.1.2/line_profiler/explicit_profiler.py000066400000000000000000000314211452103132100232660ustar00rootroot00000000000000""" New in ``line_profiler`` version 4.1.0, this module defines a top-level ``profile`` decorator which will be disabled by default **unless** a script is being run with :mod:`kernprof`, if the environment variable ``LINE_PROFILE`` is set, or if ``--line-profile`` is given on the command line. In the latter two cases, the :mod:`atexit` module is used to display and dump line profiling results to disk when Python exits. If none of the enabling conditions are met, then :py:obj:`line_profiler.profile` is a noop. This means you no longer have to add and remove the implicit ``profile`` decorators required by previous version of this library. Basic usage is to import line_profiler and decorate your function with line_profiler.profile. By default this does nothing, it's a no-op decorator. However, if you run with the environment variable ``LINE_PROFILER=1`` or if ``'--profile' in sys.argv'``, then it enables profiling and at the end of your script it will output the profile text. Here is a minimal example that will write a script to disk and then run it with profiling enabled or disabled by various methods: .. code:: bash # Write demo python script to disk python -c "if 1: import textwrap text = textwrap.dedent( ''' from line_profiler import profile @profile def plus(a, b): return a + b @profile def fib(n): a, b = 0, 1 while a < n: a, b = b, plus(a, b) @profile def main(): import math import time start = time.time() print('start calculating') while time.time() - start < 1: fib(10) math.factorial(1000) print('done calculating') main() ''' ).strip() with open('demo.py', 'w') as file: file.write(text) " echo "---" echo "## Base Case: Run without any profiling" python demo.py echo "---" echo "## Option 0: Original Usage" python -m kernprof -l demo.py python -m line_profiler -rmt demo.py.lprof echo "---" echo "## Option 1: Enable profiler with the command line" python demo.py --line-profile echo "---" echo "## Option 1: Enable profiler with an environment variable" LINE_PROFILE=1 python demo.py The explicit :py:attr:`line_profiler.profile` decorator can also be enabled and configured in the Python code itself by calling :func:`line_profiler.profile.enable`. The following example demonstrates this: .. code:: bash # In-code enabling python -c "if 1: import textwrap text = textwrap.dedent( ''' from line_profiler import profile profile.enable(output_prefix='customized') @profile def fib(n): a, b = 0, 1 while a < n: a, b = b, a + b fib(100) ''' ).strip() with open('demo.py', 'w') as file: file.write(text) " echo "## Configuration handled inside the script" python demo.py Likewise there is a :func:`line_profiler.profile.disable` function that will prevent any subsequent functions decorated with ``@profile`` from being profiled. In the following example, profiling information will only be recorded for ``func2`` and ``func4``. .. code:: bash # In-code enabling / disable python -c "if 1: import textwrap text = textwrap.dedent( ''' from line_profiler import profile @profile def func1(): return list(range(100)) profile.enable(output_prefix='custom') @profile def func2(): return tuple(range(100)) profile.disable() @profile def func3(): return set(range(100)) profile.enable() @profile def func4(): return dict(zip(range(100), range(100))) print(type(func1())) print(type(func2())) print(type(func3())) print(type(func4())) ''' ).strip() with open('demo.py', 'w') as file: file.write(text) " echo "---" echo "## Configuration handled inside the script" python demo.py # Running with --line-profile will also profile ``func1`` python demo.py --line-profile The core functionality in this module was ported from :mod:`xdev`. """ from .line_profiler import LineProfiler import sys import os import atexit _FALSY_STRINGS = {'', '0', 'off', 'false', 'no'} class GlobalProfiler: """ Manages a profiler that will output on interpreter exit. The :py:obj:`line_profile.profile` decorator is an instance of this object. Attributes: setup_config (Dict[str, List[str]]): Determines how the implicit setup behaves by defining which environment variables / command line flags to look for. output_prefix (str): The prefix of any output files written. Should include a part of a filename. Defaults to "profile_output". write_config (Dict[str, bool]): Which outputs are enabled. All default to True. Options are lprof, text, timestamped_text, and stdout. show_config (Dict[str, bool]): Display configuration options. Some outputs force certain options. (e.g. text always has details and is never rich). enabled (bool | None): True if the profiler is enabled (i.e. if it will wrap a function that it decorates with a real profiler). If None, then the value defaults based on the ``setup_config``, :py:obj:`os.environ`, and :py:obj:`sys.argv`. Example: >>> from line_profiler.explicit_profiler import * # NOQA >>> self = GlobalProfiler() >>> # Setting the _profile attribute prevents atexit from running. >>> self._profile = LineProfiler() >>> # User can personalize the configuration >>> self.show_config['details'] = True >>> self.write_config['lprof'] = False >>> self.write_config['text'] = False >>> self.write_config['timestamped_text'] = False >>> # Demo data: a function to profile >>> def collatz(n): >>> while n != 1: >>> if n % 2 == 0: >>> n = n // 2 >>> else: >>> n = 3 * n + 1 >>> return n >>> # Disabled by default, implicitly checks to auto-enable on first wrap >>> assert self.enabled is None >>> wrapped = self(collatz) >>> assert self.enabled is False >>> assert wrapped is collatz >>> # Can explicitly enable >>> self.enable() >>> wrapped = self(collatz) >>> assert self.enabled is True >>> assert wrapped is not collatz >>> wrapped(100) >>> # Can explicitly request output >>> self.show() """ def __init__(self): self.setup_config = { 'environ_flags': ['LINE_PROFILE'], 'cli_flags': ['--line-profile', '--line_profile'], } self.output_prefix = 'profile_output' self._profile = None self.enabled = None # Control which outputs will be written on exit self.write_config = { 'lprof': True, 'text': True, 'timestamped_text': True, 'stdout': True, } # Configuration for how output will be displayed self.show_config = { 'sort': 1, 'stripzeros': 1, 'rich': 1, 'details': 0, 'summarize': 1, } def _kernprof_overwrite(self, profile): """ Kernprof will call this when it runs, so we can use its profile object instead of our own. Note: when kernprof overwrites us we wont register an atexit hook. This is what we want because kernprof wants us to use another program to read its output file. """ self._profile = profile self.enabled = True def _implicit_setup(self): """ Called once the first time the user decorates a function with ``line_profiler.profile`` and they have not explicitly setup the global profiling options. """ environ_flags = self.setup_config['environ_flags'] cli_flags = self.setup_config['cli_flags'] is_profiling = any(os.environ.get(f, '').lower() not in _FALSY_STRINGS for f in environ_flags) is_profiling |= any(f in sys.argv for f in cli_flags) if is_profiling: self.enable() else: self.disable() def enable(self, output_prefix=None): """ Explicitly enables global profiler and controls its settings. """ if self._profile is None: # Try to only ever create one real LineProfiler object atexit.register(self.show) self._profile = LineProfiler() # type: ignore # The user can call this function more than once to update the final # reporting or to re-enable the profiler after it a disable. self.enabled = True if output_prefix is not None: self.output_prefix = output_prefix def disable(self): """ Explicitly initialize and disable this global profiler. """ self.enabled = False def __call__(self, func): """ If the global profiler is enabled, decorate a function to start the profiler on function entry and stop it on function exit. Otherwise return the input. Args: func (Callable): the function to profile Returns: Callable: a potentially wrapped function """ # from multiprocessing import current_process # if current_process().name != 'MainProcess': # return func if self.enabled is None: # Force a setup if we haven't done it before. self._implicit_setup() if not self.enabled: return func return self._profile(func) def show(self): """ Write the managed profiler stats to enabled outputs. If the implicit setup triggered, then this will be called by :py:mod:`atexit`. """ import io import pathlib srite_stdout = self.write_config['stdout'] write_text = self.write_config['text'] write_timestamped_text = self.write_config['timestamped_text'] write_lprof = self.write_config['lprof'] if srite_stdout: kwargs = self.show_config.copy() self._profile.print_stats(**kwargs) if write_text or write_timestamped_text: stream = io.StringIO() # Text output always contains details, and cannot be rich. text_kwargs = self.show_config.copy() text_kwargs['rich'] = 0 text_kwargs['details'] = 1 self._profile.print_stats(stream=stream, **text_kwargs) raw_text = stream.getvalue() if write_text: txt_output_fpath1 = pathlib.Path(f'{self.output_prefix}.txt') txt_output_fpath1.write_text(raw_text) print('Wrote profile results to %s' % txt_output_fpath1) if write_timestamped_text: from datetime import datetime as datetime_cls now = datetime_cls.now() timestamp = now.strftime('%Y-%m-%dT%H%M%S') txt_output_fpath2 = pathlib.Path(f'{self.output_prefix}_{timestamp}.txt') txt_output_fpath2.write_text(raw_text) print('Wrote profile results to %s' % txt_output_fpath2) if write_lprof: lprof_output_fpath = pathlib.Path(f'{self.output_prefix}.lprof') self._profile.dump_stats(lprof_output_fpath) print('Wrote profile results to %s' % lprof_output_fpath) print('To view details run:') py_exe = _python_command() print(py_exe + ' -m line_profiler -rtmz ' + str(lprof_output_fpath)) def _python_command(): """ Return a command that corresponds to :py:obj:`sys.executable`. """ import shutil if shutil.which('python') == sys.executable: return 'python' elif shutil.which('python3') == sys.executable: return 'python3' else: return sys.executable # Construct the global profiler. # The first time it is called, it will be initialized. This is usually a # NoOpProfiler unless the user requested the real one. # NOTE: kernprof or the user may explicitly setup the global profiler. profile = GlobalProfiler() line_profiler-4.1.2/line_profiler/explicit_profiler.pyi000066400000000000000000000011131452103132100234320ustar00rootroot00000000000000from typing import Dict from typing import List from typing import Callable from _typeshed import Incomplete class GlobalProfiler: setup_config: Dict[str, List[str]] output_prefix: str write_config: Dict[str, bool] show_config: Dict[str, bool] enabled: bool | None def __init__(self) -> None: ... def enable(self, output_prefix: Incomplete | None = ...) -> None: ... def disable(self) -> None: ... def __call__(self, func: Callable) -> Callable: ... def show(self) -> None: ... profile: Incomplete line_profiler-4.1.2/line_profiler/ipython_extension.py000066400000000000000000000130411452103132100233270ustar00rootroot00000000000000""" This module defines the ``%lprun`` IPython magic. If you are using IPython, there is an implementation of an %lprun magic command which will let you specify functions to profile and a statement to execute. It will also add its LineProfiler instance into the __builtins__, but typically, you would not use it like that. For IPython 0.11+, you can install it by editing the IPython configuration file ``~/.ipython/profile_default/ipython_config.py`` to add the ``'line_profiler'`` item to the extensions list:: c.TerminalIPythonApp.extensions = [ 'line_profiler', ] Or explicitly call:: %load_ext line_profiler To get usage help for %lprun, use the standard IPython help mechanism:: In [1]: %lprun? """ from io import StringIO from IPython.core.magic import Magics, magics_class, line_magic from IPython.core.page import page from IPython.utils.ipstruct import Struct from IPython.core.error import UsageError from .line_profiler import LineProfiler @magics_class class LineProfilerMagics(Magics): @line_magic def lprun(self, parameter_s=""): """ Execute a statement under the line-by-line profiler from the line_profiler module. Usage: %lprun -f func1 -f func2 The given statement (which doesn't require quote marks) is run via the LineProfiler. Profiling is enabled for the functions specified by the -f options. The statistics will be shown side-by-side with the code through the pager once the statement has completed. Options: -f : LineProfiler only profiles functions and methods it is told to profile. This option tells the profiler about these functions. Multiple -f options may be used. The argument may be any expression that gives a Python function or method object. However, one must be careful to avoid spaces that may confuse the option parser. -m : Get all the functions/methods in a module One or more -f or -m options are required to get any useful results. -D : dump the raw statistics out to a pickle file on disk. The usual extension for this is ".lprof". These statistics may be viewed later by running line_profiler.py as a script. -T : dump the text-formatted statistics with the code side-by-side out to a text file. -r: return the LineProfiler object after it has completed profiling. -s: strip out all entries from the print-out that have zeros. -u: specify time unit for the print-out in seconds. """ # Escape quote markers. opts_def = Struct(D=[""], T=[""], f=[], m=[], u=None) parameter_s = parameter_s.replace('"', r"\"").replace("'", r"\'") opts, arg_str = self.parse_options(parameter_s, "rsf:m:D:T:u:", list_all=True) opts.merge(opts_def) global_ns = self.shell.user_global_ns local_ns = self.shell.user_ns # Get the requested functions. funcs = [] for name in opts.f: try: funcs.append(eval(name, global_ns, local_ns)) except Exception as e: raise UsageError( f"Could not find module {name}.\n{e.__class__.__name__}: {e}" ) profile = LineProfiler(*funcs) # Get the modules, too for modname in opts.m: try: mod = __import__(modname, fromlist=[""]) profile.add_module(mod) except Exception as e: raise UsageError( f"Could not find module {modname}.\n{e.__class__.__name__}: {e}" ) if opts.u is not None: try: output_unit = float(opts.u[0]) except Exception: raise TypeError("Timer unit setting must be a float.") else: output_unit = None # Add the profiler to the builtins for @profile. import builtins if "profile" in builtins.__dict__: had_profile = True old_profile = builtins.__dict__["profile"] else: had_profile = False old_profile = None builtins.__dict__["profile"] = profile try: try: profile.runctx(arg_str, global_ns, local_ns) message = "" except SystemExit: message = """*** SystemExit exception caught in code being profiled.""" except KeyboardInterrupt: message = ( "*** KeyboardInterrupt exception caught in code being " "profiled." ) finally: if had_profile: builtins.__dict__["profile"] = old_profile # Trap text output. stdout_trap = StringIO() profile.print_stats( stdout_trap, output_unit=output_unit, stripzeros="s" in opts ) output = stdout_trap.getvalue() output = output.rstrip() page(output) print(message, end="") dump_file = opts.D[0] if dump_file: profile.dump_stats(dump_file) print(f"\n*** Profile stats pickled to file {dump_file!r}. {message}") text_file = opts.T[0] if text_file: pfile = open(text_file, "w") pfile.write(output) pfile.close() print(f"\n*** Profile printout saved to text file {text_file!r}. {message}") return_value = None if "r" in opts: return_value = profile return return_value line_profiler-4.1.2/line_profiler/ipython_extension.pyi000066400000000000000000000002041452103132100234750ustar00rootroot00000000000000from IPython.core.magic import Magics class LineProfilerMagics(Magics): def lprun(self, parameter_s: str = ...): ... line_profiler-4.1.2/line_profiler/line_profiler.py000077500000000000000000000422541452103132100224050ustar00rootroot00000000000000#!/usr/bin/env python """ This module defines the core :class:`LineProfiler` class as well as methods to inspect its output. This depends on the :py:mod:`line_profiler._line_profiler` Cython backend. """ import pickle import functools import inspect import linecache import tempfile import os import sys from argparse import ArgumentError, ArgumentParser try: from ._line_profiler import LineProfiler as CLineProfiler except ImportError as ex: raise ImportError( 'The line_profiler._line_profiler c-extension is not importable. ' f'Has it been compiled? Underlying error is ex={ex!r}' ) # NOTE: This needs to be in sync with ../kernprof.py and __init__.py __version__ = '4.1.2' def load_ipython_extension(ip): """ API for IPython to recognize this module as an IPython extension. """ from .ipython_extension import LineProfilerMagics ip.register_magics(LineProfilerMagics) def is_coroutine(f): return inspect.iscoroutinefunction(f) CO_GENERATOR = 0x0020 def is_generator(f): """ Return True if a function is a generator. """ isgen = (f.__code__.co_flags & CO_GENERATOR) != 0 return isgen def is_classmethod(f): return isinstance(f, classmethod) class LineProfiler(CLineProfiler): """ A profiler that records the execution times of individual lines. This provides the core line-profiler functionality. Example: >>> import line_profiler >>> profile = line_profiler.LineProfiler() >>> @profile >>> def func(): >>> x1 = list(range(10)) >>> x2 = list(range(100)) >>> x3 = list(range(1000)) >>> func() >>> profile.print_stats() """ def __call__(self, func): """ Decorate a function to start the profiler on function entry and stop it on function exit. """ self.add_function(func) if is_classmethod(func): wrapper = self.wrap_classmethod(func) elif is_coroutine(func): wrapper = self.wrap_coroutine(func) elif is_generator(func): wrapper = self.wrap_generator(func) else: wrapper = self.wrap_function(func) return wrapper def wrap_classmethod(self, func): """ Wrap a classmethod to profile it. """ @functools.wraps(func) def wrapper(*args, **kwds): self.enable_by_count() try: result = func.__func__(func.__class__, *args, **kwds) finally: self.disable_by_count() return result return wrapper def wrap_coroutine(self, func): """ Wrap a Python 3.5 coroutine to profile it. """ @functools.wraps(func) async def wrapper(*args, **kwds): self.enable_by_count() try: result = await func(*args, **kwds) finally: self.disable_by_count() return result return wrapper def wrap_generator(self, func): """ Wrap a generator to profile it. """ @functools.wraps(func) def wrapper(*args, **kwds): g = func(*args, **kwds) # The first iterate will not be a .send() self.enable_by_count() try: item = next(g) except StopIteration: return finally: self.disable_by_count() input_ = (yield item) # But any following one might be. while True: self.enable_by_count() try: item = g.send(input_) except StopIteration: return finally: self.disable_by_count() input_ = (yield item) return wrapper def wrap_function(self, func): """ Wrap a function to profile it. """ @functools.wraps(func) def wrapper(*args, **kwds): self.enable_by_count() try: result = func(*args, **kwds) finally: self.disable_by_count() return result return wrapper def dump_stats(self, filename): """ Dump a representation of the data to a file as a pickled LineStats object from `get_stats()`. """ lstats = self.get_stats() with open(filename, 'wb') as f: pickle.dump(lstats, f, pickle.HIGHEST_PROTOCOL) def print_stats(self, stream=None, output_unit=None, stripzeros=False, details=True, summarize=False, sort=False, rich=False): """ Show the gathered statistics. """ lstats = self.get_stats() show_text(lstats.timings, lstats.unit, output_unit=output_unit, stream=stream, stripzeros=stripzeros, details=details, summarize=summarize, sort=sort, rich=rich) def run(self, cmd): """ Profile a single executable statment in the main namespace. """ import __main__ main_dict = __main__.__dict__ return self.runctx(cmd, main_dict, main_dict) def runctx(self, cmd, globals, locals): """ Profile a single executable statement in the given namespaces. """ self.enable_by_count() try: exec(cmd, globals, locals) finally: self.disable_by_count() return self def runcall(self, func, *args, **kw): """ Profile a single function call. """ self.enable_by_count() try: return func(*args, **kw) finally: self.disable_by_count() def add_module(self, mod): """ Add all the functions in a module and its classes. """ from inspect import isclass, isfunction nfuncsadded = 0 for item in mod.__dict__.values(): if isclass(item): for k, v in item.__dict__.items(): if isfunction(v): self.add_function(v) nfuncsadded += 1 elif isfunction(item): self.add_function(item) nfuncsadded += 1 return nfuncsadded # This could be in the ipython_extension submodule, # but it doesn't depend on the IPython module so it's easier to just let it stay here. def is_ipython_kernel_cell(filename): """ Return True if a filename corresponds to a Jupyter Notebook cell """ return ( filename.startswith('>> from line_profiler.line_profiler import show_func >>> import line_profiler >>> # Use a function in this file as an example >>> func = line_profiler.line_profiler.show_text >>> start_lineno = func.__code__.co_firstlineno >>> filename = func.__code__.co_filename >>> func_name = func.__name__ >>> # Build fake timeings for each line in the example function >>> import inspect >>> num_lines = len(inspect.getsourcelines(func)[0]) >>> line_numbers = list(range(start_lineno + 3, start_lineno + num_lines)) >>> timings = [ >>> (lineno, idx * 1e13, idx * (2e10 ** (idx % 3))) >>> for idx, lineno in enumerate(line_numbers, start=1) >>> ] >>> unit = 1.0 >>> output_unit = 1.0 >>> stream = None >>> stripzeros = False >>> rich = 1 >>> show_func(filename, start_lineno, func_name, timings, unit, >>> output_unit, stream, stripzeros, rich) """ if stream is None: stream = sys.stdout total_hits = sum(t[1] for t in timings) total_time = sum(t[2] for t in timings) if stripzeros and total_hits == 0: return if rich: # References: # https://github.com/Textualize/rich/discussions/3076 try: from rich.syntax import Syntax from rich.highlighter import ReprHighlighter from rich.text import Text from rich.console import Console from rich.table import Table except ImportError: rich = 0 if output_unit is None: output_unit = unit scalar = unit / output_unit linenos = [t[0] for t in timings] stream.write('Total time: %g s\n' % (total_time * unit)) if os.path.exists(filename) or is_ipython_kernel_cell(filename): stream.write(f'File: {filename}\n') stream.write(f'Function: {func_name} at line {start_lineno}\n') if os.path.exists(filename): # Clear the cache to ensure that we get up-to-date results. linecache.clearcache() all_lines = linecache.getlines(filename) sublines = inspect.getblock(all_lines[start_lineno - 1:]) else: stream.write('\n') stream.write(f'Could not find file {filename}\n') stream.write('Are you sure you are running this program from the same directory\n') stream.write('that you ran the profiler from?\n') stream.write("Continuing without the function's contents.\n") # Fake empty lines so we can see the timings, if not the code. nlines = 1 if not linenos else max(linenos) - min(min(linenos), start_lineno) + 1 sublines = [''] * nlines # Define minimum column sizes so text fits and usually looks consistent default_column_sizes = { 'line': 6, 'hits': 9, 'time': 12, 'perhit': 8, 'percent': 8, } display = {} # Loop over each line to determine better column formatting. # Fallback to scientific notation if columns are larger than a threshold. for lineno, nhits, time in timings: if total_time == 0: # Happens rarely on empty function percent = '' else: percent = '%5.1f' % (100 * time / total_time) time_disp = '%5.1f' % (time * scalar) if len(time_disp) > default_column_sizes['time']: time_disp = '%5.1g' % (time * scalar) perhit_disp = '%5.1f' % (float(time) * scalar / nhits) if len(perhit_disp) > default_column_sizes['perhit']: perhit_disp = '%5.1g' % (float(time) * scalar / nhits) nhits_disp = "%d" % nhits if len(nhits_disp) > default_column_sizes['hits']: nhits_disp = '%g' % nhits display[lineno] = (nhits_disp, time_disp, perhit_disp, percent) # Expand column sizes if the numbers are large. column_sizes = default_column_sizes.copy() if len(display): max_hitlen = max(len(t[0]) for t in display.values()) max_timelen = max(len(t[1]) for t in display.values()) max_perhitlen = max(len(t[2]) for t in display.values()) column_sizes['hits'] = max(column_sizes['hits'], max_hitlen) column_sizes['time'] = max(column_sizes['time'], max_timelen) column_sizes['perhit'] = max(column_sizes['perhit'], max_perhitlen) col_order = ['line', 'hits', 'time', 'perhit', 'percent'] lhs_template = ' '.join(['%' + str(column_sizes[k]) + 's' for k in col_order]) template = lhs_template + ' %-s' linenos = range(start_lineno, start_lineno + len(sublines)) empty = ('', '', '', '') header = ('Line #', 'Hits', 'Time', 'Per Hit', '% Time', 'Line Contents') header = template % header stream.write('\n') stream.write(header) stream.write('\n') stream.write('=' * len(header)) stream.write('\n') if rich: # Build the RHS and LHS of the table separately lhs_lines = [] rhs_lines = [] for lineno, line in zip(linenos, sublines): nhits, time, per_hit, percent = display.get(lineno, empty) txt = lhs_template % (lineno, nhits, time, per_hit, percent) rhs_lines.append(line.rstrip('\n').rstrip('\r')) lhs_lines.append(txt) rhs_text = '\n'.join(rhs_lines) lhs_text = '\n'.join(lhs_lines) # Highlight the RHS with Python syntax rhs = Syntax(rhs_text, 'python', background_color='default') # Use default highlights for the LHS # TODO: could use colors to draw the eye to longer running lines. lhs = Text(lhs_text) ReprHighlighter().highlight(lhs) # Use a table to horizontally concatenate the text # reference: https://github.com/Textualize/rich/discussions/3076 table = Table( box=None, padding=0, collapse_padding=True, show_header=False, show_footer=False, show_edge=False, pad_edge=False, expand=False, ) table.add_row(lhs, ' ', rhs) # Use a Console to render to the stream # Not sure if we should force-terminal or just specify the color system # write_console = Console(file=stream, force_terminal=True, soft_wrap=True) write_console = Console(file=stream, soft_wrap=True, color_system='standard') write_console.print(table) stream.write('\n') else: for lineno, line in zip(linenos, sublines): nhits, time, per_hit, percent = display.get(lineno, empty) txt = template % (lineno, nhits, time, per_hit, percent, line.rstrip('\n').rstrip('\r')) stream.write(txt) stream.write('\n') stream.write('\n') def show_text(stats, unit, output_unit=None, stream=None, stripzeros=False, details=True, summarize=False, sort=False, rich=False): """ Show text for the given timings. """ if stream is None: stream = sys.stdout if output_unit is not None: stream.write('Timer unit: %g s\n\n' % output_unit) else: stream.write('Timer unit: %g s\n\n' % unit) if sort: # Order by ascending duration stats_order = sorted(stats.items(), key=lambda kv: sum(t[2] for t in kv[1])) else: # Default ordering stats_order = sorted(stats.items()) if details: # Show detailed per-line information for each function. for (fn, lineno, name), timings in stats_order: show_func(fn, lineno, name, stats[fn, lineno, name], unit, output_unit=output_unit, stream=stream, stripzeros=stripzeros, rich=rich) if summarize: # Summarize the total time for each function for (fn, lineno, name), timings in stats_order: total_time = sum(t[2] for t in timings) * unit line = '%6.2f seconds - %s:%s - %s\n' % (total_time, fn, lineno, name) stream.write(line) def load_stats(filename): """ Utility function to load a pickled LineStats object from a given filename. """ with open(filename, 'rb') as f: return pickle.load(f) def main(): """ The line profiler CLI to view output from ``kernprof -l``. """ def positive_float(value): val = float(value) if val <= 0: raise ArgumentError return val parser = ArgumentParser() parser.add_argument('-V', '--version', action='version', version=__version__) parser.add_argument( '-u', '--unit', default='1e-6', type=positive_float, help='Output unit (in seconds) in which the timing info is displayed (default: 1e-6)', ) parser.add_argument( '-z', '--skip-zero', action='store_true', help='Hide functions which have not been called', ) parser.add_argument( '-r', '--rich', action='store_true', help='Use rich formatting', ) parser.add_argument( '-t', '--sort', action='store_true', help='Sort by ascending total time', ) parser.add_argument( '-m', '--summarize', action='store_true', help='Print a summary of total function time', ) parser.add_argument('profile_output', help='*.lprof file created by kernprof') args = parser.parse_args() lstats = load_stats(args.profile_output) show_text( lstats.timings, lstats.unit, output_unit=args.unit, stripzeros=args.skip_zero, rich=args.rich, sort=args.sort, summarize=args.summarize, ) if __name__ == '__main__': main() line_profiler-4.1.2/line_profiler/line_profiler.pyi000066400000000000000000000037201452103132100225460ustar00rootroot00000000000000from typing import List from typing import Tuple import io from ._line_profiler import LineProfiler as CLineProfiler from _typeshed import Incomplete def load_ipython_extension(ip) -> None: ... def is_coroutine(f): ... CO_GENERATOR: int def is_generator(f): ... def is_classmethod(f): ... class LineProfiler(CLineProfiler): def __call__(self, func): ... def wrap_classmethod(self, func): ... def wrap_coroutine(self, func): ... def wrap_generator(self, func): ... def wrap_function(self, func): ... def dump_stats(self, filename) -> None: ... def print_stats(self, stream: Incomplete | None = ..., output_unit: Incomplete | None = ..., stripzeros: bool = ..., details: bool = ..., summarize: bool = ..., sort: bool = ..., rich: bool = ...) -> None: ... def run(self, cmd): ... def runctx(self, cmd, globals, locals): ... def runcall(self, func, *args, **kw): ... def add_module(self, mod): ... def is_ipython_kernel_cell(filename): ... def show_func(filename: str, start_lineno: int, func_name: str, timings: List[Tuple[int, int, float]], unit: float, output_unit: float | None = None, stream: io.TextIOBase | None = None, stripzeros: bool = False, rich: bool = False) -> None: ... def show_text(stats, unit, output_unit: Incomplete | None = ..., stream: Incomplete | None = ..., stripzeros: bool = ..., details: bool = ..., summarize: bool = ..., sort: bool = ..., rich: bool = ...): ... def load_stats(filename): ... def main(): ... line_profiler-4.1.2/line_profiler/py.typed000066400000000000000000000000001452103132100206550ustar00rootroot00000000000000line_profiler-4.1.2/line_profiler/python25.pxd000066400000000000000000001172331452103132100214040ustar00rootroot00000000000000# From: Eric Huss # # Here is my latest copy. It does not cover 100% of the API. It should be # current up to 2.5. # # -Eric # XXX: # - Need to support "long long" definitions that are different for different platforms. # - Support unicode platform dependencies. # - Add unicode calls. # - Add setobject calls. cdef extern from "sys/types.h": ctypedef unsigned int size_t cdef extern from "stdio.h": ctypedef struct FILE: pass cdef extern from "Python.h": # XXX: This is platform dependent. ctypedef unsigned short Py_UNICODE ctypedef struct PyTypeObject: pass ctypedef struct PyObject: Py_ssize_t ob_refcnt PyTypeObject * ob_type ############################################################################################### # bool ############################################################################################### PyObject * Py_False PyObject * Py_True PyTypeObject PyBool_Type int PyBool_Check (object) # Always succeeds. object PyBool_FromLong (long) ############################################################################################### # buffer ############################################################################################### PyTypeObject PyBuffer_Type int Py_END_OF_BUFFER int PyBuffer_Check (object) # Always succeeds. object PyBuffer_FromMemory (void *, Py_ssize_t) object PyBuffer_FromObject (object, Py_ssize_t, Py_ssize_t) object PyBuffer_FromReadWriteMemory (void *, Py_ssize_t) object PyBuffer_FromReadWriteObject (object, Py_ssize_t, Py_ssize_t) object PyBuffer_New (Py_ssize_t) int PyObject_AsCharBuffer (object, char **, Py_ssize_t *) except -1 int PyObject_AsReadBuffer (object, void **, Py_ssize_t *) except -1 int PyObject_AsWriteBuffer (object, void **, Py_ssize_t *) except -1 int PyObject_CheckReadBuffer (object) # Always succeeds. ############################################################################################### # cobject ############################################################################################### PyTypeObject PyCObject_Type int PyCObject_Check(object) # Always succeeds. object PyCObject_FromVoidPtr(void *, void (*)(void*)) object PyCObject_FromVoidPtrAndDesc(void *, void *, void (*)(void*,void*)) void * PyCObject_AsVoidPtr(object) except NULL void * PyCObject_GetDesc(object) except NULL void * PyCObject_Import(char *, char *) except NULL ############################################################################################### # compile ############################################################################################### ctypedef struct PyCodeObject: int co_argcount int co_nlocals int co_stacksize int co_flags PyObject *co_code PyObject *co_consts PyObject *co_names PyObject *co_varnames PyObject *co_freevars PyObject *co_cellvars PyObject *co_filename PyObject *co_name int co_firstlineno PyObject *co_lnotab int PyCode_Addr2Line(PyCodeObject *, int) ############################################################################################### # complex ############################################################################################### ctypedef struct Py_complex: double real double imag PyTypeObject PyComplex_Type Py_complex PyComplex_AsCComplex (object) # Always succeeds. int PyComplex_Check (object) # Always succeeds. int PyComplex_CheckExact (object) # Always succeeds. object PyComplex_FromCComplex (Py_complex) object PyComplex_FromDoubles (double, double) double PyComplex_ImagAsDouble (object) except? -1 double PyComplex_RealAsDouble (object) except? -1 Py_complex _Py_c_diff (Py_complex, Py_complex) Py_complex _Py_c_neg (Py_complex) Py_complex _Py_c_pow (Py_complex, Py_complex) Py_complex _Py_c_prod (Py_complex, Py_complex) Py_complex _Py_c_quot (Py_complex, Py_complex) Py_complex _Py_c_sum (Py_complex, Py_complex) ############################################################################################### # dict ############################################################################################### PyTypeObject PyDict_Type int PyDict_Check (object) # Always succeeds. int PyDict_CheckExact (object) # Always succeeds. void PyDict_Clear (object) int PyDict_Contains (object, object) except -1 object PyDict_Copy (object) int PyDict_DelItem (object, object) except -1 int PyDict_DelItemString (object, char *) except -1 object PyDict_Items (object) object PyDict_Keys (object) int PyDict_Merge (object, object, int) except -1 int PyDict_MergeFromSeq2 (object, object, int) except -1 object PyDict_New () # XXX: Pyrex doesn't support pointer to a python object? #int PyDict_Next (object, Py_ssize_t *, object *, object *) # Always succeeds. int PyDict_SetItem (object, object, object) except -1 int PyDict_SetItemString (object, char *, object) except -1 Py_ssize_t PyDict_Size (object) except -1 int PyDict_Update (object, object) except -1 object PyDict_Values (object) # XXX: Borrowed reference. No exception on NULL. #object PyDict_GetItem (object, object) # XXX: Borrowed reference. No exception on NULL #object PyDict_GetItemString (object, char *) ############################################################################################### # float ############################################################################################### PyTypeObject PyFloat_Type int _PyFloat_Pack4 (double, unsigned char *, int) except -1 int _PyFloat_Pack8 (double, unsigned char *, int) except -1 double _PyFloat_Unpack4 (unsigned char *, int) except? -1 double _PyFloat_Unpack8 (unsigned char *, int) except? -1 double PyFloat_AS_DOUBLE (object) double PyFloat_AsDouble (object) except? -1 void PyFloat_AsReprString (char*, object) void PyFloat_AsString (char*, object) int PyFloat_Check (object) # Always succeeds. int PyFloat_CheckExact (object) # Always succeeds. object PyFloat_FromDouble (double) object PyFloat_FromString (object, char**) ############################################################################################### # frame ############################################################################################### ctypedef struct PyFrameObject: PyFrameObject *f_back PyCodeObject *f_code PyObject *f_builtins PyObject *f_globals PyObject *f_locals PyObject *f_trace PyObject *f_exc_type PyObject *f_exc_value PyObject *f_exc_traceback int f_lasti int f_lineno int f_restricted int f_iblock int f_nlocals int f_ncells int f_nfreevars int f_stacksize ############################################################################################### # int ############################################################################################### PyTypeObject PyInt_Type long PyInt_AS_LONG (object) # Always succeeds. long PyInt_AsLong (object) except? -1 Py_ssize_t PyInt_AsSsize_t (object) except? -1 unsigned long long PyInt_AsUnsignedLongLongMask (object) except? -1 unsigned long PyInt_AsUnsignedLongMask (object) except? -1 int PyInt_Check (object) # Always succeeds. int PyInt_CheckExact (object) # Always succeeds. object PyInt_FromLong (long) object PyInt_FromSsize_t (Py_ssize_t) object PyInt_FromString (char*, char**, int) object PyInt_FromUnicode (Py_UNICODE*, Py_ssize_t, int) long PyInt_GetMax () # Always succeeds. ############################################################################################### # iterator ############################################################################################### int PyIter_Check (object) # Always succeeds. object PyIter_Next (object) ############################################################################################### # list ############################################################################################### PyTypeObject PyList_Type int PyList_Append (object, object) except -1 object PyList_AsTuple (object) int PyList_Check (object) # Always succeeds. int PyList_CheckExact (object) # Always succeeds. int PyList_GET_SIZE (object) # Always suceeds. object PyList_GetSlice (object, Py_ssize_t, Py_ssize_t) int PyList_Insert (object, Py_ssize_t, object) except -1 object PyList_New (Py_ssize_t) int PyList_Reverse (object) except -1 int PyList_SetSlice (object, Py_ssize_t, Py_ssize_t, object) except -1 Py_ssize_t PyList_Size (object) except -1 int PyList_Sort (object) except -1 ############################################################################################### # long ############################################################################################### PyTypeObject PyLong_Type int _PyLong_AsByteArray (object, unsigned char *, size_t, int, int) except -1 object _PyLong_FromByteArray (unsigned char *, size_t, int, int) size_t _PyLong_NumBits (object) except -1 int _PyLong_Sign (object) # No error. long PyLong_AsLong (object) except? -1 long long PyLong_AsLongLong (object) except? -1 unsigned long PyLong_AsUnsignedLong (object) except? -1 unsigned long PyLong_AsUnsignedLongMask (object) except? -1 unsigned long long PyLong_AsUnsignedLongLong (object) except? -1 unsigned long long PyLong_AsUnsignedLongLongMask (object) except? -1 int PyLong_Check (object) # Always succeeds. int PyLong_CheckExact (object) # Always succeeds. object PyLong_FromDouble (double) object PyLong_FromLong (long) object PyLong_FromLongLong (long long) object PyLong_FromUnsignedLong (unsigned long) object PyLong_FromUnsignedLongLong (unsigned long long) double PyLong_AsDouble (object) except? -1 object PyLong_FromVoidPtr (void *) void * PyLong_AsVoidPtr (object) except NULL object PyLong_FromString (char *, char **, int) object PyLong_FromUnicode (Py_UNICODE*, Py_ssize_t, int) ############################################################################################### # mapping ############################################################################################### int PyMapping_Check (object) # Always succeeds. int PyMapping_DelItem (object, object) except -1 int PyMapping_DelItemString (object, char *) except -1 object PyMapping_GetItemString (object, char *) int PyMapping_HasKey (object, object) # Always succeeds. int PyMapping_HasKeyString (object, char *) # Always succeeds. object PyMapping_Items (object) object PyMapping_Keys (object) Py_ssize_t PyMapping_Length (object) except -1 int PyMapping_SetItemString (object, char *, object) except -1 Py_ssize_t PyMapping_Size (object) except -1 object PyMapping_Values (object) ############################################################################################### # mem ############################################################################################### void PyMem_Free (void * p) void * PyMem_Malloc (size_t n) void * PyMem_Realloc (void *, size_t) ############################################################################################### # modsupport ############################################################################################### object Py_BuildValue (char *, ...) object Py_VaBuildValue (char *, va_list) ############################################################################################### # number ############################################################################################### object PyNumber_Absolute (object) object PyNumber_Add (object, object) object PyNumber_And (object, object) Py_ssize_t PyNumber_AsSsize_t (object, object) except? -1 int PyNumber_Check (object) # Always succeeds. # XXX: Pyrex doesn't support pointer to python object? #int PyNumber_Coerce (object*, object*) except -1 object PyNumber_Divide (object, object) object PyNumber_Divmod (object, object) object PyNumber_Float (object) object PyNumber_FloorDivide (object, object) object PyNumber_InPlaceAdd (object, object) object PyNumber_InPlaceAnd (object, object) object PyNumber_InPlaceDivide (object, object) object PyNumber_InPlaceFloorDivide (object, object) object PyNumber_InPlaceLshift (object, object) object PyNumber_InPlaceMultiply (object, object) object PyNumber_InPlaceOr (object, object) object PyNumber_InPlacePower (object, object, object) object PyNumber_InPlaceRemainder (object, object) object PyNumber_InPlaceRshift (object, object) object PyNumber_InPlaceSubtract (object, object) object PyNumber_InPlaceTrueDivide (object, object) object PyNumber_InPlaceXor (object, object) object PyNumber_Int (object) object PyNumber_Invert (object) object PyNumber_Long (object) object PyNumber_Lshift (object, object) object PyNumber_Multiply (object, object) object PyNumber_Negative (object) object PyNumber_Or (object, object) object PyNumber_Positive (object) object PyNumber_Power (object, object, object) object PyNumber_Remainder (object, object) object PyNumber_Rshift (object, object) object PyNumber_Subtract (object, object) object PyNumber_TrueDivide (object, object) object PyNumber_Xor (object, object) ############################################################################################### # object ############################################################################################### int PyCallable_Check (object) # Always succeeds. int PyObject_AsFileDescriptor (object) except -1 object PyObject_Call (object, object, object) object PyObject_CallFunction (object, char *, ...) object PyObject_CallFunctionObjArgs (object, ...) object PyObject_CallMethod (object, char *, char *, ...) object PyObject_CallMethodObjArgs (object, object, ...) object PyObject_CallObject (object, object) int PyObject_Cmp (object, object, int *result) except -1 # Use PyObject_Cmp instead. #int PyObject_Compare (object, object) int PyObject_DelAttr (object, object) except -1 int PyObject_DelAttrString (object, char *) except -1 int PyObject_DelItem (object, object) except -1 int PyObject_DelItemString (object, char *) except -1 object PyObject_Dir (object) object PyObject_GetAttr (object, object) object PyObject_GetAttrString (object, char *) object PyObject_GetItem (object, object) object PyObject_GetIter (object) int PyObject_HasAttr (object, object) # Always succeeds. int PyObject_HasAttrString (object, char *) # Always succeeds. long PyObject_Hash (object) except -1 int PyObject_IsInstance (object, object) except -1 int PyObject_IsSubclass (object, object) except -1 int PyObject_IsTrue (object) except -1 Py_ssize_t PyObject_Length (object) except -1 int PyObject_Not (object) except -1 int PyObject_Print (object, FILE *, int) except -1 object PyObject_Repr (object) object PyObject_RichCompare (object, object, int) int PyObject_RichCompareBool (object, object, int) except -1 int PyObject_SetAttr (object, object, object) except -1 int PyObject_SetAttrString (object, char *, object) except -1 int PyObject_SetItem (object, object, object) except -1 Py_ssize_t PyObject_Size (object) except -1 object PyObject_Str (object) object PyObject_Type (object) int PyObject_TypeCheck (object, object) # Always succeeds. object PyObject_Unicode (object) ############################################################################################### # pyerrors ############################################################################################### int PyErr_BadArgument () void PyErr_BadInternalCall () int PyErr_CheckSignals () void PyErr_Clear () int PyErr_ExceptionMatches (object) object PyErr_Format (object, char *, ...) int PyErr_GivenExceptionMatches (object, object) object PyErr_NoMemory () object PyErr_Occurred () void PyErr_Restore (object, object, object) object PyErr_SetFromErrno (object) object PyErr_SetFromErrnoWithFilename (object, char *) object PyErr_SetFromErrnoWithFilenameObject (object, object) void PyErr_SetInterrupt () void PyErr_SetNone (object) void PyErr_SetObject (object, object) void PyErr_SetString (object, char *) int PyErr_Warn (object, char *) int PyErr_WarnExplicit (object, char *, char *, int, char *, object) void PyErr_WriteUnraisable (object) ############################################################################################### # pyeval # Be extremely careful with these functions. ############################################################################################### ctypedef struct PyThreadState: PyFrameObject * frame int recursion_depth void * curexc_type void * curexc_value void * curexc_traceback void * exc_type void * exc_value void * exc_traceback void PyEval_AcquireLock () void PyEval_ReleaseLock () void PyEval_AcquireThread (PyThreadState *) void PyEval_ReleaseThread (PyThreadState *) PyThreadState* PyEval_SaveThread () void PyEval_RestoreThread (PyThreadState *) ############################################################################################### # pystate # Be extremely careful with these functions. Read PEP 311 for more detail. ############################################################################################### ctypedef int PyGILState_STATE PyGILState_STATE PyGILState_Ensure () void PyGILState_Release (PyGILState_STATE) ctypedef struct PyInterpreterState: pass PyThreadState* PyThreadState_New (PyInterpreterState *) void PyThreadState_Clear (PyThreadState *) void PyThreadState_Delete (PyThreadState *) PyThreadState* PyThreadState_Get () PyThreadState* PyThreadState_Swap (PyThreadState *tstate) # XXX: Borrowed reference. #object PyThreadState_GetDict () ############################################################################################### # run # Functions for embedded interpreters are not included. ############################################################################################### ctypedef struct PyCompilerFlags: int cf_flags ctypedef struct _node: pass ctypedef void (*PyOS_sighandler_t)(int) void PyErr_Display (object, object, object) void PyErr_Print () void PyErr_PrintEx (int) char * PyOS_Readline (FILE *, FILE *, char *) PyOS_sighandler_t PyOS_getsig (int) PyOS_sighandler_t PyOS_setsig (int, PyOS_sighandler_t) _node * PyParser_SimpleParseFile (FILE *, char *, int) except NULL _node * PyParser_SimpleParseFileFlags (FILE *, char *, int, int) except NULL _node * PyParser_SimpleParseString (char *, int) except NULL _node * PyParser_SimpleParseStringFlagsFilename(char *, char *, int, int) except NULL _node * PyParser_SimpleParseStringFlags (char *, int, int) except NULL int PyRun_AnyFile (FILE *, char *) except -1 int PyRun_AnyFileEx (FILE *, char *, int) except -1 int PyRun_AnyFileExFlags (FILE *, char *, int, PyCompilerFlags *) except -1 int PyRun_AnyFileFlags (FILE *, char *, PyCompilerFlags *) except -1 object PyRun_File (FILE *, char *, int, object, object) object PyRun_FileEx (FILE *, char *, int, object, object, int) object PyRun_FileExFlags (FILE *, char *, int, object, object, int, PyCompilerFlags *) object PyRun_FileFlags (FILE *, char *, int, object, object, PyCompilerFlags *) int PyRun_InteractiveLoop (FILE *, char *) except -1 int PyRun_InteractiveLoopFlags (FILE *, char *, PyCompilerFlags *) except -1 int PyRun_InteractiveOne (FILE *, char *) except -1 int PyRun_InteractiveOneFlags (FILE *, char *, PyCompilerFlags *) except -1 int PyRun_SimpleFile (FILE *, char *) except -1 int PyRun_SimpleFileEx (FILE *, char *, int) except -1 int PyRun_SimpleFileExFlags (FILE *, char *, int, PyCompilerFlags *) except -1 int PyRun_SimpleString (char *) except -1 int PyRun_SimpleStringFlags (char *, PyCompilerFlags *) except -1 object PyRun_String (char *, int, object, object) object PyRun_StringFlags (char *, int, object, object, PyCompilerFlags *) int Py_AtExit (void (*func)()) object Py_CompileString (char *, char *, int) object Py_CompileStringFlags (char *, char *, int, PyCompilerFlags *) void Py_Exit (int) int Py_FdIsInteractive (FILE *, char *) # Always succeeds. char * Py_GetBuildInfo () char * Py_GetCompiler () char * Py_GetCopyright () char * Py_GetExecPrefix () char * Py_GetPath () char * Py_GetPlatform () char * Py_GetPrefix () char * Py_GetProgramFullPath () char * Py_GetProgramName () char * Py_GetPythonHome () char * Py_GetVersion () ############################################################################################### # sequence ############################################################################################### int PySequence_Check (object) # Always succeeds. object PySequence_Concat (object, object) int PySequence_Contains (object, object) except -1 Py_ssize_t PySequence_Count (object, object) except -1 int PySequence_DelItem (object, Py_ssize_t) except -1 int PySequence_DelSlice (object, Py_ssize_t, Py_ssize_t) except -1 object PySequence_Fast (object, char *) int PySequence_Fast_GET_SIZE (object) object PySequence_GetItem (object, Py_ssize_t) object PySequence_GetSlice (object, Py_ssize_t, Py_ssize_t) object PySequence_ITEM (object, int) int PySequence_In (object, object) except -1 object PySequence_InPlaceConcat (object, object) object PySequence_InPlaceRepeat (object, Py_ssize_t) Py_ssize_t PySequence_Index (object, object) except -1 Py_ssize_t PySequence_Length (object) except -1 object PySequence_List (object) object PySequence_Repeat (object, Py_ssize_t) int PySequence_SetItem (object, Py_ssize_t, object) except -1 int PySequence_SetSlice (object, Py_ssize_t, Py_ssize_t, object) except -1 Py_ssize_t PySequence_Size (object) except -1 object PySequence_Tuple (object) ############################################################################################### # string ############################################################################################### PyTypeObject PyString_Type # Pyrex cannot support resizing because you have no choice but to use # realloc which may call free() on the object, and there's no way to tell # Pyrex to "forget" reference counting for the object. #int _PyString_Resize (object *, Py_ssize_t) except -1 char * PyString_AS_STRING (object) # Always succeeds. object PyString_AsDecodedObject (object, char *, char *) object PyString_AsEncodedObject (object, char *, char *) object PyString_AsEncodedString (object, char *, char *) char * PyString_AsString (object) except NULL int PyString_AsStringAndSize (object, char **, Py_ssize_t *) except -1 int PyString_Check (object) # Always succeeds. int PyString_CHECK_INTERNED (object) # Always succeeds. int PyString_CheckExact (object) # Always succeeds. # XXX: Pyrex doesn't support pointer to a python object? #void PyString_Concat (object *, object) # XXX: Pyrex doesn't support pointer to a python object? #void PyString_ConcatAndDel (object *, object) object PyString_Decode (char *, int, char *, char *) object PyString_DecodeEscape (char *, int, char *, int, char *) object PyString_Encode (char *, int, char *, char *) object PyString_Format (object, object) object PyString_FromFormat (char*, ...) object PyString_FromFormatV (char*, va_list) object PyString_FromString (char *) object PyString_FromStringAndSize (char *, Py_ssize_t) Py_ssize_t PyString_GET_SIZE (object) # Always succeeds. object PyString_InternFromString (char *) # XXX: Pyrex doesn't support pointer to a python object? #void PyString_InternImmortal (object*) # XXX: Pyrex doesn't support pointer to a python object? #void PyString_InternInPlace (object*) object PyString_Repr (object, int) Py_ssize_t PyString_Size (object) except -1 # Disgusting hack to access internal object values. ctypedef struct PyStringObject: int ob_refcnt PyTypeObject * ob_type int ob_size long ob_shash int ob_sstate char * ob_sval ############################################################################################### # tuple ############################################################################################### PyTypeObject PyTuple_Type # See PyString_Resize note about resizing. #int _PyTuple_Resize (object*, Py_ssize_t) except -1 int PyTuple_Check (object) # Always succeeds. int PyTuple_CheckExact (object) # Always succeeds. Py_ssize_t PyTuple_GET_SIZE (object) # Always succeeds. object PyTuple_GetSlice (object, Py_ssize_t, Py_ssize_t) object PyTuple_New (Py_ssize_t) object PyTuple_Pack (Py_ssize_t, ...) Py_ssize_t PyTuple_Size (object) except -1 ############################################################################################### # Dangerous things! # Do not use these unless you really, really know what you are doing. ############################################################################################### void Py_INCREF (object) void Py_XINCREF (object) void Py_DECREF (object) void Py_XDECREF (object) void Py_CLEAR (object) # XXX: Stolen reference. void PyTuple_SET_ITEM (object, Py_ssize_t, value) # XXX: Borrowed reference. object PyTuple_GET_ITEM (object, Py_ssize_t) # XXX: Borrowed reference. object PyTuple_GetItem (object, Py_ssize_t) # XXX: Stolen reference. int PyTuple_SetItem (object, Py_ssize_t, object) except -1 # XXX: Steals reference. int PyList_SetItem (object, Py_ssize_t, object) except -1 # XXX: Borrowed reference object PyList_GetItem (object, Py_ssize_t) # XXX: Borrowed reference, no NULL on error. object PyList_GET_ITEM (object, Py_ssize_t) # XXX: Stolen reference. void PyList_SET_ITEM (object, Py_ssize_t, object) # XXX: Borrowed reference. object PySequence_Fast_GET_ITEM (object, Py_ssize_t) # First parameter _must_ be a PyStringObject. object _PyString_Join (object, object) line_profiler-4.1.2/line_profiler/timers.c000066400000000000000000000024061452103132100206410ustar00rootroot00000000000000#include "Python.h" /* The following timer code comes from Python 2.5.2's _lsprof.c */ #if !defined(HAVE_LONG_LONG) #error "This module requires long longs!" #endif /*** Selection of a high-precision timer ***/ #ifdef MS_WINDOWS #include PY_LONG_LONG hpTimer(void) { LARGE_INTEGER li; QueryPerformanceCounter(&li); return li.QuadPart; } double hpTimerUnit(void) { LARGE_INTEGER li; if (QueryPerformanceFrequency(&li)) return 1.0 / li.QuadPart; else return 0.000001; /* unlikely */ } #elif (defined(PYOS_OS2) && defined(PYCC_GCC)) #include PY_LONG_LONG hpTimer(void) { struct timeval tv; PY_LONG_LONG ret; gettimeofday(&tv, (struct timezone *)NULL); ret = tv.tv_sec; ret = ret * 1000000 + tv.tv_usec; return ret; } double hpTimerUnit(void) { return 0.000001; } #else #include #include PY_LONG_LONG hpTimer(void) { struct timespec ts; PY_LONG_LONG ret; clock_gettime(CLOCK_MONOTONIC, &ts); ret = (PY_LONG_LONG)ts.tv_sec * 1000000000 + (PY_LONG_LONG)ts.tv_nsec; return ret; } double hpTimerUnit(void) { return 0.000000001; } #endif line_profiler-4.1.2/line_profiler/timers.h000066400000000000000000000001131452103132100206370ustar00rootroot00000000000000#include "Python.h" PY_LONG_LONG hpTimer(void); double hpTimerUnit(void); line_profiler-4.1.2/line_profiler/unset_trace.c000066400000000000000000000001761452103132100216540ustar00rootroot00000000000000/* Hack to hide an NULL from Cython. */ #include "Python.h" void unset_trace() { PyEval_SetTrace(NULL, NULL); } line_profiler-4.1.2/line_profiler/unset_trace.h000066400000000000000000000000241452103132100216510ustar00rootroot00000000000000void unset_trace(); line_profiler-4.1.2/publish.sh000077500000000000000000000327431452103132100163550ustar00rootroot00000000000000#!/bin/bash __doc__=' Script to publish a new version of this library on PyPI. If your script has binary dependencies then we assume that you have built a proper binary wheel with auditwheel and it exists in the wheelhouse directory. Otherwise, for source tarballs and wheels this script runs the setup.py script to create the wheels as well. Running this script with the default arguments will perform any builds and gpg signing, but nothing will be uploaded to pypi unless the user explicitly sets DO_UPLOAD=True or answers yes to the prompts. Args: TWINE_USERNAME (str) : username for pypi. This must be set if uploading to pypi. Defaults to "". TWINE_PASSWORD (str) : password for pypi. This must be set if uploading to pypi. Defaults to "". DO_GPG (bool) : If True, sign the packages with a GPG key specified by `GPG_KEYID`. defaults to auto. DO_UPLOAD (bool) : If True, upload the packages to the pypi server specified by `TWINE_REPOSITORY_URL`. DO_BUILD (bool) : If True, will execute the setup.py build script, which is expected to use setuptools. In the future we may add support for other build systems. If False, this script will expect the pre-built packages to exist in "wheelhouse/{NAME}-{VERSION}-{SUFFIX}.{EXT}". Defaults to "auto". DO_TAG (bool) : if True, will "git tag" the current HEAD with TWINE_REPOSITORY_URL (url) : The URL of the pypi server to upload to. Defaults to "auto", which if on the release branch, this will default to the live pypi server `https://upload.pypi.org/legacy` otherwise this will default to the test.pypi server: `https://test.pypi.org/legacy` GPG_KEYID (str) : The keyid of the gpg key to sign with. (if DO_GPG=True). Defaults to the local git config user.signingkey DEPLOY_REMOTE (str) : The git remote to push any tags to. Defaults to "origin" GPG_EXECUTABLE (path) : Path to the GPG executable. Defaults to "auto", which chooses "gpg2" if it exists, otherwise "gpg". MODE (str): Can be pure, binary, or all. Defaults to pure unless a CMakeLists.txt exists in which case it defaults to binary. Requirements: twine >= 1.13.0 gpg2 >= 2.2.4 OpenSSL >= 1.1.1c Notes: # NEW API TO UPLOAD TO PYPI # https://docs.travis-ci.com/user/deployment/pypi/ # https://packaging.python.org/tutorials/distributing-packages/ # https://stackoverflow.com/questions/45188811/how-to-gpg-sign-a-file-that-is-built-by-travis-ci Based on template in # github.com/Erotemic/xcookie/ ~/code/xcookie/publish.sh Usage: load_secrets # TODO: set a trap to unload secrets? cd # Set your variables or load your secrets export TWINE_USERNAME= export TWINE_PASSWORD= TWINE_REPOSITORY_URL="https://test.pypi.org/legacy/" ' DEBUG=${DEBUG:=''} if [[ "${DEBUG}" != "" ]]; then set -x fi check_variable(){ KEY=$1 HIDE=$2 VAL=${!KEY} if [[ "$HIDE" == "" ]]; then echo "[DEBUG] CHECK VARIABLE: $KEY=\"$VAL\"" else echo "[DEBUG] CHECK VARIABLE: $KEY=" fi if [[ "$VAL" == "" ]]; then echo "[ERROR] UNSET VARIABLE: $KEY=\"$VAL\"" exit 1; fi } normalize_boolean(){ ARG=$1 ARG=$(echo "$ARG" | awk '{print tolower($0)}') if [ "$ARG" = "true" ] || [ "$ARG" = "1" ] || [ "$ARG" = "yes" ] || [ "$ARG" = "y" ] || [ "$ARG" = "on" ]; then echo "True" elif [ "$ARG" = "false" ] || [ "$ARG" = "0" ] || [ "$ARG" = "no" ] || [ "$ARG" = "n" ] || [ "$ARG" = "off" ]; then echo "False" else echo "$ARG" fi } #### # Parameters ### # Options DEPLOY_REMOTE=${DEPLOY_REMOTE:=origin} NAME=${NAME:=$(python -c "import setup; print(setup.NAME)")} VERSION=$(python -c "import setup; print(setup.VERSION)") check_variable DEPLOY_REMOTE ARG_1=$1 DO_UPLOAD=${DO_UPLOAD:=$ARG_1} DO_TAG=${DO_TAG:=$ARG_1} DO_GPG=${DO_GPG:="auto"} # Verify that we want to build if [ "$DO_GPG" == "auto" ]; then DO_GPG="True" fi DO_BUILD=${DO_BUILD:="auto"} # Verify that we want to build if [ "$DO_BUILD" == "auto" ]; then DO_BUILD="True" fi DO_GPG=$(normalize_boolean "$DO_GPG") DO_BUILD=$(normalize_boolean "$DO_BUILD") DO_UPLOAD=$(normalize_boolean "$DO_UPLOAD") DO_TAG=$(normalize_boolean "$DO_TAG") TWINE_USERNAME=${TWINE_USERNAME:=""} TWINE_PASSWORD=${TWINE_PASSWORD:=""} DEFAULT_TEST_TWINE_REPO_URL="https://test.pypi.org/legacy/" DEFAULT_LIVE_TWINE_REPO_URL="https://upload.pypi.org/legacy/" TWINE_REPOSITORY_URL=${TWINE_REPOSITORY_URL:="auto"} if [[ "${TWINE_REPOSITORY_URL}" == "auto" ]]; then #if [[ "$(cat .git/HEAD)" != "ref: refs/heads/release" ]]; then # # If we are not on release, then default to the test pypi upload repo # TWINE_REPOSITORY_URL=${TWINE_REPOSITORY_URL:="https://test.pypi.org/legacy/"} #else if [[ "$DEBUG" == "" ]]; then TWINE_REPOSITORY_URL="live" else TWINE_REPOSITORY_URL="test" fi fi if [[ "${TWINE_REPOSITORY_URL}" == "live" ]]; then TWINE_REPOSITORY_URL=$DEFAULT_LIVE_TWINE_REPO_URL elif [[ "${TWINE_REPOSITORY_URL}" == "test" ]]; then TWINE_REPOSITORY_URL=$DEFAULT_TEST_TWINE_REPO_URL fi GPG_EXECUTABLE=${GPG_EXECUTABLE:="auto"} if [[ "$GPG_EXECUTABLE" == "auto" ]]; then if [[ "$(which gpg2)" != "" ]]; then GPG_EXECUTABLE="gpg2" else GPG_EXECUTABLE="gpg" fi fi GPG_KEYID=${GPG_KEYID:="auto"} if [[ "$GPG_KEYID" == "auto" ]]; then GPG_KEYID=$(git config --local user.signingkey) if [[ "$GPG_KEYID" == "" ]]; then GPG_KEYID=$(git config --global user.signingkey) fi fi if [ -f CMakeLists.txt ] ; then DEFAULT_MODE="binary" else DEFAULT_MODE="pure" fi # TODO: parameterize # The default should change depending on the application MODE=${MODE:=$DEFAULT_MODE} if [[ "$MODE" == "all" ]]; then MODE_LIST=("sdist" "native" "bdist") elif [[ "$MODE" == "pure" ]]; then MODE_LIST=("sdist" "native") elif [[ "$MODE" == "binary" ]]; then MODE_LIST=("sdist" "bdist") else MODE_LIST=("$MODE") fi MODE_LIST_STR=$(printf '"%s" ' "${MODE_LIST[@]}") #echo "MODE_LIST_STR = $MODE_LIST_STR" #### # Logic ### WAS_INTERACTION="False" echo " === PYPI BUILDING SCRIPT == NAME='$NAME' VERSION='$VERSION' TWINE_USERNAME='$TWINE_USERNAME' TWINE_REPOSITORY_URL = $TWINE_REPOSITORY_URL GPG_KEYID = '$GPG_KEYID' DO_UPLOAD=${DO_UPLOAD} DO_TAG=${DO_TAG} DO_GPG=${DO_GPG} DO_BUILD=${DO_BUILD} MODE_LIST_STR=${MODE_LIST_STR} " # Verify that we want to tag if [[ "$DO_TAG" == "True" ]]; then echo "About to tag VERSION='$VERSION'" else if [[ "$DO_TAG" == "False" ]]; then echo "We are NOT about to tag VERSION='$VERSION'" else # shellcheck disable=SC2162 read -p "Do you want to git tag and push version='$VERSION'? (input 'yes' to confirm)" ANS echo "ANS = $ANS" WAS_INTERACTION="True" DO_TAG="$ANS" DO_TAG=$(normalize_boolean "$DO_TAG") if [ "$DO_BUILD" == "auto" ]; then DO_BUILD="" DO_GPG="" fi fi fi if [[ "$DO_BUILD" == "True" ]]; then echo "About to build wheels" else if [[ "$DO_BUILD" == "False" ]]; then echo "We are NOT about to build wheels" else # shellcheck disable=SC2162 read -p "Do you need to build wheels? (input 'yes' to confirm)" ANS echo "ANS = $ANS" WAS_INTERACTION="True" DO_BUILD="$ANS" DO_BUILD=$(normalize_boolean "$DO_BUILD") fi fi # Verify that we want to publish if [[ "$DO_UPLOAD" == "True" ]]; then echo "About to directly publish VERSION='$VERSION'" else if [[ "$DO_UPLOAD" == "False" ]]; then echo "We are NOT about to directly publish VERSION='$VERSION'" else # shellcheck disable=SC2162 read -p "Are you ready to directly publish version='$VERSION'? ('yes' will twine upload)" ANS echo "ANS = $ANS" WAS_INTERACTION="True" DO_UPLOAD="$ANS" DO_UPLOAD=$(normalize_boolean "$DO_UPLOAD") fi fi if [[ "$WAS_INTERACTION" == "True" ]]; then echo " === PYPI BUILDING SCRIPT == VERSION='$VERSION' TWINE_USERNAME='$TWINE_USERNAME' TWINE_REPOSITORY_URL = $TWINE_REPOSITORY_URL GPG_KEYID = '$GPG_KEYID' DO_UPLOAD=${DO_UPLOAD} DO_TAG=${DO_TAG} DO_GPG=${DO_GPG} DO_BUILD=${DO_BUILD} MODE_LIST_STR='${MODE_LIST_STR}' " # shellcheck disable=SC2162 read -p "Look good? Ready to build? Enter any text to continue" ANS fi if [ "$DO_BUILD" == "True" ]; then echo " === === " echo "LIVE BUILDING" # Build wheel and source distribution for _MODE in "${MODE_LIST[@]}" do echo "_MODE = $_MODE" if [[ "$_MODE" == "sdist" ]]; then python setup.py sdist || { echo 'failed to build sdist wheel' ; exit 1; } elif [[ "$_MODE" == "native" ]]; then python setup.py bdist_wheel || { echo 'failed to build native wheel' ; exit 1; } elif [[ "$_MODE" == "bdist" ]]; then echo "Assume wheel has already been built" else echo "ERROR: bad mode" exit 1 fi done echo " === === " else echo "DO_BUILD=False, Skipping build" fi ls_array(){ __doc__=' Read the results of a glob pattern into an array Args: arr_name glob_pattern Example: arr_name="myarray" glob_pattern="*" pass ' local arr_name="$1" local glob_pattern="$2" shopt -s nullglob # shellcheck disable=SC2206 array=($glob_pattern) shopt -u nullglob # Turn off nullglob to make sure it doesn't interfere with anything later # FIXME; for some reason this doesnt always work properly # Copy the array into the dynamically named variable # shellcheck disable=SC2086 readarray -t $arr_name < <(printf '%s\n' "${array[@]}") } WHEEL_PATHS=() for _MODE in "${MODE_LIST[@]}" do if [[ "$_MODE" == "sdist" ]]; then ls_array "_NEW_WHEEL_PATHS" "dist/${NAME}-${VERSION}*.tar.gz" elif [[ "$_MODE" == "native" ]]; then ls_array "_NEW_WHEEL_PATHS" "dist/${NAME}-${VERSION}*.whl" elif [[ "$_MODE" == "bdist" ]]; then ls_array "_NEW_WHEEL_PATHS" "wheelhouse/${NAME}-${VERSION}-*.whl" else echo "ERROR: bad mode" exit 1 fi # hacky CONCAT because for some reason ls_array will return # something that looks empty but has one empty element for new_item in "${_NEW_WHEEL_PATHS[@]}" do if [[ "$new_item" != "" ]]; then WHEEL_PATHS+=("$new_item") fi done done # Dedup the paths readarray -t WHEEL_PATHS < <(printf '%s\n' "${WHEEL_PATHS[@]}" | sort -u) WHEEL_PATHS_STR=$(printf '"%s" ' "${WHEEL_PATHS[@]}") echo "WHEEL_PATHS_STR = $WHEEL_PATHS_STR" echo " MODE=$MODE VERSION='$VERSION' WHEEL_PATHS='$WHEEL_PATHS_STR' " if [ "$DO_GPG" == "True" ]; then echo " === === " for WHEEL_FPATH in "${WHEEL_PATHS[@]}" do echo "WHEEL_FPATH = $WHEEL_FPATH" check_variable WHEEL_FPATH # https://stackoverflow.com/questions/45188811/how-to-gpg-sign-a-file-that-is-built-by-travis-ci # secure gpg --export-secret-keys > all.gpg # REQUIRES GPG >= 2.2 check_variable GPG_EXECUTABLE || { echo 'failed no gpg exe' ; exit 1; } check_variable GPG_KEYID || { echo 'failed no gpg key' ; exit 1; } echo "Signing wheels" GPG_SIGN_CMD="$GPG_EXECUTABLE --batch --yes --detach-sign --armor --local-user $GPG_KEYID" echo "GPG_SIGN_CMD = $GPG_SIGN_CMD" $GPG_SIGN_CMD --output "$WHEEL_FPATH".asc "$WHEEL_FPATH" echo "Checking wheels" twine check "$WHEEL_FPATH".asc "$WHEEL_FPATH" || { echo 'could not check wheels' ; exit 1; } echo "Verifying wheels" $GPG_EXECUTABLE --verify "$WHEEL_FPATH".asc "$WHEEL_FPATH" || { echo 'could not verify wheels' ; exit 1; } done echo " === === " else echo "DO_GPG=False, Skipping GPG sign" fi if [[ "$DO_TAG" == "True" ]]; then TAG_NAME="v${VERSION}" # if we messed up we can delete the tag # git push origin :refs/tags/$TAG_NAME # and then tag with -f # git tag "$TAG_NAME" -m "tarball tag $VERSION" git push --tags "$DEPLOY_REMOTE" echo "Should also do a: git push $DEPLOY_REMOTE main:release" echo "For github should draft a new release: https://github.com/PyUtils/line_profiler/releases/new" else echo "Not tagging" fi if [[ "$DO_UPLOAD" == "True" ]]; then check_variable TWINE_USERNAME check_variable TWINE_PASSWORD "hide" for WHEEL_FPATH in "${WHEEL_PATHS[@]}" do twine upload --username "$TWINE_USERNAME" "--password=$TWINE_PASSWORD" \ --repository-url "$TWINE_REPOSITORY_URL" \ "$WHEEL_FPATH" --skip-existing --verbose || { echo 'failed to twine upload' ; exit 1; } done echo """ !!! FINISH: LIVE RUN !!! """ else echo """ DRY RUN ... Skipping upload DEPLOY_REMOTE = '$DEPLOY_REMOTE' DO_UPLOAD = '$DO_UPLOAD' WHEEL_FPATH = '$WHEEL_FPATH' WHEEL_PATHS_STR = '$WHEEL_PATHS_STR' MODE_LIST_STR = '$MODE_LIST_STR' VERSION='$VERSION' NAME='$NAME' TWINE_USERNAME='$TWINE_USERNAME' GPG_KEYID = '$GPG_KEYID' To do live run set DO_UPLOAD=1 and ensure deploy and current branch are the same !!! FINISH: DRY RUN !!! """ fi line_profiler-4.1.2/pyproject.toml000066400000000000000000000037371452103132100172650ustar00rootroot00000000000000[build-system] requires = [ "setuptools>=68.2.2; python_version < '4.0' and python_version >= '3.8'", "setuptools>=41.0.1; python_version < '3.8' and python_version >= '3.6'", "Cython>=3.0.3", #"Cython>=3.0.3 ; python_version < '4.0' and python_version >= '3.8' ", #"Cython>=0.29.24,<=3.0.0a11 ; python_version < '3.8' and python_version >= '3.6' ", ] build-backend = "setuptools.build_meta" # comment out to disable pep517 [tool.coverage.run] branch = true [tool.coverage.report] exclude_lines =[ "pragma: no cover", ".* # pragma: no cover", ".* # nocover", "def __repr__", "raise AssertionError", "raise NotImplementedError", "if 0:", "if trace is not None", "verbose = .*", "^ *raise", "^ *pass *$", "if _debug:", "if __name__ == .__main__.:", ".*if six.PY2:" ] omit =[ "*/setup.py" ] [tool.cibuildwheel] build = "cp36-* cp37-* cp38-* cp39-* cp310-* cp311-* cp312-*" build-frontend = "build" build-verbosity = 1 test-requires = [ "-r requirements/tests.txt",] test-command = "python {project}/run_tests.py" # https://cibuildwheel.readthedocs.io/en/stable/options/#archs [tool.cibuildwheel.macos] archs = ["x86_64", "universal2", "arm64"] [tool.mypy] ignore_missing_imports = true [tool.xcookie] tags = [ "pyutils", "binpy", "github",] mod_name = "line_profiler" repo_name = "line_profiler" rel_mod_parent_dpath = "." os = [ "all", "linux", "osx", "win",] min_python = 3.6 author = "Robert Kern" author_email = "robert.kern@enthought.com" description = "Line-by-line profiler" url = "https://github.com/pyutils/line_profiler" license = "BSD" dev_status = "stable" typed = true [tool.xcookie.entry_points] # the console_scripts entry point creates the xdoctest executable console_scripts = [ "kernprof=kernprof:main", ] [tool.pytest.ini_options] addopts = "--ignore-glob=setup.py --ignore-glob=dev" norecursedirs = ".git ignore build __pycache__ dev _skbuild" filterwarnings = [ "default", ] line_profiler-4.1.2/requirements.txt000066400000000000000000000001541452103132100176230ustar00rootroot00000000000000-r requirements/runtime.txt -r requirements/tests.txt -r requirements/optional.txt -r requirements/build.txtline_profiler-4.1.2/requirements/000077500000000000000000000000001452103132100170625ustar00rootroot00000000000000line_profiler-4.1.2/requirements/build.txt000066400000000000000000000014651452103132100207300ustar00rootroot00000000000000# Cython is the only hard requirement Cython>=3.0.3 setuptools>=68.2.2; python_version < '4.0' and python_version >= '3.8' setuptools>=41.0.1; python_version < '3.8' and python_version >= '3.6' 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>=2.11.2 ; python_version < '3.8' and python_version >= '3.7' # Python 3.7 cibuildwheel>=2.8.1 ; python_version < '3.7' and python_version >= '3.6' # Python 3.6 line_profiler-4.1.2/requirements/docs.txt000066400000000000000000000003021452103132100205460ustar00rootroot00000000000000sphinx >= 5.0.1 sphinx-autobuild >= 2021.3.14 sphinx_rtd_theme >= 1.0.0 sphinxcontrib-napoleon >= 0.7 sphinx-autoapi >= 1.8.4 Pygments >= 2.9.0 myst_parser >= 0.18.0 sphinx-reredirects >= 0.0.1 line_profiler-4.1.2/requirements/ipython.txt000066400000000000000000000005451452103132100213210ustar00rootroot00000000000000IPython >=8.14.0 ; python_version < '4.0.0' and python_version >= '3.9.0' # Python 3.9+ IPython >=8.12.2 ; python_version < '3.9.0' and python_version >= '3.8.0' # Python 3.8 IPython >=7.18.0 ; python_version < '3.8.0' and python_version >= '3.7.0' # Python 3.7 IPython >=7.14.0 ; python_version < '3.7.0' and python_version >= '3.6.0' # Python 3.6 line_profiler-4.1.2/requirements/optional.txt000066400000000000000000000001431452103132100214460ustar00rootroot00000000000000# Add requirements here, use the script for help # xdev availpkg rich rich>=12.3.0 -r ipython.txt line_profiler-4.1.2/requirements/runtime.txt000066400000000000000000000000001452103132100212740ustar00rootroot00000000000000line_profiler-4.1.2/requirements/tests.txt000066400000000000000000000020411452103132100207620ustar00rootroot00000000000000pytest>=6.2.5 ; python_version >= '3.10.0' # Python 3.10+ pytest>=4.6.0 ; python_version < '3.10.0' and python_version >= '3.7.0' # Python 3.7-3.9 pytest>=4.6.0 ; python_version < '3.7.0' and python_version >= '3.6.0' # Python 3.6 pytest>=4.6.0, <= 6.1.2 ; python_version < '3.6.0' and python_version >= '3.5.0' # Python 3.5 pytest>=4.6.0, <= 4.6.11 ; python_version < '3.5.0' and python_version >= '3.4.0' # Python 3.4 pytest>=4.6.0, <= 4.6.11 ; python_version < '2.8.0' and python_version >= '2.7.0' # Python 2.7 pytest-cov>=3.0.0 ; python_version >= '3.6.0' # Python 3.6+ pytest-cov>=2.9.0 ; python_version < '3.6.0' and python_version >= '3.5.0' # Python 3.5 pytest-cov>=2.8.1 ; python_version < '3.5.0' and python_version >= '3.4.0' # Python 3.4 pytest-cov>=2.8.1 ; python_version < '2.8.0' and python_version >= '2.7.0' # Python 2.7 coverage[toml] >= 5.3 ubelt >= 1.3.4 xdoctest >= 1.1.2 line_profiler-4.1.2/run_linter.sh000077500000000000000000000002431452103132100170560ustar00rootroot00000000000000#!/bin/bash flake8 --count --select=E9,F63,F7,F82 --show-source --statistics line_profiler flake8 --count --select=E9,F63,F7,F82 --show-source --statistics ./testsline_profiler-4.1.2/run_tests.py000077500000000000000000000112351452103132100167440ustar00rootroot00000000000000#!/usr/bin/env python from os.path import dirname, join, abspath import sqlite3 import sys import os import re def is_cibuildwheel(): """Check if run with cibuildwheel.""" return 'CIBUILDWHEEL' in os.environ def temp_rename_kernprof(repo_dir): """ Hacky workaround so kernprof.py doesn't get covered twice (installed and local). This needed to combine the .coverage files, since file paths need to be unique. """ original_path = repo_dir + '/kernprof.py' tmp_path = original_path + '.tmp' if os.path.isfile(original_path): os.rename(original_path, tmp_path) elif os.path.isfile(tmp_path): os.rename(tmp_path, original_path) def replace_docker_path(path, runner_project_dir): """Update path to a file installed in a temp venv to runner_project_dir.""" pattern = re.compile(r"\/tmp\/.+?\/site-packages") return pattern.sub(runner_project_dir, path) def update_coverage_file(coverage_path, runner_project_dir): """ Since the paths inside of docker vary from the runner paths, the paths in the .coverage file need to be adjusted to combine them, since 'coverage combine ' checks if the file paths exist. """ try: sqliteConnection = sqlite3.connect(coverage_path) cursor = sqliteConnection.cursor() print('Connected to Coverage SQLite') read_file_query = 'SELECT id, path from file' cursor.execute(read_file_query) old_records = cursor.fetchall() new_records = [(replace_docker_path(path, runner_project_dir), _id) for _id, path in old_records] print('Updated coverage file paths:\n', new_records) sql_update_query = 'Update file set path = ? where id = ?' cursor.executemany(sql_update_query, new_records) sqliteConnection.commit() print('Coverage Updated successfully') cursor.close() except sqlite3.Error as error: print('Failed to coverage: ', error) finally: if sqliteConnection: sqliteConnection.close() print('The sqlite connection is closed') def copy_coverage_cibuildwheel_docker(runner_project_dir): """ When run with cibuildwheel under linux, the tests run in the folder /project inside docker and the coverage files need to be copied to the output folder. """ coverage_path = '/project/tests/.coverage' if os.path.isfile(coverage_path): update_coverage_file(coverage_path, runner_project_dir) env_hash = hash((sys.version, os.environ.get('AUDITWHEEL_PLAT', ''))) os.makedirs('/output', exist_ok=True) os.rename(coverage_path, '/output/.coverage.{}'.format(env_hash)) if __name__ == '__main__': cwd = os.getcwd() repo_dir = abspath(dirname(__file__)) test_dir = join(repo_dir, 'tests') print('cwd = {!r}'.format(cwd)) import pytest # Prefer testing the installed version, but fallback to testing the # development version. try: import ubelt as ub except ImportError: print('running this test script requires ubelt') raise package_name = 'line_profiler' # Statically check if ``package_name`` is installed outside of the repo. # To do this, we make a copy of PYTHONPATH, remove the repodir, and use # ubelt to check to see if ``package_name`` can be resolved to a path. temp_path = list(map(abspath, sys.path)) if repo_dir in temp_path: temp_path.remove(repo_dir) modpath = ub.modname_to_modpath(package_name, sys_path=temp_path) if modpath is not None: # If it does, then import it. This should cause the installed version # to be used on further imports even if the repo_dir is in the path. print(f'Using installed version of {package_name}') module = ub.import_module_from_path(modpath, index=0) print('Installed module = {!r}'.format(module)) else: print(f'No installed version of {package_name} found') try: print('Changing dirs to test_dir={!r}'.format(test_dir)) os.chdir(test_dir) pytest_args = [ '--cov-config', '../pyproject.toml', '--cov-report', 'html', '--cov-report', 'term', '--cov-report', 'xml', '--cov=' + package_name, modpath, '.' ] if is_cibuildwheel(): pytest_args.append('--cov-append') pytest_args = pytest_args + sys.argv[1:] sys.exit(pytest.main(pytest_args)) finally: os.chdir(cwd) if is_cibuildwheel(): # for CIBW under linux copy_coverage_cibuildwheel_docker(f'/home/runner/work/{package_name}/{package_name}') print('Restoring cwd = {!r}'.format(cwd)) line_profiler-4.1.2/setup.py000077500000000000000000000255231452103132100160630ustar00rootroot00000000000000#!/usr/bin/env python from os.path import exists import sys import os import warnings import setuptools def _choose_build_method(): DISABLE_C_EXTENSIONS = os.environ.get("DISABLE_C_EXTENSIONS", "").lower() LINE_PROFILER_BUILD_METHOD = os.environ.get("LINE_PROFILER_BUILD_METHOD", "auto").lower() if DISABLE_C_EXTENSIONS in {"true", "on", "yes", "1"}: LINE_PROFILER_BUILD_METHOD = 'setuptools' if LINE_PROFILER_BUILD_METHOD == 'auto': try: import Cython # NOQA except ImportError: try: import skbuild # NOQA import cmake # NOQA import ninja # NOQA except ImportError: # The main fallback disables c-extensions LINE_PROFILER_BUILD_METHOD = 'setuptools' else: # This should never be hit LINE_PROFILER_BUILD_METHOD = 'scikit-build' else: # Use plain cython by default LINE_PROFILER_BUILD_METHOD = 'cython' return LINE_PROFILER_BUILD_METHOD def parse_version(fpath): """ Statically parse the version number from a python file """ value = static_parse("__version__", fpath) return value def static_parse(varname, fpath): """ Statically parse the a constant variable from a python file """ import ast if not exists(fpath): raise ValueError("fpath={!r} does not exist".format(fpath)) with open(fpath, "r") as file_: sourcecode = file_.read() pt = ast.parse(sourcecode) class StaticVisitor(ast.NodeVisitor): def visit_Assign(self, node): for target in node.targets: if getattr(target, "id", None) == varname: self.static_value = node.value.s visitor = StaticVisitor() visitor.visit(pt) try: value = visitor.static_value except AttributeError: value = "Unknown {}".format(varname) warnings.warn(value) return value def parse_description(): """ Parse the description in the README file CommandLine: pandoc --from=markdown --to=rst --output=README.rst README.md python -c "import setup; print(setup.parse_description())" """ from os.path import dirname, join, exists readme_fpath = join(dirname(__file__), "README.rst") # This breaks on pip install, so check that it exists. if exists(readme_fpath): with open(readme_fpath, "r") as f: text = f.read() return text return "" def parse_requirements(fname="requirements.txt", versions=False): """ Parse the package dependencies listed in a requirements file but strips specific versioning information. Args: fname (str): path to requirements file versions (bool | str, default=False): If true include version specs. If strict, then pin to the minimum version. Returns: List[str]: list of requirements items """ from os.path import exists, dirname, join import re require_fpath = fname def parse_line(line, dpath=""): """ Parse information from a line in a requirements text file line = 'git+https://a.com/somedep@sometag#egg=SomeDep' line = '-e git+https://a.com/somedep@sometag#egg=SomeDep' """ # Remove inline comments comment_pos = line.find(" #") if comment_pos > -1: line = line[:comment_pos] if line.startswith("-r "): # Allow specifying requirements in other files target = join(dpath, line.split(" ")[1]) for info in parse_require_file(target): yield info else: # See: https://www.python.org/dev/peps/pep-0508/ info = {"line": line} if line.startswith("-e "): info["package"] = line.split("#egg=")[1] else: if ";" in line: pkgpart, platpart = line.split(";") # Handle platform specific dependencies # setuptools.readthedocs.io/en/latest/setuptools.html # #declaring-platform-specific-dependencies plat_deps = platpart.strip() info["platform_deps"] = plat_deps else: pkgpart = line platpart = None # Remove versioning from the package pat = "(" + "|".join([">=", "==", ">"]) + ")" parts = re.split(pat, pkgpart, maxsplit=1) parts = [p.strip() for p in parts] info["package"] = parts[0] if len(parts) > 1: op, rest = parts[1:] version = rest # NOQA info["version"] = (op, version) yield info def parse_require_file(fpath): dpath = dirname(fpath) with open(fpath, "r") as f: for line in f.readlines(): line = line.strip() if line and not line.startswith("#"): for info in parse_line(line, dpath=dpath): yield info def gen_packages_items(): if exists(require_fpath): for info in parse_require_file(require_fpath): parts = [info["package"]] if versions and "version" in info: if versions == "strict": # In strict mode, we pin to the minimum version if info["version"]: # Only replace the first >= instance verstr = "".join(info["version"]).replace(">=", "==", 1) parts.append(verstr) else: parts.extend(info["version"]) if not sys.version.startswith("3.4"): # apparently package_deps are broken in 3.4 plat_deps = info.get("platform_deps") if plat_deps is not None: parts.append(";" + plat_deps) item = "".join(parts) yield item packages = list(gen_packages_items()) return packages long_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. """ NAME = "line_profiler" INIT_PATH = "line_profiler/line_profiler.py" VERSION = parse_version(INIT_PATH) if __name__ == '__main__': setupkw = {} LINE_PROFILER_BUILD_METHOD = _choose_build_method() if LINE_PROFILER_BUILD_METHOD == 'setuptools': setup = setuptools.setup elif LINE_PROFILER_BUILD_METHOD == 'scikit-build': import skbuild # NOQA setup = skbuild.setup elif LINE_PROFILER_BUILD_METHOD == 'cython': # no need to try importing cython because an import # was already attempted in _choose_build_method import multiprocessing from setuptools import Extension from Cython.Build import cythonize def run_cythonize(force=False): return cythonize( Extension( name="line_profiler._line_profiler", sources=["line_profiler/_line_profiler.pyx", "line_profiler/timers.c", "line_profiler/unset_trace.c"], language="c++", define_macros=[("CYTHON_TRACE", (1 if os.getenv("DEV") == "true" else 0))], ), compiler_directives={ "language_level": 3, "infer_types": True, "legacy_implicit_noexcept": True, "linetrace": (True if os.getenv("DEV") == "true" else False) }, include_path=["line_profiler/python25.pxd"], force=force, nthreads=multiprocessing.cpu_count(), ) setupkw.update(dict(ext_modules=run_cythonize())) setup = setuptools.setup else: raise Exception('Unknown build method') setupkw["install_requires"] = parse_requirements( "requirements/runtime.txt", versions="loose" ) setupkw["extras_require"] = { "all": parse_requirements("requirements.txt", versions="loose"), "tests": parse_requirements("requirements/tests.txt", versions="loose"), "optional": parse_requirements("requirements/optional.txt", versions="loose"), "all-strict": parse_requirements("requirements.txt", versions="strict"), "runtime-strict": parse_requirements( "requirements/runtime.txt", versions="strict" ), "tests-strict": parse_requirements("requirements/tests.txt", versions="strict"), "optional-strict": parse_requirements( "requirements/optional.txt", versions="strict" ), "ipython": parse_requirements('requirements/ipython.txt', versions="loose"), "ipython-strict": parse_requirements('requirements/ipython.txt', versions="strict"), } setupkw['entry_points'] = { 'console_scripts': [ 'kernprof=kernprof:main', ], } setupkw["name"] = NAME setupkw["version"] = VERSION setupkw["author"] = "Robert Kern" setupkw["author_email"] = "robert.kern@enthought.com" setupkw["url"] = "https://github.com/pyutils/line_profiler" setupkw["description"] = "Line-by-line profiler" setupkw["long_description"] = parse_description() setupkw["long_description_content_type"] = "text/x-rst" setupkw["license"] = "BSD" setupkw["packages"] = list(setuptools.find_packages()) setupkw["py_modules"] = ['kernprof', 'line_profiler'] setupkw["python_requires"] = ">=3.6" setupkw['license_files'] = ['LICENSE.txt', 'LICENSE_Python.txt'] setupkw["package_data"] = {"line_profiler": ["py.typed", "*.pyi"]} setupkw['keywords'] = ['timing', 'timer', 'profiling', 'profiler', 'line_profiler'] setupkw["classifiers"] = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: C', 'Programming Language :: Python', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Software Development', ] setup(**setupkw) line_profiler-4.1.2/tests/000077500000000000000000000000001452103132100155015ustar00rootroot00000000000000line_profiler-4.1.2/tests/complex_example.py000066400000000000000000000110001452103132100212250ustar00rootroot00000000000000""" A script used in test_complex_case.py python ~/code/line_profiler/tests/complex_example.py LINE_PROFILE=1 python ~/code/line_profiler/tests/complex_example.py python -m kernprof -v ~/code/line_profiler/tests/complex_example.py python -m kernprof -lv ~/code/line_profiler/tests/complex_example.py cd ~/code/line_profiler/tests/ # Run the code by itself without any profiling PROFILE_TYPE=none python complex_example.py # NOTE: this fails because we are not running with kernprof PROFILE_TYPE=implicit python complex_example.py # Kernprof with the implicit profile decorator # NOTE: multiprocessing breaks this invocation, so set process size to zero PROFILE_TYPE=implicit python -m kernprof -b complex_example.py --process_size=0 python -m pstats ./complex_example.py.prof # Explicit decorator with line kernprof # NOTE: again, multiprocessing breaks when using kernprof PROFILE_TYPE=explicit python -m kernprof -l complex_example.py --process_size=0 python -m line_profiler complex_example.py.lprof # Explicit decorator with cProfile kernprof # NOTE: again, multiprocessing breaks when using kernprof (does this happen in older verions?) PROFILE_TYPE=explicit python -m kernprof -b complex_example.py --process_size=0 python -m pstats ./complex_example.py.prof # Explicit decorator with environment enabling PROFILE_TYPE=explicit LINE_PROFILE=1 python complex_example.py # Explicit decorator without enabling it PROFILE_TYPE=explicit LINE_PROFILE=0 python complex_example.py # Use a custom defined line profiler object PROFILE_TYPE=custom python complex_example.py """ import os # The test will define how we expect the profile decorator to exist PROFILE_TYPE = os.environ.get('PROFILE_TYPE', '') if PROFILE_TYPE == 'implicit': # Do nothing, assume kernprof will inject profile in for us ... elif PROFILE_TYPE == 'none': # Define a no-op profiler def profile(func): return func elif PROFILE_TYPE == 'explicit': # Use the explicit profile decorator import line_profiler profile = line_profiler.profile elif PROFILE_TYPE == 'custom': # Create a custom profile decorator import line_profiler import atexit profile = line_profiler.LineProfiler() @atexit.register def _show_profile_on_end(): ... profile.print_stats(summarize=1, sort=1, stripzeros=1, rich=1) else: raise KeyError('') @profile def fib(n): a, b = 0, 1 while a < n: a, b = b, a + b @profile def fib_only_called_by_thread(n): a, b = 0, 1 while a < n: a, b = b, a + b @profile def fib_only_called_by_process(n): a, b = 0, 1 while a < n: a, b = b, a + b @profile def main(): """ Run a lot of different Fibonacci jobs """ import argparse parser = argparse.ArgumentParser() parser.add_argument('--serial_size', type=int, default=10) parser.add_argument('--thread_size', type=int, default=10) parser.add_argument('--process_size', type=int, default=10) args = parser.parse_args() for i in range(args.serial_size): fib(i) funcy_fib( i) fib(i) from concurrent.futures import ThreadPoolExecutor executor = ThreadPoolExecutor(max_workers=4) with executor: jobs = [] for i in range(args.thread_size): job = executor.submit(fib, i) jobs.append(job) job = executor.submit(funcy_fib, i) jobs.append(job) job = executor.submit(fib_only_called_by_thread, i) jobs.append(job) for job in jobs: job.result() from concurrent.futures import ProcessPoolExecutor executor = ProcessPoolExecutor(max_workers=4) with executor: jobs = [] for i in range(args.process_size): job = executor.submit(fib, i) jobs.append(job) job = executor.submit(funcy_fib, i) jobs.append(job) job = executor.submit(fib_only_called_by_process, i) jobs.append(job) for job in jobs: job.result() @profile def funcy_fib(n): """ Alternative fib function where code splits out over multiple lines """ a, b = ( 0, 1 ) while a < n: # print( # a, end=' ') a, b = b, \ a + b # print( # ) if __name__ == '__main__': """ CommandLine: cd ~/code/line_profiler/tests/ python complex_example.py --size 10 python complex_example.py --serial_size 100000 --thread_size 0 --process_size 0 """ main() line_profiler-4.1.2/tests/test_assumptions.py000066400000000000000000000003031452103132100214730ustar00rootroot00000000000000 def test_assumed_noop(): """ We are assuming the NOP code is 9. Double check that it is. """ import opcode NOP_VALUE: int = opcode.opmap['NOP'] assert NOP_VALUE == 9 line_profiler-4.1.2/tests/test_autoprofile.py000066400000000000000000000227061452103132100214520ustar00rootroot00000000000000import tempfile import sys import os import ubelt as ub def test_single_function_autoprofile(): """ Test that every function in a file is profiled when autoprofile is enabled. """ temp_dpath = ub.Path(tempfile.mkdtemp()) code = ub.codeblock( ''' def func1(a): return a + 1 func1(1) ''') with ub.ChDir(temp_dpath): script_fpath = ub.Path('script.py') script_fpath.write_text(code) args = [sys.executable, '-m', 'kernprof', '-p', 'script.py', '-l', os.fspath(script_fpath)] proc = ub.cmd(args) print(proc.stdout) print(proc.stderr) proc.check_returncode() args = [sys.executable, '-m', 'line_profiler', os.fspath(script_fpath) + '.lprof'] proc = ub.cmd(args) raw_output = proc.stdout proc.check_returncode() assert 'func1' in raw_output temp_dpath.delete() def test_multi_function_autoprofile(): """ Test that every function in a file is profiled when autoprofile is enabled. """ temp_dpath = ub.Path(tempfile.mkdtemp()) code = ub.codeblock( ''' def func1(a): return a + 1 def func2(a): return a * 2 + 2 def func3(a): return a / 10 + 3 def func4(a): return a % 2 + 4 func1(1) ''') with ub.ChDir(temp_dpath): script_fpath = ub.Path('script.py') script_fpath.write_text(code) args = [sys.executable, '-m', 'kernprof', '-p', 'script.py', '-l', os.fspath(script_fpath)] proc = ub.cmd(args) print(proc.stdout) print(proc.stderr) proc.check_returncode() args = [sys.executable, '-m', 'line_profiler', os.fspath(script_fpath) + '.lprof'] proc = ub.cmd(args) raw_output = proc.stdout proc.check_returncode() assert 'func1' in raw_output assert 'func2' in raw_output assert 'func3' in raw_output assert 'func4' in raw_output temp_dpath.delete() def test_duplicate_function_autoprofile(): """ Test that every function in a file is profiled when autoprofile is enabled. """ temp_dpath = ub.Path(tempfile.mkdtemp()) code = ub.codeblock( ''' def func1(a): return a + 1 def func2(a): return a + 1 def func3(a): return a + 1 def func4(a): return a + 1 func1(1) func2(1) func3(1) ''') with ub.ChDir(temp_dpath): script_fpath = ub.Path('script.py') script_fpath.write_text(code) args = [sys.executable, '-m', 'kernprof', '-p', 'script.py', '-l', os.fspath(script_fpath)] proc = ub.cmd(args) print(proc.stdout) print(proc.stderr) proc.check_returncode() args = [sys.executable, '-m', 'line_profiler', os.fspath(script_fpath) + '.lprof'] proc = ub.cmd(args) raw_output = proc.stdout print(raw_output) proc.check_returncode() assert 'Function: func1' in raw_output assert 'Function: func2' in raw_output assert 'Function: func3' in raw_output assert 'Function: func4' in raw_output temp_dpath.delete() def _write_demo_module(temp_dpath): """ Make a dummy test module structure """ (temp_dpath / 'test_mod').ensuredir() (temp_dpath / 'test_mod/subpkg').ensuredir() (temp_dpath / 'test_mod/__init__.py').touch() (temp_dpath / 'test_mod/subpkg/__init__.py').touch() (temp_dpath / 'test_mod/util.py').write_text(ub.codeblock( ''' def add_operator(a, b): return a + b ''')) (temp_dpath / 'test_mod/submod1.py').write_text(ub.codeblock( ''' from test_mod.util import add_operator def add_one(items): new_items = [] for item in items: new_item = add_operator(item, 1) new_items.append(new_item) return new_items ''')) (temp_dpath / 'test_mod/submod2.py').write_text(ub.codeblock( ''' from test_mod.util import add_operator def add_two(items): new_items = [add_operator(item, 2) for item in items] return new_items ''')) (temp_dpath / 'test_mod/subpkg/submod3.py').write_text(ub.codeblock( ''' from test_mod.util import add_operator def add_three(items): new_items = [add_operator(item, 3) for item in items] return new_items ''')) script_fpath = (temp_dpath / 'script.py') script_fpath.write_text(ub.codeblock( ''' from test_mod import submod1 from test_mod import submod2 from test_mod.subpkg import submod3 import statistics def main(): data = [1, 2, 3] val = submod1.add_one(data) val = submod2.add_two(val) val = submod3.add_three(val) result = statistics.harmonic_mean(val) print(result) main() ''')) return script_fpath def test_autoprofile_script_with_module(): """ Test that every function in a file is profiled when autoprofile is enabled. """ temp_dpath = ub.Path(tempfile.mkdtemp()) script_fpath = _write_demo_module(temp_dpath) # args = [sys.executable, '-m', 'kernprof', '--prof-imports', '-p', 'script.py', '-l', os.fspath(script_fpath)] args = [sys.executable, '-m', 'kernprof', '-p', 'script.py', '-l', os.fspath(script_fpath)] proc = ub.cmd(args, cwd=temp_dpath, verbose=2) print(proc.stdout) print(proc.stderr) proc.check_returncode() args = [sys.executable, '-m', 'line_profiler', os.fspath(script_fpath) + '.lprof'] proc = ub.cmd(args, cwd=temp_dpath) raw_output = proc.stdout print(raw_output) proc.check_returncode() assert 'Function: add_one' not in raw_output assert 'Function: main' in raw_output def test_autoprofile_module(): """ Test that every function in a file is profiled when autoprofile is enabled. """ temp_dpath = ub.Path(tempfile.mkdtemp()) script_fpath = _write_demo_module(temp_dpath) # args = [sys.executable, '-m', 'kernprof', '--prof-imports', '-p', 'script.py', '-l', os.fspath(script_fpath)] args = [sys.executable, '-m', 'kernprof', '-p', 'test_mod', '-l', os.fspath(script_fpath)] proc = ub.cmd(args, cwd=temp_dpath, verbose=2) print(proc.stdout) print(proc.stderr) proc.check_returncode() args = [sys.executable, '-m', 'line_profiler', os.fspath(script_fpath) + '.lprof'] proc = ub.cmd(args, cwd=temp_dpath) raw_output = proc.stdout print(raw_output) proc.check_returncode() assert 'Function: add_one' in raw_output assert 'Function: main' not in raw_output def test_autoprofile_module_list(): """ Test only modules specified are autoprofiled """ temp_dpath = ub.Path(tempfile.mkdtemp()) script_fpath = _write_demo_module(temp_dpath) # args = [sys.executable, '-m', 'kernprof', '--prof-imports', '-p', 'script.py', '-l', os.fspath(script_fpath)] args = [sys.executable, '-m', 'kernprof', '-p', 'test_mod.submod1,test_mod.subpkg.submod3', '-l', os.fspath(script_fpath)] proc = ub.cmd(args, cwd=temp_dpath, verbose=2) print(proc.stdout) print(proc.stderr) proc.check_returncode() args = [sys.executable, '-m', 'line_profiler', os.fspath(script_fpath) + '.lprof'] proc = ub.cmd(args, cwd=temp_dpath) raw_output = proc.stdout print(raw_output) proc.check_returncode() assert 'Function: add_one' in raw_output assert 'Function: add_two' not in raw_output assert 'Function: add_three' in raw_output assert 'Function: main' not in raw_output def test_autoprofile_module_with_prof_imports(): """ Test the imports of the specified modules are profiled as well. """ temp_dpath = ub.Path(tempfile.mkdtemp()) script_fpath = _write_demo_module(temp_dpath) args = [sys.executable, '-m', 'kernprof', '--prof-imports', '-p', 'test_mod.submod1', '-l', os.fspath(script_fpath)] proc = ub.cmd(args, cwd=temp_dpath, verbose=2) print(proc.stdout) print(proc.stderr) proc.check_returncode() args = [sys.executable, '-m', 'line_profiler', os.fspath(script_fpath) + '.lprof'] proc = ub.cmd(args, cwd=temp_dpath) raw_output = proc.stdout print(raw_output) proc.check_returncode() assert 'Function: add_one' in raw_output assert 'Function: add_operator' in raw_output assert 'Function: add_three' not in raw_output assert 'Function: main' not in raw_output def test_autoprofile_script_with_prof_imports(): """ Test the imports of the specified modules are profiled as well. """ temp_dpath = ub.Path(tempfile.mkdtemp()) script_fpath = _write_demo_module(temp_dpath) # import sys # if sys.version_info[0:2] >= (3, 11): # import pytest # pytest.skip('Failing due to the noop bug') args = [sys.executable, '-m', 'kernprof', '--prof-imports', '-p', 'script.py', '-l', os.fspath(script_fpath)] proc = ub.cmd(args, cwd=temp_dpath, verbose=2) print(proc.stdout) print(proc.stderr) proc.check_returncode() args = [sys.executable, '-m', 'line_profiler', os.fspath(script_fpath) + '.lprof'] proc = ub.cmd(args, cwd=temp_dpath) raw_output = proc.stdout print(raw_output) proc.check_returncode() assert 'Function: add_one' in raw_output assert 'Function: harmonic_mean' in raw_output assert 'Function: main' in raw_output line_profiler-4.1.2/tests/test_cli.py000066400000000000000000000034041452103132100176620ustar00rootroot00000000000000from os.path import join from sys import executable def test_cli(): """ Test command line interaction with kernprof and line_profiler. References: https://github.com/pyutils/line_profiler/issues/9 CommandLine: xdoctest -m ./tests/test_cli.py test_cli """ import ubelt as ub import tempfile # Create a dummy source file code = ub.codeblock( ''' @profile def my_inefficient_function(): a = 0 for i in range(10): a += i for j in range(10): a += j if __name__ == '__main__': my_inefficient_function() ''') tmp_dpath = tempfile.mkdtemp() tmp_src_fpath = join(tmp_dpath, 'foo.py') with open(tmp_src_fpath, 'w') as file: file.write(code) # Run kernprof on it info = ub.cmd(f'kernprof -l {tmp_src_fpath}', verbose=3, cwd=tmp_dpath) assert info['ret'] == 0 tmp_lprof_fpath = join(tmp_dpath, 'foo.py.lprof') tmp_lprof_fpath info = ub.cmd(f'{executable} -m line_profiler {tmp_lprof_fpath}', cwd=tmp_dpath, verbose=3) assert info['ret'] == 0 # Check for some patterns that should be in the output assert '% Time' in info['out'] assert '7 100' in info['out'] def test_version_agreement(): """ Ensure that line_profiler and kernprof have the same version info """ import ubelt as ub info1 = ub.cmd(f'{executable} -m line_profiler --version') info2 = ub.cmd(f'{executable} -m kernprof --version') # Strip local version suffixes version1 = info1['out'].strip().split('+')[0] version2 = info2['out'].strip().split('+')[0] assert version2 == version1, 'kernprof and line_profiler must be in sync' line_profiler-4.1.2/tests/test_complex_case.py000066400000000000000000000117261452103132100215630ustar00rootroot00000000000000import os import sys import tempfile import ubelt as ub LINUX = sys.platform.startswith('linux') def get_complex_example_fpath(): try: test_dpath = ub.Path(__file__).parent except NameError: # for development test_dpath = ub.Path('~/code/line_profiler/tests').expanduser() complex_fpath = test_dpath / 'complex_example.py' return complex_fpath def test_complex_example_python_none(): """ Make sure the complex example script works without any profiling """ complex_fpath = get_complex_example_fpath() info = ub.cmd(f'python {complex_fpath}', shell=True, verbose=3, env=ub.udict(os.environ) | {'PROFILE_TYPE': 'none'}) assert info.stdout == '' info.check_returncode() def test_varied_complex_invocations(): """ Tests variations of running the complex example: with / without kernprof with cProfile / LineProfiler backends with / without explicit profiler """ # Enumerate valid cases to test cases = [] for runner in ['python', 'kernprof']: for env_line_profile in ['0', '1']: if runner == 'kernprof': for profile_type in ['explicit', 'implicit']: for kern_flags in ['-l', '-b']: if 'l' in kern_flags: outpath = 'complex_example.py.lprof' else: outpath = 'complex_example.py.prof' cases.append({ 'runner': runner, 'kern_flags': kern_flags, 'env_line_profile': env_line_profile, 'profile_type': profile_type, 'outpath': outpath, }) else: if env_line_profile == '1': outpath = 'profile_output.txt' else: outpath = None cases.append({ 'runner': runner, 'env_line_profile': env_line_profile, 'profile_type': 'explicit', 'outpath': outpath, }) # Add case for auto-profile # FIXME: this runs, but doesn't quite work. cases.append({ 'runner': 'kernprof', 'kern_flags': '-l --prof-mod complex_example.py', 'env_line_profile': '0', 'profile_type': 'none', 'outpath': 'complex_example.py.lprof', 'ignore_checks': True, }) if 0: # FIXME: this does not run with prof-imports cases.append({ 'runner': 'kernprof', 'kern_flags': '-l --prof-imports --prof-mod complex_example.py', 'env_line_profile': '0', 'profile_type': 'none', 'outpath': 'complex_example.py.lprof', }) complex_fpath = get_complex_example_fpath() results = [] for case in cases: temp_dpath = tempfile.mkdtemp() with ub.ChDir(temp_dpath): env = {} outpath = case['outpath'] if outpath: outpath = ub.Path(outpath) # Construct the invocation for each case if case['runner'] == 'kernprof': kern_flags = case['kern_flags'] # FIXME: # Note: kernprof doesn't seem to play well with multiprocessing prog_flags = ' --process_size=0' runner = f'{sys.executable} -m kernprof {kern_flags}' else: env['LINE_PROFILE'] = case["env_line_profile"] runner = f'{sys.executable}' prog_flags = '' env['PROFILE_TYPE'] = case["profile_type"] command = f'{runner} {complex_fpath}' + prog_flags HAS_SHELL = LINUX if HAS_SHELL: # Use shell because it gives a indication of what is happening environ_prefix = ' '.join([f'{k}={v}' for k, v in env.items()]) info = ub.cmd(environ_prefix + ' ' + command, shell=True, verbose=3) else: env = ub.udict(os.environ) | env info = ub.cmd(command, env=env, verbose=3) info.check_returncode() result = case.copy() if outpath: result['outsize'] = outpath.stat().st_size else: result['outsize'] = None results.append(result) if outpath: assert outpath.exists() assert outpath.is_file() outpath.delete() if 0: import pandas as pd import rich table = pd.DataFrame(results) rich.print(table) # Ensure the scripts that produced output produced non-trivial output if not case.get('ignore_checks', False): for result in results: if result['outpath'] is not None: assert result['outsize'] > 100 line_profiler-4.1.2/tests/test_duplicate_functions.py000066400000000000000000000005611452103132100231560ustar00rootroot00000000000000def test_duplicate_function(): """ Test from https://github.com/pyutils/line_profiler/issues/232 """ import line_profiler class C: def f1(self): pass def f2(self): pass def f3(self): pass profile = line_profiler.LineProfiler() profile(C.f1) profile(C.f2) profile(C.f3) line_profiler-4.1.2/tests/test_explicit_profile.py000066400000000000000000000176111452103132100224610ustar00rootroot00000000000000import tempfile import sys import os import ubelt as ub def test_simple_explicit_nonglobal_usage(): """ python -c "from test_explicit_profile import *; test_simple_explicit_nonglobal_usage()" """ from line_profiler import LineProfiler profiler = LineProfiler() def func(a): return a + 1 profiled_func = profiler(func) # Run Once profiled_func(1) lstats = profiler.get_stats() print(f'lstats.timings={lstats.timings}') print(f'lstats.unit={lstats.unit}') print(f'profiler.code_hash_map={profiler.code_hash_map}') profiler.print_stats() def _demo_explicit_profile_script(): return ub.codeblock( ''' from line_profiler import profile @profile def fib(n): a, b = 0, 1 while a < n: a, b = b, a + b fib(10) ''') def test_explicit_profile_with_nothing(): """ Test that no profiling happens when we dont request it. """ temp_dpath = ub.Path(tempfile.mkdtemp()) with ub.ChDir(temp_dpath): script_fpath = ub.Path('script.py') script_fpath.write_text(_demo_explicit_profile_script()) args = [sys.executable, os.fspath(script_fpath)] proc = ub.cmd(args) print(proc.stdout) print(proc.stderr) proc.check_returncode() assert not (temp_dpath / 'profile_output.txt').exists() assert not (temp_dpath / 'profile_output.lprof').exists() temp_dpath.delete() def test_explicit_profile_with_environ_on(): """ Test that explicit profiling is enabled when we specify the LINE_PROFILE enviornment variable. """ temp_dpath = ub.Path(tempfile.mkdtemp()) env = os.environ.copy() env['LINE_PROFILE'] = '1' with ub.ChDir(temp_dpath): script_fpath = ub.Path('script.py') script_fpath.write_text(_demo_explicit_profile_script()) args = [sys.executable, os.fspath(script_fpath)] proc = ub.cmd(args, env=env) print(proc.stdout) print(proc.stderr) proc.check_returncode() assert (temp_dpath / 'profile_output.txt').exists() assert (temp_dpath / 'profile_output.lprof').exists() temp_dpath.delete() def test_explicit_profile_with_environ_off(): """ When LINE_PROFILE is falsy, profiling should not run. """ temp_dpath = ub.Path(tempfile.mkdtemp()) env = os.environ.copy() env['LINE_PROFILE'] = '0' with ub.ChDir(temp_dpath): script_fpath = ub.Path('script.py') script_fpath.write_text(_demo_explicit_profile_script()) args = [sys.executable, os.fspath(script_fpath)] proc = ub.cmd(args) print(proc.stdout) print(proc.stderr) proc.check_returncode() assert not (temp_dpath / 'profile_output.txt').exists() assert not (temp_dpath / 'profile_output.lprof').exists() temp_dpath.delete() def test_explicit_profile_with_cmdline(): """ Test that explicit profiling is enabled when we specify the --line-profile command line flag. xdoctest ~/code/line_profiler/tests/test_explicit_profile.py test_explicit_profile_with_environ """ temp_dpath = ub.Path(tempfile.mkdtemp()) with ub.ChDir(temp_dpath): script_fpath = ub.Path('script.py') script_fpath.write_text(_demo_explicit_profile_script()) args = [sys.executable, os.fspath(script_fpath), '--line-profile'] print(f'args={args}') proc = ub.cmd(args) print(proc.stdout) print(proc.stderr) proc.check_returncode() assert (temp_dpath / 'profile_output.txt').exists() assert (temp_dpath / 'profile_output.lprof').exists() temp_dpath.delete() def test_explicit_profile_with_kernprof(): """ Test that explicit profiling works when using kernprof. In this case we should get as many output files. """ temp_dpath = ub.Path(tempfile.mkdtemp()) with ub.ChDir(temp_dpath): script_fpath = ub.Path('script.py') script_fpath.write_text(_demo_explicit_profile_script()) args = [sys.executable, '-m', 'kernprof', '-l', os.fspath(script_fpath)] proc = ub.cmd(args) print(proc.stdout) print(proc.stderr) proc.check_returncode() assert not (temp_dpath / 'profile_output.txt').exists() assert (temp_dpath / 'script.py.lprof').exists() temp_dpath.delete() def test_explicit_profile_with_in_code_enable(): """ Test that the user can enable the profiler explicitly from within their code. CommandLine: pytest tests/test_explicit_profile.py -s -k test_explicit_profile_with_in_code_enable """ temp_dpath = ub.Path(tempfile.mkdtemp()) code = ub.codeblock( ''' from line_profiler import profile import ubelt as ub print('') print('') print('start test') print('profile = {}'.format(ub.urepr(profile, nl=1))) print(f'profile._profile={profile._profile}') print(f'profile.enabled={profile.enabled}') @profile def func1(a): return a + 1 profile.enable(output_prefix='custom_output') print('profile = {}'.format(ub.urepr(profile, nl=1))) print(f'profile._profile={profile._profile}') print(f'profile.enabled={profile.enabled}') @profile def func2(a): return a + 1 print('func2 = {}'.format(ub.urepr(func2, nl=1))) profile.disable() @profile def func3(a): return a + 1 profile.enable() @profile def func4(a): return a + 1 func1(1) func2(1) func3(1) func4(1) profile._profile ''') with ub.ChDir(temp_dpath): script_fpath = ub.Path('script.py') script_fpath.write_text(code) args = [sys.executable, os.fspath(script_fpath)] proc = ub.cmd(args) print(proc.stdout) print(proc.stderr) proc.check_returncode() print('Finished running script') output_fpath = (temp_dpath / 'custom_output.txt') raw_output = output_fpath.read_text() print(f'Contents of {output_fpath}') print(raw_output) assert 'Function: func1' not in raw_output assert 'Function: func2' in raw_output assert 'Function: func3' not in raw_output assert 'Function: func4' in raw_output assert output_fpath.exists() assert (temp_dpath / 'custom_output.lprof').exists() temp_dpath.delete() def test_explicit_profile_with_duplicate_functions(): """ Test profiling duplicate functions with the explicit profiler CommandLine: pytest -sv tests/test_explicit_profile.py -k test_explicit_profile_with_duplicate_functions """ temp_dpath = ub.Path(tempfile.mkdtemp()) code = ub.codeblock( ''' from line_profiler import profile @profile def func1(a): return a + 1 @profile def func2(a): return a + 1 @profile def func3(a): return a + 1 @profile def func4(a): return a + 1 func1(1) func2(1) func3(1) func4(1) ''').strip() with ub.ChDir(temp_dpath): script_fpath = ub.Path('script.py') script_fpath.write_text(code) args = [sys.executable, os.fspath(script_fpath), '--line-profile'] proc = ub.cmd(args) print(proc.stdout) print(proc.stderr) proc.check_returncode() output_fpath = (temp_dpath / 'profile_output.txt') raw_output = output_fpath.read_text() print(raw_output) assert 'Function: func1' in raw_output assert 'Function: func2' in raw_output assert 'Function: func3' in raw_output assert 'Function: func4' in raw_output assert output_fpath.exists() assert (temp_dpath / 'profile_output.lprof').exists() temp_dpath.delete() if __name__ == '__main__': ... test_simple_explicit_nonglobal_usage() line_profiler-4.1.2/tests/test_import.py000066400000000000000000000011071452103132100204230ustar00rootroot00000000000000def test_import(): import line_profiler assert hasattr(line_profiler, 'LineProfiler') assert hasattr(line_profiler, '__version__') def test_version(): import line_profiler from packaging.version import Version import kernprof line_profiler_version1 = Version(line_profiler.__version__) line_profiler_version2 = Version(line_profiler.line_profiler.__version__) kernprof_version = Version(kernprof.__version__) assert line_profiler_version1 == line_profiler_version2 == kernprof_version, ( 'All 3 places should have the same version') line_profiler-4.1.2/tests/test_ipython.py000066400000000000000000000020671452103132100206110ustar00rootroot00000000000000import unittest class TestIPython(unittest.TestCase): def test_init(self): """ CommandLine: pytest -k test_init -s -v """ try: from IPython.testing.globalipapp import get_ipython except ImportError: import pytest pytest.skip() ip = get_ipython() ip.run_line_magic('load_ext', 'line_profiler') ip.run_cell(raw_cell='def func():\n return 2**20') lprof = ip.run_line_magic('lprun', '-r -f func func()') timings = lprof.get_stats().timings self.assertEqual(len(timings), 1) # 1 function func_data, lines_data = next(iter(timings.items())) print(f'func_data={func_data}') print(f'lines_data={lines_data}') self.assertEqual(func_data[1], 1) # lineno of the function self.assertEqual(func_data[2], "func") # function name self.assertEqual(len(lines_data), 1) # 1 line of code self.assertEqual(lines_data[0][0], 2) # lineno self.assertEqual(lines_data[0][1], 1) # hits line_profiler-4.1.2/tests/test_kernprof.py000066400000000000000000000046671452103132100207550ustar00rootroot00000000000000import unittest from kernprof import ContextualProfile def f(x): """ A function. """ y = x + 10 return y def g(x): """ A generator. """ y = yield x + 10 yield y + 20 class TestKernprof(unittest.TestCase): def test_enable_disable(self): profile = ContextualProfile() self.assertEqual(profile.enable_count, 0) profile.enable_by_count() self.assertEqual(profile.enable_count, 1) profile.enable_by_count() self.assertEqual(profile.enable_count, 2) profile.disable_by_count() self.assertEqual(profile.enable_count, 1) profile.disable_by_count() self.assertEqual(profile.enable_count, 0) profile.disable_by_count() self.assertEqual(profile.enable_count, 0) with profile: self.assertEqual(profile.enable_count, 1) with profile: self.assertEqual(profile.enable_count, 2) self.assertEqual(profile.enable_count, 1) self.assertEqual(profile.enable_count, 0) with self.assertRaises(RuntimeError): self.assertEqual(profile.enable_count, 0) with profile: self.assertEqual(profile.enable_count, 1) raise RuntimeError() self.assertEqual(profile.enable_count, 0) def test_function_decorator(self): profile = ContextualProfile() f_wrapped = profile(f) self.assertEqual(f_wrapped.__name__, f.__name__) self.assertEqual(f_wrapped.__doc__, f.__doc__) self.assertEqual(profile.enable_count, 0) value = f_wrapped(10) self.assertEqual(profile.enable_count, 0) self.assertEqual(value, f(10)) def test_gen_decorator(self): profile = ContextualProfile() g_wrapped = profile(g) self.assertEqual(g_wrapped.__name__, g.__name__) self.assertEqual(g_wrapped.__doc__, g.__doc__) self.assertEqual(profile.enable_count, 0) i = g_wrapped(10) self.assertEqual(profile.enable_count, 0) self.assertEqual(next(i), 20) self.assertEqual(profile.enable_count, 0) self.assertEqual(i.send(30), 50) self.assertEqual(profile.enable_count, 0) with self.assertRaises((StopIteration, RuntimeError)): next(i) self.assertEqual(profile.enable_count, 0) if __name__ == '__main__': """ CommandLine: python ./tests/test_kernprof.py """ unittest.main() line_profiler-4.1.2/tests/test_line_profiler.py000066400000000000000000000075311452103132100217510ustar00rootroot00000000000000import pytest from line_profiler import LineProfiler def f(x): """A docstring.""" y = x + 10 return y def g(x): y = yield x + 10 yield y + 20 class C: @classmethod def c(self, value): print(value) return 0 def test_init(): lp = LineProfiler() assert lp.functions == [] assert lp.code_map == {} lp = LineProfiler(f) assert lp.functions == [f] assert lp.code_map == {f.__code__: {}} lp = LineProfiler(f, g) assert lp.functions == [f, g] assert lp.code_map == { f.__code__: {}, g.__code__: {}, } def test_enable_disable(): lp = LineProfiler() assert lp.enable_count == 0 lp.enable_by_count() assert lp.enable_count == 1 lp.enable_by_count() assert lp.enable_count == 2 lp.disable_by_count() assert lp.enable_count == 1 lp.disable_by_count() assert lp.enable_count == 0 assert lp.last_time == {} lp.disable_by_count() assert lp.enable_count == 0 with lp: assert lp.enable_count == 1 with lp: assert lp.enable_count == 2 assert lp.enable_count == 1 assert lp.enable_count == 0 assert lp.last_time == {} with pytest.raises(RuntimeError): assert lp.enable_count == 0 with lp: assert lp.enable_count == 1 raise RuntimeError() assert lp.enable_count == 0 assert lp.last_time == {} def test_function_decorator(): profile = LineProfiler() f_wrapped = profile(f) assert f_wrapped.__name__ == 'f' assert profile.enable_count == 0 value = f_wrapped(10) assert profile.enable_count == 0 assert value == f(10) def test_gen_decorator(): profile = LineProfiler() g_wrapped = profile(g) assert g_wrapped.__name__ == 'g' assert profile.enable_count == 0 i = g_wrapped(10) assert profile.enable_count == 0 assert next(i) == 20 assert profile.enable_count == 0 assert i.send(30) == 50 assert profile.enable_count == 0 with pytest.raises(StopIteration): next(i) assert profile.enable_count == 0 def test_classmethod_decorator(): profile = LineProfiler() c_wrapped = profile(C.c) assert c_wrapped.__name__ == 'c' assert profile.enable_count == 0 val = c_wrapped('test') assert profile.enable_count == 0 assert val == C.c('test') assert profile.enable_count == 0 def test_show_func_column_formatting(): from line_profiler.line_profiler import show_func import line_profiler import io # Use a function in this module as an example func = line_profiler.line_profiler.show_text start_lineno = func.__code__.co_firstlineno filename = func.__code__.co_filename func_name = func.__name__ def get_func_linenos(func): import sys if sys.version_info[0:2] >= (3, 10): return sorted(set([t[2] for t in func.__code__.co_lines()])) else: import dis return sorted(set([t[1] for t in dis.findlinestarts(func.__code__)])) line_numbers = get_func_linenos(func) unit = 1.0 output_unit = 1.0 stripzeros = False # Build fake timeings for each line in the example function timings = [ (lineno, idx * 1e13, idx * (2e10 ** (idx % 3))) for idx, lineno in enumerate(line_numbers, start=1) ] stream = io.StringIO() show_func(filename, start_lineno, func_name, timings, unit, output_unit, stream, stripzeros) text = stream.getvalue() print(text) timings = [ (lineno, idx * 1e15, idx * 2e19) for idx, lineno in enumerate(line_numbers, start=1) ] stream = io.StringIO() show_func(filename, start_lineno, func_name, timings, unit, output_unit, stream, stripzeros) text = stream.getvalue() print(text) # TODO: write a check to verify columns are aligned nicely